Compare commits

...

42 Commits

Author SHA1 Message Date
ReinUsesLisp eb914b6c50 video_core: Enforce -Werror=switch
This forces us to fix all -Wswitch warnings in video_core.
2020-09-16 17:48:01 -03:00
Rodrigo Locatti b0ae8265ea Merge pull request #4661 from lioncash/system-loader
core/loader: Remove dependencies on the global system instance
2020-09-16 20:59:44 +00:00
Lioncash 113a3972a6 core/loader: Remove dependencies on the global system instance
Now all that remains is:

18 instances in file_sys code
14 instances in GDB stub code (this can be tossed wholesale)
4 instances in HLE code
2 instances in settings code.
2020-09-16 08:46:59 -04:00
Rodrigo Locatti 004bfefeb5 Merge pull request #4658 from lioncash/copy3
nca_patch: Reduce stack usage size within SearchBucketEntry()
2020-09-16 00:25:11 +00:00
Rodrigo Locatti 9cd1ea338b Merge pull request #4657 from lioncash/cheatparser
cheat_engine: Remove unnecessary system argument to CheatParser's Parse function
2020-09-16 00:24:14 +00:00
Lioncash 66fc037ef2 nca_patch: Significantly reduce the stack usage size within SearchBucketEntry()
Previously this function was using ~16KB of stack (16528 bytes), which
was caused by the function arguments being taken by value rather than by
reference.

We can make this significantly lighter on the stack by taking them by
reference.
2020-09-15 09:10:58 -04:00
Lioncash 99b372a6c5 nca_patch: Make SearchBucketEntry() internally linked
This is only used internally and doesn't depend on any class state, so
we can make it fully internal.
2020-09-15 09:06:46 -04:00
Lioncash 3a8464cde2 cheat_engine: Convert ExtractName into a non-template function
We don't need to create two separate instantiations of the same code, we
can simply make the character template argument a regular function
parameter.
2020-09-15 03:24:44 -04:00
Lioncash ba7eb5abf4 cheat_engine: Remove unnecessary system argument to CheatParser's Parse function
This isn't used within the function at all in any implementations, so we
can remove it entirely.
2020-09-15 03:20:40 -04:00
Rodrigo Locatti b5f4221c3d Merge pull request #4655 from lioncash/internal2
patch_manager: Minor cleanup
2020-09-15 01:57:13 +00:00
Lioncash 33e4a0b6c1 patch_manager: Resolve implicit truncations in FormatTitleVersion()
We make it explicit that we're truncating arithmetic here to resolve
compiler warnings (even if the sizes weren't u32/u64 arithmetic
generally promotes to int :<)
2020-09-14 19:19:59 -04:00
Lioncash a4392c24cf patch_manager: Make use of type aliases
We can use these to avoid typing the same type redundantly. This way, if
these ever change, only a single location needs to be modified.
2020-09-14 19:17:50 -04:00
Lioncash 637ab14ae6 patch_manager: Make a few functions internally linked
These functions are only used within this translation unit, so we can
make them internally linked.
2020-09-14 19:04:51 -04:00
Rodrigo Locatti 0bac7b6a95 Merge pull request #4652 from lioncash/crypto
crypto/key_manager: Remove dependency on the global system accessor
2020-09-14 22:27:33 +00:00
Lioncash e0dd440b1f crypto/key_manager: Remove dependency on the global system accessor
We can supply the content provider as an argument instead of hardcoding
a global accessor in the implementation.
2020-09-14 16:49:59 -04:00
Rodrigo Locatti 1a9774f824 Merge pull request #4651 from lioncash/kernel-global
kernel: Remove all dependencies on the global system instance
2020-09-14 20:39:05 +00:00
Lioncash ec2a6e5ba8 kernel: Remove all dependencies on the global system instance
With this, the kernel finally doesn't depend directly on the global
system instance anymore.
2020-09-14 14:03:13 -04:00
bunnei 042567e4b2 Merge pull request #4636 from lioncash/kernel-hle
service: Remove two usages of the global system accessor
2020-09-14 09:17:10 -07:00
bunnei 5fc6bf96d8 Merge pull request #4323 from ReinUsesLisp/no-spin
kernel/scheduler: Use std::mutex instead of spin lock
2020-09-11 23:23:53 -07:00
bunnei 508f2072a9 Merge pull request #4645 from v1993/lgtm-less-packages
Remove bad and useless packages from LGTM build
2020-09-11 22:10:27 -07:00
bunnei f4400f3ba2 Merge pull request #4638 from Morph1984/qt-5.12.8
cmake: Update to Qt 5.12.8
2020-09-11 15:19:29 -07:00
bunnei ec634b6a88 Merge pull request #4634 from lioncash/blocking
bsd: Resolve a few warnings
2020-09-11 15:17:34 -07:00
bunnei 324029d4f9 Merge pull request #4310 from ogniK5377/apollo-1-prod
audio_core: Apollo Part 1, AudioRenderer refactor
2020-09-11 10:57:27 -04:00
Morph 09126f3a4a cmake: Update to Qt 5.12.8 2020-09-09 08:50:39 -04:00
Lioncash ffdf8c0cb3 service: Remove two usages of the global system accessor
Removes more instances of reliance on global state.
2020-09-07 03:18:45 -04:00
Lioncash 40968e3993 bsd: Resolve unused value within SendToImpl
Previously the address provided to SendToImpl would never be propagated
to SendTo(). This fixes that.
2020-09-07 01:06:30 -04:00
Lioncash cd643ab5c9 bsd: Resolve sign comparison warnings 2020-09-07 01:06:27 -04:00
Lioncash 180fa6859f sockets_translate: Make use of designated initializers
Same behavior, less typing.
2020-09-07 00:53:10 -04:00
Lioncash 188a3cf74c blocking_worker: Make use of templated lambda
We can simplify this a little by explicitly specifying the typename for
the lambda function.
2020-09-07 00:47:46 -04:00
Lioncash 9652973db2 blocking_worker: Resolve -Wdocumentation warning 2020-09-07 00:45:53 -04:00
David Marcec 80ac1331b5 Preliminary effects 2020-08-17 01:23:55 +10:00
David Marcec 1f1c3bddc0 Disable biquad filter 2020-08-14 23:20:20 +10:00
David Marcec 1b3d86c02f Reworked ADPCM decoder to allow better streaming 2020-08-14 21:04:28 +10:00
David Marcec 0947f613b1 mix buffer depopping 2020-08-01 16:25:08 +10:00
David Marcec 1b8fe7073b adpcm streaming 2020-07-30 18:16:57 +10:00
David Marcec 3dcbba38bf Fix perf regression 2020-07-25 21:46:25 +10:00
David Marcec f4eb7dceaf Fix stream channel count when outputting to stereo 2020-07-25 13:31:43 +10:00
David Marcec b924c71822 Address issues 2020-07-25 12:39:37 +10:00
David Marcec 8a497adf85 Queue extra mix buffer 2020-07-25 12:39:36 +10:00
David Marcec d68856ab12 Disable time stretcher for time being 2020-07-25 12:39:35 +10:00
David Marcec 380658c21d audio_core: Apollo Part 1, AudioRenderer refactor 2020-07-25 12:39:34 +10:00
ReinUsesLisp 9b38f4fc55 kernel/scheduler: Use std::mutex instead of spin lock
Profiling shows that this is a highly contested mutex, causing dimishing
results compared to a OS lock. std::mutex implementations can spin for a
while before falling back to an OS lock.

This avoids wasting precious CPU cycles in a no-op.
2020-07-12 21:27:24 -03:00
76 changed files with 5440 additions and 1011 deletions
+1 -1
View File
@@ -210,7 +210,7 @@ if(ENABLE_QT)
set(QT_PREFIX_HINT)
if(YUZU_USE_BUNDLED_QT)
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
set(QT_VER qt-5.12.0-msvc2017_64)
set(QT_VER qt-5.12.8-msvc2017_64)
else()
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
endif()
+16
View File
@@ -12,16 +12,32 @@ add_library(audio_core STATIC
buffer.h
codec.cpp
codec.h
command_generator.cpp
command_generator.h
common.h
effect_context.cpp
effect_context.h
info_updater.cpp
info_updater.h
memory_pool.cpp
memory_pool.h
mix_context.cpp
mix_context.h
null_sink.h
sink.h
sink_context.cpp
sink_context.h
sink_details.cpp
sink_details.h
sink_stream.h
splitter_context.cpp
splitter_context.h
stream.cpp
stream.h
time_stretch.cpp
time_stretch.h
voice_context.cpp
voice_context.h
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
)
+32
View File
@@ -197,4 +197,36 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
return output;
}
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) {
const std::array<s16, 512>& lut = [pitch] {
if (pitch > 0xaaaa) {
return curve_lut0;
}
if (pitch <= 0x8000) {
return curve_lut1;
}
return curve_lut2;
}();
std::size_t index{};
for (std::size_t i = 0; i < sample_count; i++) {
const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4};
const auto l0 = lut[lut_index + 0];
const auto l1 = lut[lut_index + 1];
const auto l2 = lut[lut_index + 2];
const auto l3 = lut[lut_index + 3];
const auto s0 = static_cast<s32>(input[index]);
const auto s1 = static_cast<s32>(input[index + 1]);
const auto s2 = static_cast<s32>(input[index + 2]);
const auto s3 = static_cast<s32>(input[index + 3]);
output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15;
fraction += pitch;
index += (fraction >> 15);
fraction &= 0x7fff;
}
}
} // namespace AudioCore
+3
View File
@@ -38,4 +38,7 @@ inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16>
return Interpolate(state, std::move(input), ratio);
}
/// Nintendo Switchs DSP resampling algorithm. Based on a single channel
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count);
} // namespace AudioCore
+198 -399
View File
@@ -2,95 +2,49 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <vector>
#include "audio_core/algorithm/interpolate.h"
#include "audio_core/audio_out.h"
#include "audio_core/audio_renderer.h"
#include "audio_core/codec.h"
#include "audio_core/common.h"
#include "audio_core/info_updater.h"
#include "audio_core/voice_context.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/writable_event.h"
#include "core/memory.h"
#include "core/settings.h"
namespace AudioCore {
constexpr u32 STREAM_SAMPLE_RATE{48000};
constexpr u32 STREAM_NUM_CHANNELS{2};
using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>;
class AudioRenderer::VoiceState {
public:
bool IsPlaying() const {
return is_in_use && info.play_state == PlayState::Started;
}
const VoiceOutStatus& GetOutStatus() const {
return out_status;
}
const VoiceInfo& GetInfo() const {
return info;
}
VoiceInfo& GetInfo() {
return info;
}
void SetWaveIndex(std::size_t index);
std::vector<s16> DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory,
const VoiceChannelHolder& voice_resources);
void UpdateState();
void RefreshBuffer(Core::Memory::Memory& memory, const VoiceChannelHolder& voice_resources);
private:
bool is_in_use{};
bool is_refresh_pending{};
std::size_t wave_index{};
std::size_t offset{};
Codec::ADPCMState adpcm_state{};
InterpolationState interp_state{};
std::vector<s16> samples;
VoiceOutStatus out_status{};
VoiceInfo info{};
};
class AudioRenderer::EffectState {
public:
const EffectOutStatus& GetOutStatus() const {
return out_status;
}
const EffectInStatus& GetInfo() const {
return info;
}
EffectInStatus& GetInfo() {
return info;
}
void UpdateState(Core::Memory::Memory& memory);
private:
EffectOutStatus out_status{};
EffectInStatus info{};
};
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
AudioRendererParameter params,
AudioCommon::AudioRendererParameter params,
std::shared_ptr<Kernel::WritableEvent> buffer_event,
std::size_t instance_number)
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
voice_resources(params.voice_count), effects(params.effect_count), memory{memory_} {
: worker_params{params}, buffer_event{buffer_event},
memory_pool_info(params.effect_count + params.voice_count * 4),
voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
sink_context(params.sink_count), splitter_context(),
voices(params.voice_count), memory{memory_},
command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context,
memory),
temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) {
behavior_info.SetUserRevision(params.revision);
splitter_context.Initialize(behavior_info, params.splitter_count,
params.num_splitter_send_channels);
mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
audio_out = std::make_unique<AudioCore::AudioOut>();
stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS,
fmt::format("AudioRenderer-Instance{}", instance_number),
[=]() { buffer_event->Signal(); });
stream =
audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
fmt::format("AudioRenderer-Instance{}", instance_number),
[=]() { buffer_event->Signal(); });
audio_out->StartStream(stream);
QueueMixedBuffer(0);
QueueMixedBuffer(1);
QueueMixedBuffer(2);
QueueMixedBuffer(3);
}
AudioRenderer::~AudioRenderer() = default;
@@ -111,355 +65,200 @@ Stream::State AudioRenderer::GetStreamState() const {
return stream->GetState();
}
ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
// Copy UpdateDataHeader struct
UpdateDataHeader config{};
std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader));
u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
if (!behavior_info.UpdateInput(input_params, sizeof(UpdateDataHeader))) {
LOG_ERROR(Audio, "Failed to update behavior info input parameters");
return Audren::ERR_INVALID_PARAMETERS;
}
// Copy MemoryPoolInfo structs
std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
std::memcpy(mem_pool_info.data(),
input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
memory_pool_count * sizeof(MemoryPoolInfo));
// Copy voice resources
const std::size_t voice_resource_offset{sizeof(UpdateDataHeader) + config.behavior_size +
config.memory_pools_size};
std::memcpy(voice_resources.data(), input_params.data() + voice_resource_offset,
sizeof(VoiceResourceInformation) * voice_resources.size());
// Copy VoiceInfo structs
std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
config.memory_pools_size + config.voice_resource_size};
for (auto& voice : voices) {
std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
voice_offset += sizeof(VoiceInfo);
}
std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
config.memory_pools_size + config.voice_resource_size +
config.voices_size};
for (auto& effect : effects) {
std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
effect_offset += sizeof(EffectInStatus);
}
// Update memory pool state
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
for (std::size_t index = 0; index < memory_pool.size(); ++index) {
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
memory_pool[index].state = MemoryPoolStates::Attached;
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
memory_pool[index].state = MemoryPoolStates::Detached;
}
}
// Update voices
for (auto& voice : voices) {
voice.UpdateState();
if (!voice.GetInfo().is_in_use) {
continue;
}
if (voice.GetInfo().is_new) {
voice.SetWaveIndex(voice.GetInfo().wave_buffer_head);
}
}
for (auto& effect : effects) {
effect.UpdateState(memory);
}
// Release previous buffers and queue next ones for playback
ReleaseAndQueueBuffers();
// Copy output header
UpdateDataHeader response_data{worker_params};
if (behavior_info.IsElapsedFrameCountSupported()) {
response_data.render_info = sizeof(RendererInfo);
response_data.total_size += sizeof(RendererInfo);
}
std::vector<u8> output_params(response_data.total_size);
std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
// Copy output memory pool entries
std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(),
response_data.memory_pools_size);
// Copy output voice status
std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size};
for (const auto& voice : voices) {
std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(),
sizeof(VoiceOutStatus));
voice_out_status_offset += sizeof(VoiceOutStatus);
}
std::size_t effect_out_status_offset{
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
response_data.voice_resource_size};
for (const auto& effect : effects) {
std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
sizeof(EffectOutStatus));
effect_out_status_offset += sizeof(EffectOutStatus);
}
// Update behavior info output
const std::size_t behavior_out_status_offset{
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
response_data.effects_size + response_data.sinks_size +
response_data.performance_manager_size};
if (!behavior_info.UpdateOutput(output_params, behavior_out_status_offset)) {
LOG_ERROR(Audio, "Failed to update behavior info output parameters");
return Audren::ERR_INVALID_PARAMETERS;
}
if (behavior_info.IsElapsedFrameCountSupported()) {
const std::size_t renderer_info_offset{
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
response_data.effects_size + response_data.sinks_size +
response_data.performance_manager_size + response_data.behavior_size};
RendererInfo renderer_info{};
renderer_info.elasped_frame_count = elapsed_frame_count;
std::memcpy(output_params.data() + renderer_info_offset, &renderer_info,
sizeof(RendererInfo));
}
return MakeResult(output_params);
}
void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) {
wave_index = index & 3;
is_refresh_pending = true;
}
std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(
std::size_t sample_count, Core::Memory::Memory& memory,
const VoiceChannelHolder& voice_resources) {
if (!IsPlaying()) {
return {};
}
if (is_refresh_pending) {
RefreshBuffer(memory, voice_resources);
}
const std::size_t max_size{samples.size() - offset};
const std::size_t dequeue_offset{offset};
std::size_t size{sample_count * STREAM_NUM_CHANNELS};
if (size > max_size) {
size = max_size;
}
out_status.played_sample_count += size / STREAM_NUM_CHANNELS;
offset += size;
const auto& wave_buffer{info.wave_buffer[wave_index]};
if (offset == samples.size()) {
offset = 0;
if (!wave_buffer.is_looping && wave_buffer.buffer_sz) {
SetWaveIndex(wave_index + 1);
}
if (wave_buffer.buffer_sz) {
out_status.wave_buffer_consumed++;
}
if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) {
info.play_state = PlayState::Paused;
}
}
return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size};
}
void AudioRenderer::VoiceState::UpdateState() {
if (is_in_use && !info.is_in_use) {
// No longer in use, reset state
is_refresh_pending = true;
wave_index = 0;
offset = 0;
out_status = {};
}
is_in_use = info.is_in_use;
}
void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory,
const VoiceChannelHolder& voice_resources) {
const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr;
const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz;
std::vector<s16> new_samples(wave_buffer_size / sizeof(s16));
memory.ReadBlock(wave_buffer_address, new_samples.data(), wave_buffer_size);
switch (static_cast<Codec::PcmFormat>(info.sample_format)) {
case Codec::PcmFormat::Int16: {
// PCM16 is played as-is
break;
}
case Codec::PcmFormat::Adpcm: {
// Decode ADPCM to PCM16
Codec::ADPCM_Coeff coeffs;
memory.ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff));
new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()),
new_samples.size() * sizeof(s16), coeffs, adpcm_state);
break;
}
default:
UNIMPLEMENTED_MSG("Unimplemented sample_format={}", info.sample_format);
break;
}
switch (info.channel_count) {
case 1: {
// 1 channel is upsampled to 2 channel
samples.resize(new_samples.size() * 2);
for (std::size_t index = 0; index < new_samples.size(); ++index) {
auto sample = static_cast<float>(new_samples[index]);
if (voice_resources[0]->in_use) {
sample *= voice_resources[0]->mix_volumes[0];
}
samples[index * 2] = static_cast<s16>(sample * info.volume);
samples[index * 2 + 1] = static_cast<s16>(sample * info.volume);
}
break;
}
case 2: {
// 2 channel is played as is
samples = std::move(new_samples);
const std::size_t sample_count = (samples.size() / 2);
for (std::size_t index = 0; index < sample_count; ++index) {
const std::size_t index_l = index * 2;
const std::size_t index_r = index * 2 + 1;
auto sample_l = static_cast<float>(samples[index_l]);
auto sample_r = static_cast<float>(samples[index_r]);
if (voice_resources[0]->in_use) {
sample_l *= voice_resources[0]->mix_volumes[0];
}
if (voice_resources[1]->in_use) {
sample_r *= voice_resources[1]->mix_volumes[1];
}
samples[index_l] = static_cast<s16>(sample_l * info.volume);
samples[index_r] = static_cast<s16>(sample_r * info.volume);
}
break;
}
case 6: {
samples.resize((new_samples.size() / 6) * 2);
const std::size_t sample_count = samples.size() / 2;
for (std::size_t index = 0; index < sample_count; ++index) {
auto FL = static_cast<float>(new_samples[index * 6]);
auto FR = static_cast<float>(new_samples[index * 6 + 1]);
auto FC = static_cast<float>(new_samples[index * 6 + 2]);
auto BL = static_cast<float>(new_samples[index * 6 + 4]);
auto BR = static_cast<float>(new_samples[index * 6 + 5]);
if (voice_resources[0]->in_use) {
FL *= voice_resources[0]->mix_volumes[0];
}
if (voice_resources[1]->in_use) {
FR *= voice_resources[1]->mix_volumes[1];
}
if (voice_resources[2]->in_use) {
FC *= voice_resources[2]->mix_volumes[2];
}
if (voice_resources[4]->in_use) {
BL *= voice_resources[4]->mix_volumes[4];
}
if (voice_resources[5]->in_use) {
BR *= voice_resources[5]->mix_volumes[5];
}
samples[index * 2] =
static_cast<s16>((0.3694f * FL + 0.2612f * FC + 0.3694f * BL) * info.volume);
samples[index * 2 + 1] =
static_cast<s16>((0.3694f * FR + 0.2612f * FC + 0.3694f * BR) * info.volume);
}
break;
}
default:
UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count);
break;
}
// Only interpolate when necessary, expensive.
if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) {
samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate,
STREAM_SAMPLE_RATE);
}
is_refresh_pending = false;
}
void AudioRenderer::EffectState::UpdateState(Core::Memory::Memory& memory) {
if (info.is_new) {
out_status.state = EffectStatus::New;
} else {
if (info.type == Effect::Aux) {
ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_info) == 0,
"Aux buffers tried to update");
ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_info) == 0,
"Aux buffers tried to update");
ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_base) == 0,
"Aux buffers tried to update");
ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_base) == 0,
"Aux buffers tried to update");
}
}
}
static constexpr s16 ClampToS16(s32 value) {
return static_cast<s16>(std::clamp(value, -32768, 32767));
}
ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
std::vector<u8>& output_params) {
InfoUpdater info_updater{input_params, output_params, behavior_info};
if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
LOG_ERROR(Audio, "Failed to update behavior info input parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
LOG_ERROR(Audio, "Failed to update memory pool parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
LOG_ERROR(Audio, "Failed to update voice parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
// TODO(ogniK): Deal with stopped audio renderer but updates still taking place
if (!info_updater.UpdateEffects(effect_context, true)) {
LOG_ERROR(Audio, "Failed to update effect parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (behavior_info.IsSplitterSupported()) {
if (!info_updater.UpdateSplitterInfo(splitter_context)) {
LOG_ERROR(Audio, "Failed to update splitter parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
}
auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count,
splitter_context, effect_context);
if (mix_result.IsError()) {
LOG_ERROR(Audio, "Failed to update mix parameters");
return mix_result;
}
// TODO(ogniK): Sinks
if (!info_updater.UpdateSinks(sink_context)) {
LOG_ERROR(Audio, "Failed to update sink parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
// TODO(ogniK): Performance buffer
if (!info_updater.UpdatePerformanceBuffer()) {
LOG_ERROR(Audio, "Failed to update performance buffer parameters");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (!info_updater.UpdateErrorInfo(behavior_info)) {
LOG_ERROR(Audio, "Failed to update error info");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (behavior_info.IsElapsedFrameCountSupported()) {
if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
LOG_ERROR(Audio, "Failed to update renderer info");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
}
// TODO(ogniK): Statistics
if (!info_updater.WriteOutputHeader()) {
LOG_ERROR(Audio, "Failed to write output header");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
// TODO(ogniK): Check when all sections are implemented
if (!info_updater.CheckConsumedSize()) {
LOG_ERROR(Audio, "Audio buffers were not consumed!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
ReleaseAndQueueBuffers();
return RESULT_SUCCESS;
}
void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
constexpr std::size_t BUFFER_SIZE{512};
command_generator.PreCommand();
// Clear mix buffers before our next operation
command_generator.ClearMixBuffers();
// If the splitter is not in use, sort our mixes
if (!splitter_context.UsingSplitter()) {
mix_context.SortInfo();
}
// Sort our voices
voice_context.SortInfo();
// Handle samples
command_generator.GenerateVoiceCommands();
command_generator.GenerateSubMixCommands();
command_generator.GenerateFinalMixCommands();
command_generator.PostCommand();
// Base sample size
std::size_t BUFFER_SIZE{worker_params.sample_count};
// Samples
std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
// Make sure to clear our samples
std::memset(buffer.data(), 0, buffer.size() * sizeof(s16));
for (auto& voice : voices) {
if (!voice.IsPlaying()) {
continue;
}
VoiceChannelHolder resources{};
for (u32 channel = 0; channel < voice.GetInfo().channel_count; channel++) {
const auto channel_resource_id = voice.GetInfo().voice_channel_resource_ids[channel];
resources[channel] = &voice_resources[channel_resource_id];
if (sink_context.InUse()) {
const auto stream_channel_count = stream->GetNumChannels();
const auto buffer_offsets = sink_context.OutputBuffers();
const auto channel_count = buffer_offsets.size();
const auto& final_mix = mix_context.GetFinalMixInfo();
const auto& in_params = final_mix.GetInParams();
std::vector<s32*> mix_buffers(channel_count);
for (std::size_t i = 0; i < channel_count; i++) {
mix_buffers[i] =
command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
}
std::size_t offset{};
s64 samples_remaining{BUFFER_SIZE};
while (samples_remaining > 0) {
const std::vector<s16> samples{
voice.DequeueSamples(samples_remaining, memory, resources)};
for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
if (channel_count == 1) {
const auto sample = ClampToS16(mix_buffers[0][i]);
buffer[i * stream_channel_count + 0] = sample;
if (stream_channel_count > 1) {
buffer[i * stream_channel_count + 1] = sample;
}
if (stream_channel_count == 6) {
buffer[i * stream_channel_count + 2] = sample;
buffer[i * stream_channel_count + 4] = sample;
buffer[i * stream_channel_count + 5] = sample;
}
} else if (channel_count == 2) {
const auto l_sample = ClampToS16(mix_buffers[0][i]);
const auto r_sample = ClampToS16(mix_buffers[1][i]);
if (stream_channel_count == 1) {
buffer[i * stream_channel_count + 0] = l_sample;
} else if (stream_channel_count == 2) {
buffer[i * stream_channel_count + 0] = l_sample;
buffer[i * stream_channel_count + 1] = r_sample;
} else if (stream_channel_count == 6) {
buffer[i * stream_channel_count + 0] = l_sample;
buffer[i * stream_channel_count + 1] = r_sample;
if (samples.empty()) {
break;
}
buffer[i * stream_channel_count + 2] =
ClampToS16((static_cast<s32>(l_sample) + static_cast<s32>(r_sample)) / 2);
samples_remaining -= samples.size() / stream->GetNumChannels();
buffer[i * stream_channel_count + 4] = l_sample;
buffer[i * stream_channel_count + 5] = r_sample;
}
for (const auto& sample : samples) {
const s32 buffer_sample{buffer[offset]};
buffer[offset++] =
ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume));
} else if (channel_count == 6) {
const auto fl_sample = ClampToS16(mix_buffers[0][i]);
const auto fr_sample = ClampToS16(mix_buffers[1][i]);
const auto fc_sample = ClampToS16(mix_buffers[2][i]);
const auto lf_sample = ClampToS16(mix_buffers[3][i]);
const auto bl_sample = ClampToS16(mix_buffers[4][i]);
const auto br_sample = ClampToS16(mix_buffers[5][i]);
if (stream_channel_count == 1) {
buffer[i * stream_channel_count + 0] = fc_sample;
} else if (stream_channel_count == 2) {
buffer[i * stream_channel_count + 0] =
static_cast<s16>(0.3694f * static_cast<float>(fl_sample) +
0.2612f * static_cast<float>(fc_sample) +
0.3694f * static_cast<float>(bl_sample));
buffer[i * stream_channel_count + 1] =
static_cast<s16>(0.3694f * static_cast<float>(fr_sample) +
0.2612f * static_cast<float>(fc_sample) +
0.3694f * static_cast<float>(br_sample));
} else if (stream_channel_count == 6) {
buffer[i * stream_channel_count + 0] = fl_sample;
buffer[i * stream_channel_count + 1] = fr_sample;
buffer[i * stream_channel_count + 2] = fc_sample;
buffer[i * stream_channel_count + 3] = lf_sample;
buffer[i * stream_channel_count + 4] = bl_sample;
buffer[i * stream_channel_count + 5] = br_sample;
}
}
}
}
audio_out->QueueBuffer(stream, tag, std::move(buffer));
elapsed_frame_count++;
voice_context.UpdateStateByDspShared();
}
void AudioRenderer::ReleaseAndQueueBuffers() {
+20 -204
View File
@@ -9,8 +9,15 @@
#include <vector>
#include "audio_core/behavior_info.h"
#include "audio_core/command_generator.h"
#include "audio_core/common.h"
#include "audio_core/effect_context.h"
#include "audio_core/memory_pool.h"
#include "audio_core/mix_context.h"
#include "audio_core/sink_context.h"
#include "audio_core/splitter_context.h"
#include "audio_core/stream.h"
#include "audio_core/voice_context.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
@@ -30,220 +37,25 @@ class Memory;
}
namespace AudioCore {
using DSPStateHolder = std::array<VoiceState*, 6>;
class AudioOut;
enum class PlayState : u8 {
Started = 0,
Stopped = 1,
Paused = 2,
};
enum class Effect : u8 {
None = 0,
Aux = 2,
};
enum class EffectStatus : u8 {
None = 0,
New = 1,
};
struct AudioRendererParameter {
u32_le sample_rate;
u32_le sample_count;
u32_le mix_buffer_count;
u32_le submix_count;
u32_le voice_count;
u32_le sink_count;
u32_le effect_count;
u32_le performance_frame_count;
u8 is_voice_drop_enabled;
u8 unknown_21;
u8 unknown_22;
u8 execution_mode;
u32_le splitter_count;
u32_le num_splitter_send_channels;
u32_le unknown_30;
u32_le revision;
};
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
enum class MemoryPoolStates : u32 { // Should be LE
Invalid = 0x0,
Unknown = 0x1,
RequestDetach = 0x2,
Detached = 0x3,
RequestAttach = 0x4,
Attached = 0x5,
Released = 0x6,
};
struct MemoryPoolEntry {
MemoryPoolStates state;
u32_le unknown_4;
u32_le unknown_8;
u32_le unknown_c;
};
static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
struct MemoryPoolInfo {
u64_le pool_address;
u64_le pool_size;
MemoryPoolStates pool_state;
INSERT_PADDING_WORDS(3); // Unknown
};
static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
struct BiquadFilter {
u8 enable;
INSERT_PADDING_BYTES(1);
std::array<s16_le, 3> numerator;
std::array<s16_le, 2> denominator;
};
static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
struct WaveBuffer {
u64_le buffer_addr;
u64_le buffer_sz;
s32_le start_sample_offset;
s32_le end_sample_offset;
u8 is_looping;
u8 end_of_stream;
u8 sent_to_server;
INSERT_PADDING_BYTES(5);
u64 context_addr;
u64 context_sz;
INSERT_PADDING_BYTES(8);
};
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
struct VoiceResourceInformation {
s32_le id{};
std::array<float_le, MAX_MIX_BUFFERS> mix_volumes{};
bool in_use{};
INSERT_PADDING_BYTES(11);
};
static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size");
struct VoiceInfo {
u32_le id;
u32_le node_id;
u8 is_new;
u8 is_in_use;
PlayState play_state;
u8 sample_format;
u32_le sample_rate;
u32_le priority;
u32_le sorting_order;
u32_le channel_count;
float_le pitch;
float_le volume;
std::array<BiquadFilter, 2> biquad_filter;
u32_le wave_buffer_count;
u32_le wave_buffer_head;
INSERT_PADDING_WORDS(1);
u64_le additional_params_addr;
u64_le additional_params_sz;
u32_le mix_id;
u32_le splitter_info_id;
std::array<WaveBuffer, 4> wave_buffer;
std::array<u32_le, 6> voice_channel_resource_ids;
INSERT_PADDING_BYTES(24);
};
static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
struct VoiceOutStatus {
u64_le played_sample_count;
u32_le wave_buffer_consumed;
u32_le voice_drops_count;
};
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
struct AuxInfo {
std::array<u8, 24> input_mix_buffers;
std::array<u8, 24> output_mix_buffers;
u32_le mix_buffer_count;
u32_le sample_rate; // Stored in the aux buffer currently
u32_le sample_count;
u64_le send_buffer_info;
u64_le send_buffer_base;
u64_le return_buffer_info;
u64_le return_buffer_base;
};
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
struct EffectInStatus {
Effect type;
u8 is_new;
u8 is_enabled;
INSERT_PADDING_BYTES(1);
u32_le mix_id;
u64_le buffer_base;
u64_le buffer_sz;
s32_le priority;
INSERT_PADDING_BYTES(4);
union {
std::array<u8, 0xa0> raw;
AuxInfo aux_info;
};
};
static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
struct EffectOutStatus {
EffectStatus state;
INSERT_PADDING_BYTES(0xf);
};
static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
struct RendererInfo {
u64_le elasped_frame_count{};
INSERT_PADDING_WORDS(2);
};
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
struct UpdateDataHeader {
UpdateDataHeader() {}
explicit UpdateDataHeader(const AudioRendererParameter& config) {
revision = Common::MakeMagic('R', 'E', 'V', '8'); // 9.2.0 Revision
behavior_size = 0xb0;
memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
voices_size = config.voice_count * 0x10;
voice_resource_size = 0x0;
effects_size = config.effect_count * 0x10;
mixes_size = 0x0;
sinks_size = config.sink_count * 0x20;
performance_manager_size = 0x10;
render_info = 0;
total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
effects_size + sinks_size + performance_manager_size;
}
u32_le revision{};
u32_le behavior_size{};
u32_le memory_pools_size{};
u32_le voices_size{};
u32_le voice_resource_size{};
u32_le effects_size{};
u32_le mixes_size{};
u32_le sinks_size{};
u32_le performance_manager_size{};
u32_le splitter_size{};
u32_le render_info{};
INSERT_PADDING_WORDS(4);
u32_le total_size{};
};
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
class AudioRenderer {
public:
AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
AudioRendererParameter params,
AudioCommon::AudioRendererParameter params,
std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number);
~AudioRenderer();
ResultVal<std::vector<u8>> UpdateAudioRenderer(const std::vector<u8>& input_params);
ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
std::vector<u8>& output_params);
void QueueMixedBuffer(Buffer::Tag tag);
void ReleaseAndQueueBuffers();
u32 GetSampleRate() const;
@@ -252,19 +64,23 @@ public:
Stream::State GetStreamState() const;
private:
class EffectState;
class VoiceState;
BehaviorInfo behavior_info{};
AudioRendererParameter worker_params;
AudioCommon::AudioRendererParameter worker_params;
std::shared_ptr<Kernel::WritableEvent> buffer_event;
std::vector<ServerMemoryPoolInfo> memory_pool_info;
VoiceContext voice_context;
EffectContext effect_context;
MixContext mix_context;
SinkContext sink_context;
SplitterContext splitter_context;
std::vector<VoiceState> voices;
std::vector<VoiceResourceInformation> voice_resources;
std::vector<EffectState> effects;
std::unique_ptr<AudioOut> audio_out;
StreamPtr stream;
Core::Memory::Memory& memory;
CommandGenerator command_generator;
std::size_t elapsed_frame_count{};
std::vector<s32> temp_mix_buffer{};
};
} // namespace AudioCore
+42 -37
View File
@@ -9,39 +9,11 @@
namespace AudioCore {
BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {}
BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
BehaviorInfo::~BehaviorInfo() = default;
bool BehaviorInfo::UpdateInput(const std::vector<u8>& buffer, std::size_t offset) {
if (!CanConsumeBuffer(buffer.size(), offset, sizeof(InParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
InParams params{};
std::memcpy(&params, buffer.data() + offset, sizeof(InParams));
if (!IsValidRevision(params.revision)) {
LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", params.revision);
return false;
}
if (user_revision != params.revision) {
LOG_ERROR(Audio,
"User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
user_revision, params.revision);
return false;
}
ClearError();
UpdateFlags(params.flags);
// TODO(ogniK): Check input params size when InfoUpdater is used
return true;
}
bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
if (!CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
@@ -65,36 +37,69 @@ void BehaviorInfo::SetUserRevision(u32_le revision) {
user_revision = revision;
}
u32_le BehaviorInfo::GetUserRevision() const {
return user_revision;
}
u32_le BehaviorInfo::GetProcessRevision() const {
return process_revision;
}
bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
return IsRevisionSupported(2, user_revision);
return AudioCommon::IsRevisionSupported(2, user_revision);
}
bool BehaviorInfo::IsSplitterSupported() const {
return IsRevisionSupported(2, user_revision);
return AudioCommon::IsRevisionSupported(2, user_revision);
}
bool BehaviorInfo::IsLongSizePreDelaySupported() const {
return IsRevisionSupported(3, user_revision);
return AudioCommon::IsRevisionSupported(3, user_revision);
}
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const {
return IsRevisionSupported(5, user_revision);
return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const {
return IsRevisionSupported(4, user_revision);
return AudioCommon::IsRevisionSupported(4, user_revision);
}
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const {
return IsRevisionSupported(1, user_revision);
return AudioCommon::IsRevisionSupported(1, user_revision);
}
bool BehaviorInfo::IsElapsedFrameCountSupported() const {
return IsRevisionSupported(5, user_revision);
return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const {
return (flags & 1) != 0;
}
bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
return AudioCommon::IsRevisionSupported(5, user_revision);
}
bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
return AudioCommon::IsRevisionSupported(7, user_revision);
}
bool BehaviorInfo::IsSplitterBugFixed() const {
return AudioCommon::IsRevisionSupported(5, user_revision);
}
void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) {
dst.error_count = static_cast<u32>(error_count);
std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin());
}
} // namespace AudioCore
+33 -27
View File
@@ -14,30 +14,6 @@
namespace AudioCore {
class BehaviorInfo {
public:
explicit BehaviorInfo();
~BehaviorInfo();
bool UpdateInput(const std::vector<u8>& buffer, std::size_t offset);
bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
void ClearError();
void UpdateFlags(u64_le dest_flags);
void SetUserRevision(u32_le revision);
bool IsAdpcmLoopContextBugFixed() const;
bool IsSplitterSupported() const;
bool IsLongSizePreDelaySupported() const;
bool IsAudioRenererProcessingTimeLimit80PercentSupported() const;
bool IsAudioRenererProcessingTimeLimit75PercentSupported() const;
bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
bool IsElapsedFrameCountSupported() const;
bool IsMemoryPoolForceMappingEnabled() const;
private:
u32_le process_revision{};
u32_le user_revision{};
u64_le flags{};
struct ErrorInfo {
u32_le result{};
INSERT_PADDING_WORDS(1);
@@ -45,9 +21,6 @@ private:
};
static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
std::array<ErrorInfo, 10> errors{};
std::size_t error_count{};
struct InParams {
u32_le revision{};
u32_le padding{};
@@ -61,6 +34,39 @@ private:
INSERT_PADDING_BYTES(12);
};
static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
explicit BehaviorInfo();
~BehaviorInfo();
bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
void ClearError();
void UpdateFlags(u64_le dest_flags);
void SetUserRevision(u32_le revision);
u32_le GetUserRevision() const;
u32_le GetProcessRevision() const;
bool IsAdpcmLoopContextBugFixed() const;
bool IsSplitterSupported() const;
bool IsLongSizePreDelaySupported() const;
bool IsAudioRenererProcessingTimeLimit80PercentSupported() const;
bool IsAudioRenererProcessingTimeLimit75PercentSupported() const;
bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
bool IsElapsedFrameCountSupported() const;
bool IsMemoryPoolForceMappingEnabled() const;
bool IsFlushVoiceWaveBuffersSupported() const;
bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
bool IsVoicePitchAndSrcSkippedSupported() const;
bool IsMixInParameterDirtyOnlyUpdateSupported() const;
bool IsSplitterBugFixed() const;
void CopyErrorInfo(OutParams& dst);
private:
u32_le process_revision{};
u32_le user_revision{};
u64_le flags{};
std::array<ErrorInfo, 10> errors{};
std::size_t error_count{};
};
} // namespace AudioCore
+976
View File
@@ -0,0 +1,976 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/algorithm/interpolate.h"
#include "audio_core/command_generator.h"
#include "audio_core/effect_context.h"
#include "audio_core/mix_context.h"
#include "audio_core/voice_context.h"
#include "core/memory.h"
namespace AudioCore {
namespace {
constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00;
constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL;
template <std::size_t N>
void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) {
for (std::size_t i = 0; i < static_cast<std::size_t>(sample_count); i += N) {
for (std::size_t j = 0; j < N; j++) {
output[i + j] +=
static_cast<s32>((static_cast<s64>(input[i + j]) * gain + 0x4000) >> 15);
}
}
}
s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sample_count) {
s32 x = 0;
for (s32 i = 0; i < sample_count; i++) {
x = static_cast<s32>(static_cast<float>(input[i]) * gain);
output[i] += x;
gain += delta;
}
return x;
}
void ApplyGain(s32* output, const s32* input, s32 gain, s32 delta, s32 sample_count) {
for (s32 i = 0; i < sample_count; i++) {
output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
gain += delta;
}
}
void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_count) {
for (s32 i = 0; i < sample_count; i++) {
output[i] = static_cast<s32>((static_cast<s64>(input[i]) * gain + 0x4000) >> 15);
}
}
s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) {
const bool positive = first_sample > 0;
auto final_sample = std::abs(first_sample);
for (s32 i = 0; i < sample_count; i++) {
final_sample = static_cast<s32>((static_cast<s64>(final_sample) * delta) >> 15);
if (positive) {
output[i] += final_sample;
} else {
output[i] -= final_sample;
}
}
if (positive) {
return final_sample;
} else {
return -final_sample;
}
}
} // namespace
CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
VoiceContext& voice_context, MixContext& mix_context,
SplitterContext& splitter_context, EffectContext& effect_context,
Core::Memory::Memory& memory)
: worker_params(worker_params), voice_context(voice_context), mix_context(mix_context),
splitter_context(splitter_context), effect_context(effect_context), memory(memory),
mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
worker_params.sample_count),
sample_buffer(MIX_BUFFER_SIZE),
depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) *
worker_params.sample_count) {}
CommandGenerator::~CommandGenerator() = default;
void CommandGenerator::ClearMixBuffers() {
std::fill(mix_buffer.begin(), mix_buffer.end(), 0);
std::fill(sample_buffer.begin(), sample_buffer.end(), 0);
// std::fill(depop_buffer.begin(), depop_buffer.end(), 0);
}
void CommandGenerator::GenerateVoiceCommands() {
if (dumping_frame) {
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands");
}
// Grab all our voices
const auto voice_count = voice_context.GetVoiceCount();
for (std::size_t i = 0; i < voice_count; i++) {
auto& voice_info = voice_context.GetSortedInfo(i);
// Update voices and check if we should queue them
if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) {
continue;
}
// Queue our voice
GenerateVoiceCommand(voice_info);
}
// Update our splitters
splitter_context.UpdateInternalState();
}
void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) {
auto& in_params = voice_info.GetInParams();
const auto channel_count = in_params.channel_count;
for (s32 channel = 0; channel < channel_count; channel++) {
const auto resource_id = in_params.voice_channel_resource_id[channel];
auto& dsp_state = voice_context.GetDspSharedState(resource_id);
auto& channel_resource = voice_context.GetChannelResource(resource_id);
// Decode our samples for our channel
GenerateDataSourceCommand(voice_info, dsp_state, channel);
if (in_params.should_depop) {
in_params.last_volume = 0.0f;
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER ||
in_params.mix_id != AudioCommon::NO_MIX) {
// Apply a biquad filter if needed
GenerateBiquadFilterCommandForVoice(voice_info, dsp_state,
worker_params.mix_buffer_count, channel);
// Base voice volume ramping
GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel,
in_params.node_id);
in_params.last_volume = in_params.volume;
if (in_params.mix_id != AudioCommon::NO_MIX) {
// If we're using a mix id
auto& mix_info = mix_context.GetInfo(in_params.mix_id);
const auto& dest_mix_params = mix_info.GetInParams();
// Voice Mixing
GenerateVoiceMixCommand(
channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(),
dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
worker_params.mix_buffer_count + channel, in_params.node_id);
// Update last mix volumes
channel_resource.UpdateLastMixVolumes();
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
s32 base = channel;
while (auto* destination_data =
GetDestinationData(in_params.splitter_info_id, base)) {
base += channel_count;
if (!destination_data->IsConfigured()) {
continue;
}
if (destination_data->GetMixId() >= mix_context.GetCount()) {
continue;
}
const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId());
const auto& dest_mix_params = mix_info.GetInParams();
GenerateVoiceMixCommand(
destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(),
dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count,
worker_params.mix_buffer_count + channel, in_params.node_id);
destination_data->MarkDirty();
}
}
// Update biquad filter enabled states
for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled;
}
}
}
}
void CommandGenerator::GenerateSubMixCommands() {
const auto mix_count = mix_context.GetCount();
for (std::size_t i = 0; i < mix_count; i++) {
auto& mix_info = mix_context.GetSortedInfo(i);
const auto& in_params = mix_info.GetInParams();
if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) {
continue;
}
GenerateSubMixCommand(mix_info);
}
}
void CommandGenerator::GenerateFinalMixCommands() {
GenerateFinalMixCommand();
}
void CommandGenerator::PreCommand() {
if (!dumping_frame) {
return;
}
for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) {
const auto& base = splitter_context.GetInfo(i);
std::string graph = fmt::format("b[{}]", i);
auto* head = base.GetHead();
while (head != nullptr) {
graph += fmt::format("->{}", head->GetMixId());
head = head->GetNextDestination();
}
LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph);
}
}
void CommandGenerator::PostCommand() {
if (!dumping_frame) {
return;
}
dumping_frame = false;
}
void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
s32 channel) {
auto& in_params = voice_info.GetInParams();
const auto depop = in_params.should_depop;
if (depop) {
if (in_params.mix_id != AudioCommon::NO_MIX) {
auto& mix_info = mix_context.GetInfo(in_params.mix_id);
const auto& mix_in = mix_info.GetInParams();
GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
} else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) {
s32 index{};
while (const auto* destination =
GetDestinationData(in_params.splitter_info_id, index++)) {
if (!destination->IsConfigured()) {
continue;
}
auto& mix_info = mix_context.GetInfo(destination->GetMixId());
const auto& mix_in = mix_info.GetInParams();
GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset);
}
}
} else {
switch (in_params.sample_format) {
case SampleFormat::Pcm16:
DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel,
worker_params.sample_rate, worker_params.sample_count,
in_params.node_id);
break;
case SampleFormat::Adpcm:
ASSERT(channel == 0 && in_params.channel_count == 1);
DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0,
worker_params.sample_rate, worker_params.sample_count,
in_params.node_id);
break;
default:
UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
}
}
}
void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info,
VoiceState& dsp_state,
s32 mix_buffer_count, s32 channel) {
for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) {
const auto& in_params = voice_info.GetInParams();
auto& biquad_filter = in_params.biquad_filter[i];
// Check if biquad filter is actually used
if (!biquad_filter.enabled) {
continue;
}
// Reinitialize our biquad filter state if it was enabled previously
if (!in_params.was_biquad_filter_enabled[i]) {
dsp_state.biquad_filter_state.fill(0);
}
// Generate biquad filter
// GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter,
// dsp_state.biquad_filter_state,
// mix_buffer_count + channel, mix_buffer_count +
// channel, worker_params.sample_count,
// voice_info.GetInParams().node_id);
}
}
void AudioCore::CommandGenerator::GenerateBiquadFilterCommand(
s32 mix_buffer, const BiquadFilterParameter& params, std::array<s64, 2>& state,
std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id) {
if (dumping_frame) {
LOG_DEBUG(Audio,
"(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, "
"input_mix_buffer={}, output_mix_buffer={}",
node_id, input_offset, output_offset);
}
const auto* input = GetMixBuffer(input_offset);
auto* output = GetMixBuffer(output_offset);
// Biquad filter parameters
const auto [n0, n1, n2] = params.numerator;
const auto [d0, d1] = params.denominator;
// Biquad filter states
auto [s0, s1] = state;
constexpr s64 int32_min = std::numeric_limits<s32>::min();
constexpr s64 int32_max = std::numeric_limits<s32>::max();
for (int i = 0; i < sample_count; ++i) {
const auto sample = static_cast<s64>(input[i]);
const auto f = (sample * n0 + s0 + 0x4000) >> 15;
const auto y = std::clamp(f, int32_min, int32_max);
s0 = sample * n1 + y * d0 + s1;
s1 = sample * n2 + y * d1;
output[i] = static_cast<s32>(y);
}
state = {s0, s1};
}
void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state,
std::size_t mix_buffer_count,
std::size_t mix_buffer_offset) {
for (std::size_t i = 0; i < mix_buffer_count; i++) {
auto& sample = dsp_state.previous_samples[i];
if (sample != 0) {
depop_buffer[mix_buffer_offset + i] += sample;
sample = 0;
}
}
}
void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
std::size_t mix_buffer_offset,
s32 sample_rate) {
const std::size_t end_offset =
std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount());
const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB;
for (std::size_t i = mix_buffer_offset; i < end_offset; i++) {
if (depop_buffer[i] == 0) {
continue;
}
depop_buffer[i] =
ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count);
}
}
void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) {
const std::size_t effect_count = effect_context.GetCount();
const auto buffer_offset = mix_info.GetInParams().buffer_offset;
for (std::size_t i = 0; i < effect_count; i++) {
const auto index = mix_info.GetEffectOrder(i);
if (index == AudioCommon::NO_EFFECT_ORDER) {
break;
}
auto* info = effect_context.GetInfo(index);
const auto type = info->GetType();
// TODO(ogniK): Finish remaining effects
switch (type) {
case EffectType::Aux:
GenerateAuxCommand(buffer_offset, info, info->IsEnabled());
break;
case EffectType::I3dl2Reverb:
GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled());
break;
case EffectType::BiquadFilter:
GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled());
break;
default:
break;
}
info->UpdateForCommandGeneration();
}
}
void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info,
bool enabled) {
if (!enabled) {
return;
}
const auto& params = dynamic_cast<EffectI3dl2Reverb*>(info)->GetParams();
const auto channel_count = params.channel_count;
for (s32 i = 0; i < channel_count; i++) {
// TODO(ogniK): Actually implement reverb
if (params.input[i] != params.output[i]) {
const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
ApplyMix<1>(output, input, 32768, worker_params.sample_count);
}
}
}
void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info,
bool enabled) {
if (!enabled) {
return;
}
const auto& params = dynamic_cast<EffectBiquadFilter*>(info)->GetParams();
const auto channel_count = params.channel_count;
for (s32 i = 0; i < channel_count; i++) {
// TODO(ogniK): Actually implement biquad filter
if (params.input[i] != params.output[i]) {
const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]);
auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]);
ApplyMix<1>(output, input, 32768, worker_params.sample_count);
}
}
}
void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) {
auto aux = dynamic_cast<EffectAuxInfo*>(info);
const auto& params = aux->GetParams();
if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) {
const auto max_channels = params.count;
u32 offset{};
for (u32 channel = 0; channel < max_channels; channel++) {
u32 write_count = 0;
if (channel == (max_channels - 1)) {
write_count = offset + worker_params.sample_count;
}
const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset;
const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset;
if (enabled) {
AuxInfoDSP send_info{};
AuxInfoDSP recv_info{};
memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count,
GetMixBuffer(input_index), worker_params.sample_count, offset,
write_count);
memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP));
const auto samples_read = ReadAuxBuffer(
recv_info, aux->GetRecvBuffer(), params.sample_count,
GetMixBuffer(output_index), worker_params.sample_count, offset, write_count);
memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP));
if (samples_read != worker_params.sample_count &&
samples_read <= params.sample_count) {
std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read);
}
} else {
AuxInfoDSP empty{};
memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP));
memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP));
if (output_index != input_index) {
std::memcpy(GetMixBuffer(output_index), GetMixBuffer(input_index),
worker_params.sample_count * sizeof(s32));
}
}
offset += worker_params.sample_count;
}
}
}
ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) {
if (splitter_id == AudioCommon::NO_SPLITTER) {
return nullptr;
}
return splitter_context.GetDestinationData(splitter_id, index);
}
s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples,
const s32* data, u32 sample_count, u32 write_offset,
u32 write_count) {
if (max_samples == 0) {
return 0;
}
u32 offset = dsp_info.write_offset + write_offset;
if (send_buffer == 0 || offset > max_samples) {
return 0;
}
std::size_t data_offset{};
u32 remaining = sample_count;
while (remaining > 0) {
// Get position in buffer
const auto base = send_buffer + (offset * sizeof(u32));
const auto samples_to_grab = std::min(max_samples - offset, remaining);
// Write to output
memory.WriteBlock(base, (data + data_offset), samples_to_grab * sizeof(u32));
offset = (offset + samples_to_grab) % max_samples;
remaining -= samples_to_grab;
data_offset += samples_to_grab;
}
if (write_count != 0) {
dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples;
}
return sample_count;
}
s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples,
s32* out_data, u32 sample_count, u32 read_offset,
u32 read_count) {
if (max_samples == 0) {
return 0;
}
u32 offset = recv_info.read_offset + read_offset;
if (recv_buffer == 0 || offset > max_samples) {
return 0;
}
u32 remaining = sample_count;
while (remaining > 0) {
const auto base = recv_buffer + (offset * sizeof(u32));
const auto samples_to_grab = std::min(max_samples - offset, remaining);
std::vector<s32> buffer(samples_to_grab);
memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32));
std::memcpy(out_data, buffer.data(), buffer.size() * sizeof(u32));
out_data += samples_to_grab;
offset = (offset + samples_to_grab) % max_samples;
remaining -= samples_to_grab;
}
if (read_count != 0) {
recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples;
}
return sample_count;
}
void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume,
s32 channel, s32 node_id) {
const auto last = static_cast<s32>(last_volume * 32768.0f);
const auto current = static_cast<s32>(current_volume * 32768.0f);
const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) /
static_cast<float>(worker_params.sample_count));
if (dumping_frame) {
LOG_DEBUG(Audio,
"(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, "
"last_volume={}, current_volume={}",
node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel),
last_volume, current_volume);
}
// Apply generic gain on samples
ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta,
worker_params.sample_count);
}
void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
const MixVolumeBuffer& last_mix_volumes,
VoiceState& dsp_state, s32 mix_buffer_offset,
s32 mix_buffer_count, s32 voice_index, s32 node_id) {
// Loop all our mix buffers
for (s32 i = 0; i < mix_buffer_count; i++) {
if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) {
const auto delta = static_cast<float>((mix_volumes[i] - last_mix_volumes[i])) /
static_cast<float>(worker_params.sample_count);
if (dumping_frame) {
LOG_DEBUG(Audio,
"(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, "
"output={}, last_volume={}, current_volume={}",
node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i],
mix_volumes[i]);
}
dsp_state.previous_samples[i] =
ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index),
last_mix_volumes[i], delta, worker_params.sample_count);
} else {
dsp_state.previous_samples[i] = 0;
}
}
}
void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) {
if (dumping_frame) {
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand");
}
auto& in_params = mix_info.GetInParams();
GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
in_params.sample_rate);
GenerateEffectCommand(mix_info);
GenerateMixCommands(mix_info);
}
void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) {
if (!mix_info.HasAnyConnection()) {
return;
}
const auto& in_params = mix_info.GetInParams();
if (in_params.dest_mix_id != AudioCommon::NO_MIX) {
const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id);
const auto& dest_in_params = dest_mix.GetInParams();
const auto buffer_count = in_params.buffer_count;
for (s32 i = 0; i < buffer_count; i++) {
for (s32 j = 0; j < dest_in_params.buffer_count; j++) {
const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j];
if (mixed_volume != 0.0f) {
GenerateMixCommand(dest_in_params.buffer_offset + j,
in_params.buffer_offset + i, mixed_volume,
in_params.node_id);
}
}
}
} else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) {
s32 base{};
while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) {
if (!destination_data->IsConfigured()) {
continue;
}
const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId());
const auto& dest_in_params = dest_mix.GetInParams();
const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset;
for (std::size_t i = 0; i < dest_in_params.buffer_count; i++) {
const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i);
if (mixed_volume != 0.0f) {
GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume,
in_params.node_id);
}
}
}
}
}
void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset,
float volume, s32 node_id) {
if (dumping_frame) {
LOG_DEBUG(Audio,
"(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}",
node_id, input_offset, output_offset, volume);
}
auto* output = GetMixBuffer(output_offset);
const auto* input = GetMixBuffer(input_offset);
const s32 gain = static_cast<s32>(volume * 32768.0f);
// Mix with loop unrolling
if (worker_params.sample_count % 4 == 0) {
ApplyMix<4>(output, input, gain, worker_params.sample_count);
} else if (worker_params.sample_count % 2 == 0) {
ApplyMix<2>(output, input, gain, worker_params.sample_count);
} else {
ApplyMix<1>(output, input, gain, worker_params.sample_count);
}
}
void CommandGenerator::GenerateFinalMixCommand() {
if (dumping_frame) {
LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand");
}
auto& mix_info = mix_context.GetFinalMixInfo();
const auto in_params = mix_info.GetInParams();
GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset,
in_params.sample_rate);
GenerateEffectCommand(mix_info);
for (s32 i = 0; i < in_params.buffer_count; i++) {
const s32 gain = static_cast<s32>(in_params.volume * 32768.0f);
if (dumping_frame) {
LOG_DEBUG(
Audio,
"(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}",
in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i,
in_params.volume);
}
ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i),
GetMixBuffer(in_params.buffer_offset + i), gain,
worker_params.sample_count);
}
}
s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
s32 sample_count, s32 channel, std::size_t mix_offset) {
auto& in_params = voice_info.GetInParams();
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
if (wave_buffer.buffer_address == 0) {
return 0;
}
if (wave_buffer.buffer_size == 0) {
return 0;
}
if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
return 0;
}
const auto samples_remaining =
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
const auto start_offset =
((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) *
sizeof(s16);
const auto buffer_pos = wave_buffer.buffer_address + start_offset;
const auto samples_processed = std::min(sample_count, samples_remaining);
if (in_params.channel_count == 1) {
std::vector<s16> buffer(samples_processed);
memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
for (std::size_t i = 0; i < buffer.size(); i++) {
sample_buffer[mix_offset + i] = buffer[i];
}
} else {
const auto channel_count = in_params.channel_count;
std::vector<s16> buffer(samples_processed * channel_count);
memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16));
for (std::size_t i = 0; i < samples_processed; i++) {
sample_buffer[mix_offset + i] = buffer[i * channel_count + channel];
}
}
return samples_processed;
}
s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
s32 sample_count, s32 channel, std::size_t mix_offset) {
auto& in_params = voice_info.GetInParams();
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
if (wave_buffer.buffer_address == 0) {
return 0;
}
if (wave_buffer.buffer_size == 0) {
return 0;
}
if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) {
return 0;
}
constexpr std::array<int, 16> SIGNED_NIBBLES = {
{0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}};
constexpr std::size_t FRAME_LEN = 8;
constexpr std::size_t NIBBLES_PER_SAMPLE = 16;
constexpr std::size_t SAMPLES_PER_FRAME = 14;
auto frame_header = dsp_state.context.header;
s32 idx = (frame_header >> 4) & 0xf;
s32 scale = frame_header & 0xf;
s16 yn1 = dsp_state.context.yn1;
s16 yn2 = dsp_state.context.yn2;
Codec::ADPCM_Coeff coeffs;
memory.ReadBlock(in_params.additional_params_address, coeffs.data(),
sizeof(Codec::ADPCM_Coeff));
s32 coef1 = coeffs[idx * 2];
s32 coef2 = coeffs[idx * 2 + 1];
const auto samples_remaining =
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset;
const auto samples_processed = std::min(sample_count, samples_remaining);
const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset;
const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME;
auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) +
samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0);
const auto decode_sample = [&](const int nibble) -> s16 {
const int xn = nibble * (1 << scale);
// We first transform everything into 11 bit fixed point, perform the second order
// digital filter, then transform back.
// 0x400 == 0.5 in 11 bit fixed point.
// Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
// Clamp to output range.
val = std::clamp<s32>(val, -32768, 32767);
// Advance output feedback.
yn2 = yn1;
yn1 = val;
return static_cast<s16>(val);
};
std::size_t buffer_offset{};
std::vector<u8> buffer(
std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN));
memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(),
buffer.size());
std::size_t cur_mix_offset = mix_offset;
auto remaining_samples = samples_processed;
while (remaining_samples > 0) {
if (position_in_frame % NIBBLES_PER_SAMPLE == 0) {
// Read header
frame_header = buffer[buffer_offset++];
idx = (frame_header >> 4) & 0xf;
scale = frame_header & 0xf;
coef1 = coeffs[idx * 2];
coef2 = coeffs[idx * 2 + 1];
position_in_frame += 2;
// Decode entire frame
if (remaining_samples >= SAMPLES_PER_FRAME) {
for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) {
// Sample 1
const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4];
const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf];
const s16 sample_1 = decode_sample(s0);
const s16 sample_2 = decode_sample(s1);
sample_buffer[cur_mix_offset++] = sample_1;
sample_buffer[cur_mix_offset++] = sample_2;
}
remaining_samples -= SAMPLES_PER_FRAME;
position_in_frame += SAMPLES_PER_FRAME;
continue;
}
}
// Decode mid frame
s32 current_nibble = buffer[buffer_offset];
if (position_in_frame++ & 0x1) {
current_nibble &= 0xf;
buffer_offset++;
} else {
current_nibble >>= 4;
}
const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]);
sample_buffer[cur_mix_offset++] = sample;
remaining_samples--;
}
dsp_state.context.header = frame_header;
dsp_state.context.yn1 = yn1;
dsp_state.context.yn2 = yn2;
return samples_processed;
}
s32* CommandGenerator::GetMixBuffer(std::size_t index) {
return mix_buffer.data() + (index * worker_params.sample_count);
}
const s32* CommandGenerator::GetMixBuffer(std::size_t index) const {
return mix_buffer.data() + (index * worker_params.sample_count);
}
std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const {
return worker_params.mix_buffer_count + channel;
}
std::size_t CommandGenerator::GetTotalMixBufferCount() const {
return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT;
}
s32* CommandGenerator::GetChannelMixBuffer(s32 channel) {
return GetMixBuffer(worker_params.mix_buffer_count + channel);
}
const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const {
return GetMixBuffer(worker_params.mix_buffer_count + channel);
}
void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output,
VoiceState& dsp_state, s32 channel,
s32 target_sample_rate, s32 sample_count,
s32 node_id) {
auto& in_params = voice_info.GetInParams();
if (dumping_frame) {
LOG_DEBUG(Audio,
"(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, "
"format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}",
node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate,
in_params.mix_id, in_params.splitter_info_id);
}
ASSERT_OR_EXECUTE(output != nullptr, { return; });
const auto resample_rate = static_cast<s32>(
static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
static_cast<float>(static_cast<s32>(in_params.pitch * 32768.0f)));
auto* output_base = output;
if ((dsp_state.fraction + sample_count * resample_rate) > (SCALED_MIX_BUFFER_SIZE - 4ULL)) {
return;
}
auto min_required_samples =
std::min(static_cast<s32>(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate);
if (min_required_samples >= sample_count) {
min_required_samples = sample_count;
}
std::size_t temp_mix_offset{};
bool is_buffer_completed{false};
auto samples_remaining = sample_count;
while (samples_remaining > 0 && !is_buffer_completed) {
const auto samples_to_output = std::min(samples_remaining, min_required_samples);
const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15;
if (!in_params.behavior_flags.is_pitch_and_src_skipped) {
// Append sample histtory for resampler
for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i];
}
temp_mix_offset += 4;
}
s32 samples_read{};
while (samples_read < samples_to_read) {
const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index];
// No more data can be read
if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) {
is_buffer_completed = true;
break;
}
if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 &&
wave_buffer.context_address != 0 && wave_buffer.context_size != 0) {
// TODO(ogniK): ADPCM loop context
}
s32 samples_decoded{0};
switch (in_params.sample_format) {
case SampleFormat::Pcm16:
samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read,
channel, temp_mix_offset);
break;
case SampleFormat::Adpcm:
samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read,
channel, temp_mix_offset);
break;
default:
UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format);
}
temp_mix_offset += samples_decoded;
samples_read += samples_decoded;
dsp_state.offset += samples_decoded;
dsp_state.played_sample_count += samples_decoded;
if (dsp_state.offset >=
(wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) ||
samples_decoded == 0) {
// Reset our sample offset
dsp_state.offset = 0;
if (wave_buffer.is_looping) {
if (samples_decoded == 0) {
// End of our buffer
is_buffer_completed = true;
break;
}
if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) {
dsp_state.played_sample_count = 0;
}
} else {
// Update our wave buffer states
dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false;
dsp_state.wave_buffer_consumed++;
dsp_state.wave_buffer_index =
(dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
if (wave_buffer.end_of_stream) {
dsp_state.played_sample_count = 0;
}
}
}
}
if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) {
// No need to resample
std::memcpy(output, sample_buffer.data(), samples_read * sizeof(s32));
} else {
std::fill(sample_buffer.begin() + temp_mix_offset,
sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read),
0);
AudioCore::Resample(output, sample_buffer.data(), resample_rate, dsp_state.fraction,
samples_to_output);
// Resample
for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) {
dsp_state.sample_history[i] = sample_buffer[samples_to_read + i];
}
}
output += samples_to_output;
samples_remaining -= samples_to_output;
}
}
} // namespace AudioCore
+103
View File
@@ -0,0 +1,103 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "audio_core/common.h"
#include "audio_core/voice_context.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace Core::Memory {
class Memory;
}
namespace AudioCore {
class MixContext;
class SplitterContext;
class ServerSplitterDestinationData;
class ServerMixInfo;
class EffectContext;
class EffectBase;
struct AuxInfoDSP;
using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
class CommandGenerator {
public:
explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
VoiceContext& voice_context, MixContext& mix_context,
SplitterContext& splitter_context, EffectContext& effect_context,
Core::Memory::Memory& memory);
~CommandGenerator();
void ClearMixBuffers();
void GenerateVoiceCommands();
void GenerateVoiceCommand(ServerVoiceInfo& voice_info);
void GenerateSubMixCommands();
void GenerateFinalMixCommands();
void PreCommand();
void PostCommand();
s32* GetChannelMixBuffer(s32 channel);
const s32* GetChannelMixBuffer(s32 channel) const;
s32* GetMixBuffer(std::size_t index);
const s32* GetMixBuffer(std::size_t index) const;
std::size_t GetMixChannelBufferOffset(s32 channel) const;
std::size_t GetTotalMixBufferCount() const;
private:
void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel);
void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
s32 mix_buffer_count, s32 channel);
void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel,
s32 node_id);
void GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes,
const MixVolumeBuffer& last_mix_volumes, VoiceState& dsp_state,
s32 mix_buffer_offset, s32 mix_buffer_count, s32 voice_index,
s32 node_id);
void GenerateSubMixCommand(ServerMixInfo& mix_info);
void GenerateMixCommands(ServerMixInfo& mix_info);
void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume,
s32 node_id);
void GenerateFinalMixCommand();
void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params,
std::array<s64, 2>& state, std::size_t input_offset,
std::size_t output_offset, s32 sample_count, s32 node_id);
void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count,
std::size_t mix_buffer_offset);
void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count,
std::size_t mix_buffer_offset, s32 sample_rate);
void GenerateEffectCommand(ServerMixInfo& mix_info);
void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled);
ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data,
u32 sample_count, u32 write_offset, u32 write_count);
s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data,
u32 sample_count, u32 read_offset, u32 read_count);
// DSP Code
s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
s32 channel, std::size_t mix_offset);
s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
s32 channel, std::size_t mix_offset);
void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state,
s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id);
AudioCommon::AudioRendererParameter& worker_params;
VoiceContext& voice_context;
MixContext& mix_context;
SplitterContext& splitter_context;
EffectContext& effect_context;
Core::Memory::Memory& memory;
std::vector<s32> mix_buffer{};
std::vector<s32> sample_buffer{};
std::vector<s32> depop_buffer{};
bool dumping_frame{false};
};
} // namespace AudioCore
+62 -3
View File
@@ -8,13 +8,30 @@
#include "common/swap.h"
#include "core/hle/result.h"
namespace AudioCore {
namespace AudioCommon {
namespace Audren {
constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
}
constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
} // namespace Audren
constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8');
constexpr std::size_t MAX_MIX_BUFFERS = 24;
constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
constexpr std::size_t MAX_CHANNEL_COUNT = 6;
constexpr std::size_t MAX_WAVE_BUFFERS = 4;
constexpr std::size_t MAX_SAMPLE_HISTORY = 4;
constexpr u32 STREAM_SAMPLE_RATE = 48000;
constexpr u32 STREAM_NUM_CHANNELS = 6;
constexpr s32 NO_SPLITTER = -1;
constexpr s32 NO_MIX = 0x7fffffff;
constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
constexpr s32 FINAL_MIX = 0;
constexpr s32 NO_EFFECT_ORDER = -1;
constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant
// Any size checks seem to take the sample history into account
// and our const ends up being 0x3f04, the 4 bytes are most
// likely the sample history
constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
static constexpr u32 VersionFromRevision(u32_le rev) {
// "REV7" -> 7
@@ -45,4 +62,46 @@ static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std
return true;
}
} // namespace AudioCore
struct UpdateDataSizes {
u32_le behavior{};
u32_le memory_pool{};
u32_le voice{};
u32_le voice_channel_resource{};
u32_le effect{};
u32_le mixer{};
u32_le sink{};
u32_le performance{};
u32_le splitter{};
u32_le render_info{};
INSERT_PADDING_WORDS(4);
};
static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size");
struct UpdateDataHeader {
u32_le revision{};
UpdateDataSizes size{};
u32_le total_size{};
};
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size");
struct AudioRendererParameter {
u32_le sample_rate;
u32_le sample_count;
u32_le mix_buffer_count;
u32_le submix_count;
u32_le voice_count;
u32_le sink_count;
u32_le effect_count;
u32_le performance_frame_count;
u8 is_voice_drop_enabled;
u8 unknown_21;
u8 unknown_22;
u8 execution_mode;
u32_le splitter_count;
u32_le num_splitter_send_channels;
u32_le unknown_30;
u32_le revision;
};
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
} // namespace AudioCommon
+15 -3
View File
@@ -23,14 +23,24 @@ class CubebSinkStream final : public SinkStream {
public:
CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
const std::string& name)
: ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate,
: ctx{ctx}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate,
num_channels} {
cubeb_stream_params params{};
params.rate = sample_rate;
params.channels = num_channels;
params.format = CUBEB_SAMPLE_S16NE;
params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO;
switch (num_channels) {
case 1:
params.layout = CUBEB_LAYOUT_MONO;
break;
case 2:
params.layout = CUBEB_LAYOUT_STEREO;
break;
case 6:
params.layout = CUBEB_LAYOUT_3F2_LFE;
break;
}
u32 minimum_latency{};
if (cubeb_get_min_latency(ctx, &params, &minimum_latency) != CUBEB_OK) {
@@ -193,6 +203,7 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
const std::size_t samples_to_write = num_channels * num_frames;
std::size_t samples_written;
/*
if (Settings::values.enable_audio_stretching.GetValue()) {
const std::vector<s16> in{impl->queue.Pop()};
const std::size_t num_in{in.size() / num_channels};
@@ -207,7 +218,8 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
}
} else {
samples_written = impl->queue.Pop(buffer, samples_to_write);
}
}*/
samples_written = impl->queue.Pop(buffer, samples_to_write);
if (samples_written >= num_channels) {
std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
+299
View File
@@ -0,0 +1,299 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include "audio_core/effect_context.h"
namespace AudioCore {
namespace {
bool ValidChannelCountForEffect(s32 channel_count) {
return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6;
}
} // namespace
EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) {
effects.reserve(effect_count);
std::generate_n(std::back_inserter(effects), effect_count,
[] { return std::make_unique<EffectStubbed>(); });
}
EffectContext::~EffectContext() = default;
std::size_t EffectContext::GetCount() const {
return effect_count;
}
EffectBase* EffectContext::GetInfo(std::size_t i) {
return effects.at(i).get();
}
EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) {
switch (effect) {
case EffectType::Invalid:
effects[i] = std::make_unique<EffectStubbed>();
break;
case EffectType::BufferMixer:
effects[i] = std::make_unique<EffectBufferMixer>();
break;
case EffectType::Aux:
effects[i] = std::make_unique<EffectAuxInfo>();
break;
case EffectType::Delay:
effects[i] = std::make_unique<EffectDelay>();
break;
case EffectType::Reverb:
effects[i] = std::make_unique<EffectReverb>();
break;
case EffectType::I3dl2Reverb:
effects[i] = std::make_unique<EffectI3dl2Reverb>();
break;
case EffectType::BiquadFilter:
effects[i] = std::make_unique<EffectBiquadFilter>();
break;
default:
UNREACHABLE_MSG("Unimplemented effect {}", effect);
effects[i] = std::make_unique<EffectStubbed>();
}
return GetInfo(i);
}
const EffectBase* EffectContext::GetInfo(std::size_t i) const {
return effects.at(i).get();
}
EffectStubbed::EffectStubbed() : EffectBase::EffectBase(EffectType::Invalid) {}
EffectStubbed::~EffectStubbed() = default;
void EffectStubbed::Update(EffectInfo::InParams& in_params) {}
void EffectStubbed::UpdateForCommandGeneration() {}
EffectBase::EffectBase(EffectType effect_type) : effect_type(effect_type) {}
EffectBase::~EffectBase() = default;
UsageState EffectBase::GetUsage() const {
return usage;
}
EffectType EffectBase::GetType() const {
return effect_type;
}
bool EffectBase::IsEnabled() const {
return enabled;
}
s32 EffectBase::GetMixID() const {
return mix_id;
}
s32 EffectBase::GetProcessingOrder() const {
return processing_order;
}
EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric::EffectGeneric(EffectType::I3dl2Reverb) {}
EffectI3dl2Reverb::~EffectI3dl2Reverb() = default;
void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) {
auto& internal_params = GetParams();
const auto* reverb_params = reinterpret_cast<I3dl2ReverbParams*>(in_params.raw.data());
if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels);
return;
}
const auto last_status = internal_params.status;
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
internal_params = *reverb_params;
if (!ValidChannelCountForEffect(reverb_params->channel_count)) {
internal_params.channel_count = internal_params.max_channels;
}
enabled = in_params.is_enabled;
if (last_status != ParameterStatus::Updated) {
internal_params.status = last_status;
}
if (in_params.is_new || skipped) {
usage = UsageState::Initialized;
internal_params.status = ParameterStatus::Initialized;
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
}
}
void EffectI3dl2Reverb::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric::EffectGeneric(EffectType::BiquadFilter) {}
EffectBiquadFilter::~EffectBiquadFilter() = default;
void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) {
auto& internal_params = GetParams();
const auto* biquad_params = reinterpret_cast<BiquadFilterParams*>(in_params.raw.data());
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
internal_params = *biquad_params;
enabled = in_params.is_enabled;
}
void EffectBiquadFilter::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
EffectAuxInfo::EffectAuxInfo() : EffectGeneric::EffectGeneric(EffectType::Aux) {}
EffectAuxInfo::~EffectAuxInfo() = default;
void EffectAuxInfo::Update(EffectInfo::InParams& in_params) {
const auto* aux_params = reinterpret_cast<AuxInfo*>(in_params.raw.data());
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
GetParams() = *aux_params;
enabled = in_params.is_enabled;
if (in_params.is_new || skipped) {
skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0;
if (skipped) {
return;
}
// There's two AuxInfos which are an identical size, the first one is managed by the cpu,
// the second is managed by the dsp. All we care about is managing the DSP one
send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP);
send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2);
recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP);
recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2);
}
}
void EffectAuxInfo::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
}
const VAddr EffectAuxInfo::GetSendInfo() const {
return send_info;
}
const VAddr EffectAuxInfo::GetSendBuffer() const {
return send_buffer;
}
const VAddr EffectAuxInfo::GetRecvInfo() const {
return recv_info;
}
const VAddr EffectAuxInfo::GetRecvBuffer() const {
return recv_buffer;
}
EffectDelay::EffectDelay() : EffectGeneric::EffectGeneric(EffectType::Delay) {}
EffectDelay::~EffectDelay() = default;
void EffectDelay::Update(EffectInfo::InParams& in_params) {
const auto* delay_params = reinterpret_cast<DelayParams*>(in_params.raw.data());
auto& internal_params = GetParams();
if (!ValidChannelCountForEffect(delay_params->max_channels)) {
return;
}
const auto last_status = internal_params.status;
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
internal_params = *delay_params;
if (!ValidChannelCountForEffect(delay_params->channels)) {
internal_params.channels = internal_params.max_channels;
}
enabled = in_params.is_enabled;
if (last_status != ParameterStatus::Updated) {
internal_params.status = last_status;
}
if (in_params.is_new || skipped) {
usage = UsageState::Initialized;
internal_params.status = ParameterStatus::Initialized;
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
}
}
void EffectDelay::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
EffectBufferMixer::EffectBufferMixer() : EffectGeneric::EffectGeneric(EffectType::BufferMixer) {}
EffectBufferMixer::~EffectBufferMixer() = default;
void EffectBufferMixer::Update(EffectInfo::InParams& in_params) {
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
GetParams() = *reinterpret_cast<BufferMixerParams*>(in_params.raw.data());
enabled = in_params.is_enabled;
}
void EffectBufferMixer::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
}
EffectReverb::EffectReverb() : EffectGeneric::EffectGeneric(EffectType::Reverb) {}
EffectReverb::~EffectReverb() = default;
void EffectReverb::Update(EffectInfo::InParams& in_params) {
const auto* reverb_params = reinterpret_cast<ReverbParams*>(in_params.raw.data());
auto& internal_params = GetParams();
if (!ValidChannelCountForEffect(reverb_params->max_channels)) {
return;
}
const auto last_status = internal_params.status;
mix_id = in_params.mix_id;
processing_order = in_params.processing_order;
internal_params = *reverb_params;
if (!ValidChannelCountForEffect(reverb_params->channels)) {
internal_params.channels = internal_params.max_channels;
}
enabled = in_params.is_enabled;
if (last_status != ParameterStatus::Updated) {
internal_params.status = last_status;
}
if (in_params.is_new || skipped) {
usage = UsageState::Initialized;
internal_params.status = ParameterStatus::Initialized;
skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0;
}
}
void EffectReverb::UpdateForCommandGeneration() {
if (enabled) {
usage = UsageState::Running;
} else {
usage = UsageState::Stopped;
}
GetParams().status = ParameterStatus::Updated;
}
} // namespace AudioCore
+322
View File
@@ -0,0 +1,322 @@
// Copyright 2020 yuzu 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/common.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace AudioCore {
enum class EffectType : u8 {
Invalid = 0,
BufferMixer = 1,
Aux = 2,
Delay = 3,
Reverb = 4,
I3dl2Reverb = 5,
BiquadFilter = 6,
};
enum class UsageStatus : u8 {
Invalid = 0,
New = 1,
Initialized = 2,
Used = 3,
Removed = 4,
};
enum class UsageState {
Invalid = 0,
Initialized = 1,
Running = 2,
Stopped = 3,
};
enum class ParameterStatus : u8 {
Initialized = 0,
Updating = 1,
Updated = 2,
};
struct BufferMixerParams {
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{};
s32_le count{};
};
static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
struct AuxInfoDSP {
u32_le read_offset{};
u32_le write_offset{};
u32_le remaining{};
INSERT_PADDING_WORDS(13);
};
static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size");
struct AuxInfo {
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
u32_le count{};
s32_le sample_rate{};
s32_le sample_count{};
s32_le mix_buffer_count{};
u64_le send_buffer_info{};
u64_le send_buffer_base{};
u64_le return_buffer_info{};
u64_le return_buffer_base{};
};
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
struct I3dl2ReverbParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
u16_le max_channels{};
u16_le channel_count{};
INSERT_PADDING_BYTES(1);
u32_le sample_rate{};
f32 room_hf{};
f32 hf_reference{};
f32 decay_time{};
f32 hf_decay_ratio{};
f32 room{};
f32 reflection{};
f32 reverb{};
f32 diffusion{};
f32 reflection_delay{};
f32 reverb_delay{};
f32 density{};
f32 dry_gain{};
ParameterStatus status{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size");
struct BiquadFilterParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
std::array<s16_le, 3> numerator;
std::array<s16_le, 2> denominator;
s8 channel_count{};
ParameterStatus status{};
};
static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size");
struct DelayParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
u16_le max_channels{};
u16_le channels{};
s32_le max_delay{};
s32_le delay{};
s32_le sample_rate{};
s32_le gain{};
s32_le feedback_gain{};
s32_le out_gain{};
s32_le dry_gain{};
s32_le channel_spread{};
s32_le low_pass{};
ParameterStatus status{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size");
struct ReverbParams {
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> input{};
std::array<s8, AudioCommon::MAX_CHANNEL_COUNT> output{};
u16_le max_channels{};
u16_le channels{};
s32_le sample_rate{};
s32_le mode0{};
s32_le mode0_gain{};
s32_le pre_delay{};
s32_le mode1{};
s32_le mode1_gain{};
s32_le decay{};
s32_le hf_decay_ratio{};
s32_le coloration{};
s32_le reverb_gain{};
s32_le out_gain{};
s32_le dry_gain{};
ParameterStatus status{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size");
class EffectInfo {
public:
struct InParams {
EffectType type{};
u8 is_new{};
u8 is_enabled{};
INSERT_PADDING_BYTES(1);
s32_le mix_id{};
u64_le buffer_address{};
u64_le buffer_size{};
s32_le processing_order{};
INSERT_PADDING_BYTES(4);
union {
std::array<u8, 0xa0> raw;
};
};
static_assert(sizeof(EffectInfo::InParams) == 0xc0, "InParams is an invalid size");
struct OutParams {
UsageStatus status{};
INSERT_PADDING_BYTES(15);
};
static_assert(sizeof(EffectInfo::OutParams) == 0x10, "OutParams is an invalid size");
};
struct AuxAddress {
VAddr send_dsp_info{};
VAddr send_buffer_base{};
VAddr return_dsp_info{};
VAddr return_buffer_base{};
};
class EffectBase {
public:
EffectBase(EffectType effect_type);
~EffectBase();
virtual void Update(EffectInfo::InParams& in_params) = 0;
virtual void UpdateForCommandGeneration() = 0;
UsageState GetUsage() const;
EffectType GetType() const;
bool IsEnabled() const;
s32 GetMixID() const;
s32 GetProcessingOrder() const;
protected:
UsageState usage{UsageState::Invalid};
EffectType effect_type{};
s32 mix_id{};
s32 processing_order{};
bool enabled = false;
};
template <typename T>
class EffectGeneric : public EffectBase {
public:
EffectGeneric(EffectType effect_type) : EffectBase::EffectBase(effect_type) {}
~EffectGeneric() = default;
T& GetParams() {
return internal_params;
}
const I3dl2ReverbParams& GetParams() const {
return internal_params;
}
private:
T internal_params{};
};
class EffectStubbed : public EffectBase {
public:
explicit EffectStubbed();
~EffectStubbed();
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
};
class EffectI3dl2Reverb : public EffectGeneric<I3dl2ReverbParams> {
public:
explicit EffectI3dl2Reverb();
~EffectI3dl2Reverb();
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
private:
bool skipped = false;
};
class EffectBiquadFilter : public EffectGeneric<BiquadFilterParams> {
public:
explicit EffectBiquadFilter();
~EffectBiquadFilter();
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
};
class EffectAuxInfo : public EffectGeneric<AuxInfo> {
public:
explicit EffectAuxInfo();
~EffectAuxInfo();
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
const VAddr GetSendInfo() const;
const VAddr GetSendBuffer() const;
const VAddr GetRecvInfo() const;
const VAddr GetRecvBuffer() const;
private:
VAddr send_info{};
VAddr send_buffer{};
VAddr recv_info{};
VAddr recv_buffer{};
bool skipped = false;
AuxAddress addresses{};
};
class EffectDelay : public EffectGeneric<DelayParams> {
public:
explicit EffectDelay();
~EffectDelay();
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
private:
bool skipped = false;
};
class EffectBufferMixer : public EffectGeneric<BufferMixerParams> {
public:
explicit EffectBufferMixer();
~EffectBufferMixer();
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
};
class EffectReverb : public EffectGeneric<ReverbParams> {
public:
explicit EffectReverb();
~EffectReverb();
void Update(EffectInfo::InParams& in_params) override;
void UpdateForCommandGeneration() override;
private:
bool skipped = false;
};
class EffectContext {
public:
explicit EffectContext(std::size_t effect_count);
~EffectContext();
std::size_t GetCount() const;
EffectBase* GetInfo(std::size_t i);
EffectBase* RetargetEffect(std::size_t i, EffectType effect);
const EffectBase* GetInfo(std::size_t i) const;
private:
std::size_t effect_count{};
std::vector<std::unique_ptr<EffectBase>> effects;
};
} // namespace AudioCore
+517
View File
@@ -0,0 +1,517 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/behavior_info.h"
#include "audio_core/effect_context.h"
#include "audio_core/info_updater.h"
#include "audio_core/memory_pool.h"
#include "audio_core/mix_context.h"
#include "audio_core/sink_context.h"
#include "audio_core/splitter_context.h"
#include "audio_core/voice_context.h"
#include "common/logging/log.h"
namespace AudioCore {
InfoUpdater::InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
BehaviorInfo& behavior_info)
: in_params(in_params), out_params(out_params), behavior_info(behavior_info) {
ASSERT(
AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader)));
std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader));
output_header.total_size = sizeof(AudioCommon::UpdateDataHeader);
}
InfoUpdater::~InfoUpdater() = default;
bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) {
if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) {
LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}",
sizeof(BehaviorInfo::InParams), input_header.size.behavior);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
sizeof(BehaviorInfo::InParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
BehaviorInfo::InParams behavior_in{};
std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams));
input_offset += sizeof(BehaviorInfo::InParams);
// Make sure it's an audio revision we can actually support
if (!AudioCommon::IsValidRevision(behavior_in.revision)) {
LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision);
return false;
}
// Make sure that our behavior info revision matches the input
if (in_behavior_info.GetUserRevision() != behavior_in.revision) {
LOG_ERROR(Audio,
"User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
in_behavior_info.GetUserRevision(), behavior_in.revision);
return false;
}
// Update behavior info flags
in_behavior_info.ClearError();
in_behavior_info.UpdateFlags(behavior_in.flags);
return true;
}
bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) {
const auto force_mapping = behavior_info.IsMemoryPoolForceMappingEnabled();
const auto memory_pool_count = memory_pool_info.size();
const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count;
const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count;
if (input_header.size.memory_pool != total_memory_pool_in) {
LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}",
total_memory_pool_in, input_header.size.memory_pool);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count);
std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count);
std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in);
input_offset += total_memory_pool_in;
// Update our memory pools
for (std::size_t i = 0; i < memory_pool_count; i++) {
if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) {
LOG_ERROR(Audio, "Failed to update memory pool {}!", i);
return false;
}
}
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset,
sizeof(BehaviorInfo::InParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out);
output_offset += total_memory_pool_out;
output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out);
return true;
}
bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
const auto voice_count = voice_context.GetVoiceCount();
const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams);
std::vector<VoiceChannelResource::InParams> resources_in(voice_count);
if (input_header.size.voice_channel_resource != voice_size) {
LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}",
voice_size, input_header.size.voice_channel_resource);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size);
input_offset += voice_size;
// Update our channel resources
for (std::size_t i = 0; i < voice_count; i++) {
// Grab our channel resource
auto& resource = voice_context.GetChannelResource(i);
resource.Update(resources_in[i]);
}
return true;
}
bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
std::vector<ServerMemoryPoolInfo>& memory_pool_info,
VAddr audio_codec_dsp_addr) {
const auto voice_count = voice_context.GetVoiceCount();
std::vector<VoiceInfo::InParams> voice_in(voice_count);
std::vector<VoiceInfo::OutParams> voice_out(voice_count);
const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams);
const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams);
if (input_header.size.voice != voice_in_size) {
LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}",
voice_in_size, input_header.size.voice);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size);
input_offset += voice_in_size;
// Set all voices to not be in use
for (std::size_t i = 0; i < voice_count; i++) {
voice_context.GetInfo(i).GetInParams().in_use = false;
}
// Update our voices
for (std::size_t i = 0; i < voice_count; i++) {
auto& in_params = voice_in[i];
const auto channel_count = static_cast<std::size_t>(in_params.channel_count);
// Skip if it's not currently in use
if (!in_params.is_in_use) {
continue;
}
// Voice states for each channel
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{};
ASSERT(in_params.id < voice_count);
// Grab our current voice info
auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(in_params.id));
ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT);
// Get all our channel voice states
for (std::size_t channel = 0; channel < channel_count; channel++) {
voice_states[channel] =
&voice_context.GetState(in_params.voice_channel_resource_ids[channel]);
}
if (in_params.is_new) {
// Default our values for our voice
voice_info.Initialize();
if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) {
continue;
}
// Zero out our voice states
for (std::size_t channel = 0; channel < channel_count; channel++) {
std::memset(voice_states[channel], 0, sizeof(VoiceState));
}
}
// Update our voice
voice_info.UpdateParameters(in_params, behavior_info);
// TODO(ogniK): Handle mapping errors with behavior info based on in params response
// Update our wave buffers
voice_info.UpdateWaveBuffers(in_params, voice_states, behavior_info);
voice_info.WriteOutStatus(voice_out[i], in_params, voice_states);
}
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size);
output_offset += voice_out_size;
output_header.size.voice = static_cast<u32>(voice_out_size);
return true;
}
bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
const auto effect_count = effect_context.GetCount();
std::vector<EffectInfo::InParams> effect_in(effect_count);
std::vector<EffectInfo::OutParams> effect_out(effect_count);
const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams);
const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams);
if (input_header.size.effect != total_effect_in) {
LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}",
total_effect_in, input_header.size.effect);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in);
input_offset += total_effect_in;
// Update effects
for (std::size_t i = 0; i < effect_count; i++) {
auto* info = effect_context.GetInfo(i);
if (effect_in[i].type != info->GetType()) {
info = effect_context.RetargetEffect(i, effect_in[i].type);
}
info->Update(effect_in[i]);
if ((!is_active && info->GetUsage() != UsageState::Initialized) ||
info->GetUsage() == UsageState::Stopped) {
effect_out[i].status = UsageStatus::Removed;
} else {
effect_out[i].status = UsageStatus::Used;
}
}
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out);
output_offset += total_effect_out;
output_header.size.effect = static_cast<u32>(total_effect_out);
return true;
}
bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
std::size_t start_offset = input_offset;
std::size_t bytes_read{};
// Update splitter context
if (!splitter_context.Update(in_params, input_offset, bytes_read)) {
LOG_ERROR(Audio, "Failed to update splitter context!");
return false;
}
const auto consumed = input_offset - start_offset;
if (input_header.size.splitter != consumed) {
LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}",
bytes_read, input_header.size.splitter);
return false;
}
return true;
}
ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
SplitterContext& splitter_context,
EffectContext& effect_context) {
std::vector<MixInfo::InParams> mix_in_params;
if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
// If we're not dirty, get ALL mix in parameters
const auto context_mix_count = mix_context.GetCount();
const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams);
if (input_header.size.mixer != total_mix_in) {
LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
total_mix_in, input_header.size.mixer);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
mix_in_params.resize(context_mix_count);
std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in);
input_offset += total_mix_in;
} else {
// Only update the "dirty" mixes
MixInfo::DirtyHeader dirty_header{};
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
sizeof(MixInfo::DirtyHeader))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader));
input_offset += sizeof(MixInfo::DirtyHeader);
const auto total_mix_in =
dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader);
if (input_header.size.mixer != total_mix_in) {
LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
total_mix_in, input_header.size.mixer);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
if (dirty_header.mixer_count != 0) {
mix_in_params.resize(dirty_header.mixer_count);
std::memcpy(mix_in_params.data(), in_params.data() + input_offset,
mix_in_params.size() * sizeof(MixInfo::InParams));
input_offset += mix_in_params.size() * sizeof(MixInfo::InParams);
}
}
// Get our total input count
const auto mix_count = mix_in_params.size();
if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
// Only verify our buffer count if we're not dirty
std::size_t total_buffer_count{};
for (std::size_t i = 0; i < mix_count; i++) {
const auto& in = mix_in_params[i];
total_buffer_count += in.buffer_count;
if (in.dest_mix_id > mix_count && in.dest_mix_id != AudioCommon::NO_MIX &&
in.mix_id != AudioCommon::FINAL_MIX) {
LOG_ERROR(
Audio,
"Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}",
in.mix_id, in.dest_mix_id, mix_buffer_count);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
}
if (total_buffer_count > mix_buffer_count) {
LOG_ERROR(Audio,
"Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}",
mix_buffer_count, total_buffer_count);
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
}
if (mix_buffer_count == 0) {
LOG_ERROR(Audio, "No mix buffers!");
return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
}
bool should_sort = false;
for (std::size_t i = 0; i < mix_count; i++) {
const auto& mix_in = mix_in_params[i];
std::size_t target_mix{};
if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
target_mix = mix_in.mix_id;
} else {
// Non dirty supported games just use i instead of the actual mix_id
target_mix = i;
}
auto& mix_info = mix_context.GetInfo(target_mix);
auto& mix_info_params = mix_info.GetInParams();
if (mix_info_params.in_use != mix_in.in_use) {
mix_info_params.in_use = mix_in.in_use;
mix_info.ResetEffectProcessingOrder();
should_sort = true;
}
if (mix_in.in_use) {
should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
splitter_context, effect_context);
}
}
if (should_sort && behavior_info.IsSplitterSupported()) {
// Sort our splitter data
if (!mix_context.TsortInfo(splitter_context)) {
return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED;
}
}
// TODO(ogniK): Sort when splitter is suppoorted
return RESULT_SUCCESS;
}
bool InfoUpdater::UpdateSinks(SinkContext& sink_context) {
const auto sink_count = sink_context.GetCount();
std::vector<SinkInfo::InParams> sink_in_params(sink_count);
const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams);
if (input_header.size.sink != total_sink_in) {
LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}",
total_sink_in, input_header.size.effect);
return false;
}
if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in);
input_offset += total_sink_in;
// TODO(ogniK): Properly update sinks
if (!sink_in_params.empty()) {
sink_context.UpdateMainSink(sink_in_params[0]);
}
output_header.size.sink = static_cast<u32>(0x20 * sink_count);
output_offset += 0x20 * sink_count;
return true;
}
bool InfoUpdater::UpdatePerformanceBuffer() {
output_header.size.performance = 0x10;
output_offset += 0x10;
return true;
}
bool InfoUpdater::UpdateErrorInfo(BehaviorInfo& in_behavior_info) {
const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams);
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
BehaviorInfo::OutParams behavior_info_out{};
behavior_info.CopyErrorInfo(behavior_info_out);
std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out);
output_offset += total_beahvior_info_out;
output_header.size.behavior = total_beahvior_info_out;
return true;
}
struct RendererInfo {
u64_le elasped_frame_count{};
INSERT_PADDING_WORDS(2);
};
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) {
const auto total_renderer_info_out = sizeof(RendererInfo);
if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
RendererInfo out{};
out.elasped_frame_count = elapsed_frame_count;
std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out);
output_offset += total_renderer_info_out;
output_header.size.render_info = total_renderer_info_out;
return true;
}
bool InfoUpdater::CheckConsumedSize() const {
if (output_offset != out_params.size()) {
LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining",
output_offset, out_params.size(), out_params.size() - output_offset);
return false;
}
/*if (input_offset != in_params.size()) {
LOG_ERROR(Audio, "Input is not consumed!");
return false;
}*/
return true;
}
bool InfoUpdater::WriteOutputHeader() {
if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0,
sizeof(AudioCommon::UpdateDataHeader))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION;
const auto& sz = output_header.size;
output_header.total_size += sz.behavior + sz.memory_pool + sz.voice +
sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink +
sz.performance + sz.splitter + sz.render_info;
std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader));
return true;
}
} // namespace AudioCore
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "audio_core/common.h"
#include "common/common_types.h"
namespace AudioCore {
class BehaviorInfo;
class ServerMemoryPoolInfo;
class VoiceContext;
class EffectContext;
class MixContext;
class SinkContext;
class SplitterContext;
class InfoUpdater {
public:
// TODO(ogniK): Pass process handle when we support it
InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
BehaviorInfo& behavior_info);
~InfoUpdater();
bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info);
bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info);
bool UpdateVoiceChannelResources(VoiceContext& voice_context);
bool UpdateVoices(VoiceContext& voice_context,
std::vector<ServerMemoryPoolInfo>& memory_pool_info,
VAddr audio_codec_dsp_addr);
bool UpdateEffects(EffectContext& effect_context, bool is_active);
bool UpdateSplitterInfo(SplitterContext& splitter_context);
ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
SplitterContext& splitter_context, EffectContext& effect_context);
bool UpdateSinks(SinkContext& sink_context);
bool UpdatePerformanceBuffer();
bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
bool UpdateRendererInfo(std::size_t elapsed_frame_count);
bool CheckConsumedSize() const;
bool WriteOutputHeader();
private:
const std::vector<u8>& in_params;
std::vector<u8>& out_params;
BehaviorInfo& behavior_info;
AudioCommon::UpdateDataHeader input_header{};
AudioCommon::UpdateDataHeader output_header{};
std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)};
std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)};
};
} // namespace AudioCore
+62
View File
@@ -0,0 +1,62 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/memory_pool.h"
#include "common/logging/log.h"
namespace AudioCore {
ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default;
ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default;
bool ServerMemoryPoolInfo::Update(const ServerMemoryPoolInfo::InParams& in_params,
ServerMemoryPoolInfo::OutParams& out_params) {
// Our state does not need to be changed
if (in_params.state != ServerMemoryPoolInfo::State::RequestAttach &&
in_params.state != ServerMemoryPoolInfo::State::RequestDetach) {
return true;
}
// Address or size is null
if (in_params.address == 0 || in_params.size == 0) {
LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}",
in_params.address, in_params.size);
return false;
}
// Address or size is not aligned
if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) {
LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}",
in_params.address, in_params.size);
return false;
}
if (in_params.state == ServerMemoryPoolInfo::State::RequestAttach) {
cpu_address = in_params.address;
size = in_params.size;
used = true;
out_params.state = ServerMemoryPoolInfo::State::Attached;
} else {
// Unexpected address
if (cpu_address != in_params.address) {
LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}",
cpu_address, in_params.address);
return false;
}
if (size != in_params.size) {
LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size,
in_params.size);
return false;
}
cpu_address = 0;
size = 0;
used = false;
out_params.state = ServerMemoryPoolInfo::State::Detached;
}
return true;
}
} // namespace AudioCore
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace AudioCore {
class ServerMemoryPoolInfo {
public:
ServerMemoryPoolInfo();
~ServerMemoryPoolInfo();
enum class State : u32_le {
Invalid = 0x0,
Aquired = 0x1,
RequestDetach = 0x2,
Detached = 0x3,
RequestAttach = 0x4,
Attached = 0x5,
Released = 0x6,
};
struct InParams {
u64_le address{};
u64_le size{};
ServerMemoryPoolInfo::State state{};
INSERT_PADDING_WORDS(3);
};
static_assert(sizeof(ServerMemoryPoolInfo::InParams) == 0x20, "InParams are an invalid size");
struct OutParams {
ServerMemoryPoolInfo::State state{};
INSERT_PADDING_WORDS(3);
};
static_assert(sizeof(ServerMemoryPoolInfo::OutParams) == 0x10, "OutParams are an invalid size");
bool Update(const ServerMemoryPoolInfo::InParams& in_params,
ServerMemoryPoolInfo::OutParams& out_params);
private:
// There's another entry here which is the DSP address, however since we're not talking to the
// DSP we can just use the same address provided by the guest without needing to remap
u64_le cpu_address{};
u64_le size{};
bool used{};
};
} // namespace AudioCore
+296
View File
@@ -0,0 +1,296 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/behavior_info.h"
#include "audio_core/common.h"
#include "audio_core/effect_context.h"
#include "audio_core/mix_context.h"
#include "audio_core/splitter_context.h"
namespace AudioCore {
MixContext::MixContext() = default;
MixContext::~MixContext() = default;
void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
std::size_t effect_count) {
info_count = mix_count;
infos.resize(info_count);
auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
sorted_info.reserve(infos.size());
for (auto& info : infos) {
sorted_info.push_back(&info);
}
for (auto& info : infos) {
info.SetEffectCount(effect_count);
}
// Only initialize our edge matrix and node states if splitters are supported
if (behavior_info.IsSplitterSupported()) {
node_states.Initialize(mix_count);
edge_matrix.Initialize(mix_count);
}
}
void MixContext::UpdateDistancesFromFinalMix() {
// Set all distances to be invalid
for (std::size_t i = 0; i < info_count; i++) {
GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
}
for (std::size_t i = 0; i < info_count; i++) {
auto& info = GetInfo(i);
auto& in_params = info.GetInParams();
// Populate our sorted info
sorted_info[i] = &info;
if (!in_params.in_use) {
continue;
}
auto mix_id = in_params.mix_id;
// Needs to be referenced out of scope
s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
for (; distance_to_final_mix < info_count; distance_to_final_mix++) {
if (mix_id == AudioCommon::FINAL_MIX) {
// If we're at the final mix, we're done
break;
} else if (mix_id == AudioCommon::NO_MIX) {
// If we have no more mix ids, we're done
distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
break;
} else {
const auto& dest_mix = GetInfo(mix_id);
const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
// If our current mix isn't pointing to a final mix, follow through
mix_id = dest_mix.GetInParams().dest_mix_id;
} else {
// Our current mix + 1 = final distance
distance_to_final_mix = dest_mix_distance + 1;
break;
}
}
}
// If we're out of range for our distance, mark it as no final mix
if (distance_to_final_mix >= info_count) {
distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
}
in_params.final_mix_distance = distance_to_final_mix;
}
}
void MixContext::CalcMixBufferOffset() {
s32 offset{};
for (std::size_t i = 0; i < info_count; i++) {
auto& info = GetSortedInfo(i);
auto& in_params = info.GetInParams();
if (in_params.in_use) {
// Only update if in use
in_params.buffer_offset = offset;
offset += in_params.buffer_count;
}
}
}
void MixContext::SortInfo() {
// Get the distance to the final mix
UpdateDistancesFromFinalMix();
// Sort based on the distance to the final mix
std::sort(sorted_info.begin(), sorted_info.end(),
[](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
return lhs->GetInParams().final_mix_distance >
rhs->GetInParams().final_mix_distance;
});
// Calculate the mix buffer offset
CalcMixBufferOffset();
}
bool MixContext::TsortInfo(SplitterContext& splitter_context) {
// If we're not using mixes, just calculate the mix buffer offset
if (!splitter_context.UsingSplitter()) {
CalcMixBufferOffset();
return true;
}
// Sort our node states
if (!node_states.Tsort(edge_matrix)) {
return false;
}
// Get our sorted list
const auto sorted_list = node_states.GetIndexList();
std::size_t info_id{};
for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
// Set our sorted info
sorted_info[info_id++] = &GetInfo(*itr);
}
// Calculate the mix buffer offset
CalcMixBufferOffset();
return true;
}
std::size_t MixContext::GetCount() const {
return info_count;
}
ServerMixInfo& MixContext::GetInfo(std::size_t i) {
ASSERT(i < info_count);
return infos.at(i);
}
const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
ASSERT(i < info_count);
return infos.at(i);
}
ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
ASSERT(i < info_count);
return *sorted_info.at(i);
}
const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
ASSERT(i < info_count);
return *sorted_info.at(i);
}
ServerMixInfo& MixContext::GetFinalMixInfo() {
return infos.at(AudioCommon::FINAL_MIX);
}
const ServerMixInfo& MixContext::GetFinalMixInfo() const {
return infos.at(AudioCommon::FINAL_MIX);
}
EdgeMatrix& MixContext::GetEdgeMatrix() {
return edge_matrix;
}
const EdgeMatrix& MixContext::GetEdgeMatrix() const {
return edge_matrix;
}
ServerMixInfo::ServerMixInfo() {
Cleanup();
}
ServerMixInfo::~ServerMixInfo() = default;
const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
return in_params;
}
ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
return in_params;
}
bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
BehaviorInfo& behavior_info, SplitterContext& splitter_context,
EffectContext& effect_context) {
in_params.volume = mix_in.volume;
in_params.sample_rate = mix_in.sample_rate;
in_params.buffer_count = mix_in.buffer_count;
in_params.in_use = mix_in.in_use;
in_params.mix_id = mix_in.mix_id;
in_params.node_id = mix_in.node_id;
for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
in_params.mix_volume[i].begin());
}
bool require_sort = false;
if (behavior_info.IsSplitterSupported()) {
require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
} else {
in_params.dest_mix_id = mix_in.dest_mix_id;
in_params.splitter_id = AudioCommon::NO_SPLITTER;
}
ResetEffectProcessingOrder();
const auto effect_count = effect_context.GetCount();
for (std::size_t i = 0; i < effect_count; i++) {
auto* effect_info = effect_context.GetInfo(i);
if (effect_info->GetMixID() == in_params.mix_id) {
effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
}
}
// TODO(ogniK): Update effect processing order
return require_sort;
}
bool ServerMixInfo::HasAnyConnection() const {
return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
in_params.mix_id != AudioCommon::NO_MIX;
}
void ServerMixInfo::Cleanup() {
in_params.volume = 0.0f;
in_params.sample_rate = 0;
in_params.buffer_count = 0;
in_params.in_use = false;
in_params.mix_id = AudioCommon::NO_MIX;
in_params.node_id = 0;
in_params.buffer_offset = 0;
in_params.dest_mix_id = AudioCommon::NO_MIX;
in_params.splitter_id = AudioCommon::NO_SPLITTER;
std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
}
void ServerMixInfo::SetEffectCount(std::size_t count) {
effect_processing_order.resize(count);
ResetEffectProcessingOrder();
}
void ServerMixInfo::ResetEffectProcessingOrder() {
for (auto& order : effect_processing_order) {
order = AudioCommon::NO_EFFECT_ORDER;
}
}
s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
return effect_processing_order.at(i);
}
bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
SplitterContext& splitter_context) {
// Mixes are identical
if (in_params.dest_mix_id == mix_in.dest_mix_id &&
in_params.splitter_id == mix_in.splitter_id &&
((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
!splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
return false;
}
// Remove current edges for mix id
edge_matrix.RemoveEdges(in_params.mix_id);
if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
// If we have a valid destination mix id, set our edge matrix
edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
} else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
// Recurse our splitter linked and set our edges
auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
const auto length = splitter_info.GetLength();
for (s32 i = 0; i < length; i++) {
const auto* splitter_destination =
splitter_context.GetDestinationData(mix_in.splitter_id, i);
if (splitter_destination == nullptr) {
continue;
}
if (splitter_destination->ValidMixId()) {
edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
}
}
}
in_params.dest_mix_id = mix_in.dest_mix_id;
in_params.splitter_id = mix_in.splitter_id;
return true;
}
} // namespace AudioCore
+114
View File
@@ -0,0 +1,114 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <vector>
#include "audio_core/common.h"
#include "audio_core/splitter_context.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace AudioCore {
class BehaviorInfo;
class EffectContext;
class MixInfo {
public:
struct DirtyHeader {
u32_le magic{};
u32_le mixer_count{};
INSERT_PADDING_BYTES(0x18);
};
static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size");
struct InParams {
float_le volume{};
s32_le sample_rate{};
s32_le buffer_count{};
bool in_use{};
INSERT_PADDING_BYTES(3);
s32_le mix_id{};
s32_le effect_count{};
u32_le node_id{};
INSERT_PADDING_WORDS(2);
std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
mix_volume{};
s32_le dest_mix_id{};
s32_le splitter_id{};
INSERT_PADDING_WORDS(1);
};
static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size");
};
class ServerMixInfo {
public:
struct InParams {
float volume{};
s32 sample_rate{};
s32 buffer_count{};
bool in_use{};
s32 mix_id{};
u32 node_id{};
std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
mix_volume{};
s32 dest_mix_id{};
s32 splitter_id{};
s32 buffer_offset{};
s32 final_mix_distance{};
};
ServerMixInfo();
~ServerMixInfo();
const ServerMixInfo::InParams& GetInParams() const;
ServerMixInfo::InParams& GetInParams();
bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
BehaviorInfo& behavior_info, SplitterContext& splitter_context,
EffectContext& effect_context);
bool HasAnyConnection() const;
void Cleanup();
void SetEffectCount(std::size_t count);
void ResetEffectProcessingOrder();
s32 GetEffectOrder(std::size_t i) const;
private:
std::vector<s32> effect_processing_order;
InParams in_params{};
bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
SplitterContext& splitter_context);
};
class MixContext {
public:
MixContext();
~MixContext();
void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
std::size_t effect_count);
void SortInfo();
bool TsortInfo(SplitterContext& splitter_context);
std::size_t GetCount() const;
ServerMixInfo& GetInfo(std::size_t i);
const ServerMixInfo& GetInfo(std::size_t i) const;
ServerMixInfo& GetSortedInfo(std::size_t i);
const ServerMixInfo& GetSortedInfo(std::size_t i) const;
ServerMixInfo& GetFinalMixInfo();
const ServerMixInfo& GetFinalMixInfo() const;
EdgeMatrix& GetEdgeMatrix();
const EdgeMatrix& GetEdgeMatrix() const;
private:
void CalcMixBufferOffset();
void UpdateDistancesFromFinalMix();
NodeStates node_states{};
EdgeMatrix edge_matrix{};
std::size_t info_count{};
std::vector<ServerMixInfo> infos{};
std::vector<ServerMixInfo*> sorted_info{};
};
} // namespace AudioCore
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/sink_context.h"
namespace AudioCore {
SinkContext::SinkContext(std::size_t sink_count) : sink_count(sink_count) {}
SinkContext::~SinkContext() = default;
std::size_t SinkContext::GetCount() const {
return sink_count;
}
void SinkContext::UpdateMainSink(SinkInfo::InParams& in) {
in_use = in.in_use;
use_count = in.device.input_count;
std::memcpy(buffers.data(), in.device.input.data(), AudioCommon::MAX_CHANNEL_COUNT);
}
bool SinkContext::InUse() const {
return in_use;
}
std::vector<u8> SinkContext::OutputBuffers() const {
std::vector<u8> buffer_ret(use_count);
std::memcpy(buffer_ret.data(), buffers.data(), use_count);
return buffer_ret;
}
} // namespace AudioCore
+89
View File
@@ -0,0 +1,89 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "audio_core/common.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace AudioCore {
enum class SinkTypes : u8 {
Invalid = 0,
Device = 1,
Circular = 2,
};
enum class SinkSampleFormat : u32_le {
None = 0,
Pcm8 = 1,
Pcm16 = 2,
Pcm24 = 3,
Pcm32 = 4,
PcmFloat = 5,
Adpcm = 6,
};
class SinkInfo {
public:
struct CircularBufferIn {
u64_le address;
u32_le size;
u32_le input_count;
u32_le sample_count;
u32_le previous_position;
SinkSampleFormat sample_format;
std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
bool in_use;
INSERT_UNION_PADDING_BYTES(5);
};
static_assert(sizeof(SinkInfo::CircularBufferIn) == 0x28,
"SinkInfo::CircularBufferIn is in invalid size");
struct DeviceIn {
std::array<u8, 255> device_name;
INSERT_UNION_PADDING_BYTES(1);
s32_le input_count;
std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
INSERT_UNION_PADDING_BYTES(1);
bool down_matrix_enabled;
std::array<float_le, 4> down_matrix_coef;
};
static_assert(sizeof(SinkInfo::DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size");
struct InParams {
SinkTypes type{};
bool in_use{};
INSERT_PADDING_BYTES(2);
u32_le node_id{};
INSERT_PADDING_WORDS(6);
union {
// std::array<u8, 0x120> raw{};
SinkInfo::DeviceIn device;
SinkInfo::CircularBufferIn circular_buffer;
};
};
static_assert(sizeof(SinkInfo::InParams) == 0x140, "SinkInfo::InParams are an invalid size!");
};
class SinkContext {
public:
explicit SinkContext(std::size_t sink_count);
~SinkContext();
std::size_t GetCount() const;
void UpdateMainSink(SinkInfo::InParams& in);
bool InUse() const;
std::vector<u8> OutputBuffers() const;
private:
bool in_use{false};
s32 use_count{};
std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{};
std::size_t sink_count{};
};
} // namespace AudioCore
+617
View File
@@ -0,0 +1,617 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/behavior_info.h"
#include "audio_core/splitter_context.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
namespace AudioCore {
ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {}
ServerSplitterDestinationData::~ServerSplitterDestinationData() = default;
void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) {
// Log error as these are not actually failure states
if (header.magic != SplitterMagic::DataHeader) {
LOG_ERROR(Audio, "Splitter destination header is invalid!");
return;
}
// Incorrect splitter id
if (header.splitter_id != id) {
LOG_ERROR(Audio, "Splitter destination ids do not match!");
return;
}
mix_id = header.mix_id;
// Copy our mix volumes
std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin());
if (!in_use && header.in_use) {
// Update mix volumes
std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
needs_update = false;
}
in_use = header.in_use;
}
ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() {
return next;
}
const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const {
return next;
}
void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) {
next = dest;
}
bool ServerSplitterDestinationData::ValidMixId() const {
return GetMixId() != AudioCommon::NO_MIX;
}
s32 ServerSplitterDestinationData::GetMixId() const {
return mix_id;
}
bool ServerSplitterDestinationData::IsConfigured() const {
return in_use && ValidMixId();
}
float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const {
ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
return current_mix_volumes.at(i);
}
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
ServerSplitterDestinationData::CurrentMixVolumes() const {
return current_mix_volumes;
}
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
ServerSplitterDestinationData::LastMixVolumes() const {
return last_mix_volumes;
}
void ServerSplitterDestinationData::MarkDirty() {
needs_update = true;
}
void ServerSplitterDestinationData::UpdateInternalState() {
if (in_use && needs_update) {
std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
}
needs_update = false;
}
ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {}
ServerSplitterInfo::~ServerSplitterInfo() = default;
void ServerSplitterInfo::InitializeInfos() {
send_length = 0;
head = nullptr;
new_connection = true;
}
void ServerSplitterInfo::ClearNewConnectionFlag() {
new_connection = false;
}
std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) {
if (header.send_id != id) {
return 0;
}
sample_rate = header.sample_rate;
new_connection = true;
// We need to update the size here due to the splitter bug being present and providing an
// incorrect size. We're suppose to also update the header here but we just ignore and continue
return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3);
}
ServerSplitterDestinationData* ServerSplitterInfo::GetHead() {
return head;
}
const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const {
return head;
}
ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) {
auto current_head = head;
for (std::size_t i = 0; i < depth; i++) {
if (current_head == nullptr) {
return nullptr;
}
current_head = current_head->GetNextDestination();
}
return current_head;
}
const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const {
auto current_head = head;
for (std::size_t i = 0; i < depth; i++) {
if (current_head == nullptr) {
return nullptr;
}
current_head = current_head->GetNextDestination();
}
return current_head;
}
bool ServerSplitterInfo::HasNewConnection() const {
return new_connection;
}
s32 ServerSplitterInfo::GetLength() const {
return send_length;
}
void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) {
head = new_head;
}
void ServerSplitterInfo::SetHeadDepth(s32 length) {
send_length = length;
}
SplitterContext::SplitterContext() = default;
SplitterContext::~SplitterContext() = default;
void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count,
std::size_t _data_count) {
if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) {
Setup(0, 0, false);
return;
}
// Only initialize if we're using splitters
Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed());
}
bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset,
std::size_t& bytes_read) {
const auto UpdateOffsets = [&](std::size_t read) {
input_offset += read;
bytes_read += read;
};
if (info_count == 0 || data_count == 0) {
bytes_read = 0;
return true;
}
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
sizeof(SplitterInfo::InHeader))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
SplitterInfo::InHeader header{};
std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader));
UpdateOffsets(sizeof(SplitterInfo::InHeader));
if (header.magic != SplitterMagic::SplitterHeader) {
LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}",
SplitterMagic::SplitterHeader, header.magic);
return false;
}
// Clear all connections
for (auto& info : infos) {
info.ClearNewConnectionFlag();
}
UpdateInfo(input, input_offset, bytes_read, header.info_count);
UpdateData(input, input_offset, bytes_read, header.data_count);
const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16);
input_offset += aligned_bytes_read - bytes_read;
bytes_read = aligned_bytes_read;
return true;
}
bool SplitterContext::UsingSplitter() const {
return info_count > 0 && data_count > 0;
}
ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) {
ASSERT(i < info_count);
return infos.at(i);
}
const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const {
ASSERT(i < info_count);
return infos.at(i);
}
ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) {
ASSERT(i < data_count);
return datas.at(i);
}
const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const {
ASSERT(i < data_count);
return datas.at(i);
}
ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
std::size_t data) {
ASSERT(info < info_count);
auto& cur_info = GetInfo(info);
return cur_info.GetData(data);
}
const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
std::size_t data) const {
ASSERT(info < info_count);
auto& cur_info = GetInfo(info);
return cur_info.GetData(data);
}
void SplitterContext::UpdateInternalState() {
if (data_count == 0) {
return;
}
for (auto& data : datas) {
data.UpdateInternalState();
}
}
std::size_t SplitterContext::GetInfoCount() const {
return info_count;
}
std::size_t SplitterContext::GetDataCount() const {
return data_count;
}
void SplitterContext::Setup(std::size_t _info_count, std::size_t _data_count,
bool is_splitter_bug_fixed) {
info_count = _info_count;
data_count = _data_count;
for (std::size_t i = 0; i < info_count; i++) {
auto& splitter = infos.emplace_back(static_cast<s32>(i));
splitter.InitializeInfos();
}
for (std::size_t i = 0; i < data_count; i++) {
datas.emplace_back(static_cast<s32>(i));
}
bug_fixed = is_splitter_bug_fixed;
}
bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
std::size_t& bytes_read, s32 in_splitter_count) {
const auto UpdateOffsets = [&](std::size_t read) {
input_offset += read;
bytes_read += read;
};
for (s32 i = 0; i < in_splitter_count; i++) {
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
sizeof(SplitterInfo::InInfoPrams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
SplitterInfo::InInfoPrams header{};
std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams));
// Logged as warning as these don't actually cause a bailout for some reason
if (header.magic != SplitterMagic::InfoHeader) {
LOG_ERROR(Audio, "Bad splitter data header");
break;
}
if (header.send_id < 0 || header.send_id > info_count) {
LOG_ERROR(Audio, "Bad splitter data id");
break;
}
UpdateOffsets(sizeof(SplitterInfo::InInfoPrams));
auto& info = GetInfo(header.send_id);
if (!RecomposeDestination(info, header, input, input_offset)) {
LOG_ERROR(Audio, "Failed to recompose destination for splitter!");
return false;
}
const std::size_t read = info.Update(header);
bytes_read += read;
input_offset += read;
}
return true;
}
bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
std::size_t& bytes_read, s32 in_data_count) {
const auto UpdateOffsets = [&](std::size_t read) {
input_offset += read;
bytes_read += read;
};
for (s32 i = 0; i < in_data_count; i++) {
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
sizeof(SplitterInfo::InDestinationParams))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
SplitterInfo::InDestinationParams header{};
std::memcpy(&header, input.data() + input_offset,
sizeof(SplitterInfo::InDestinationParams));
UpdateOffsets(sizeof(SplitterInfo::InDestinationParams));
// Logged as warning as these don't actually cause a bailout for some reason
if (header.magic != SplitterMagic::DataHeader) {
LOG_ERROR(Audio, "Bad splitter data header");
break;
}
if (header.splitter_id < 0 || header.splitter_id > data_count) {
LOG_ERROR(Audio, "Bad splitter data id");
break;
}
GetData(header.splitter_id).Update(header);
}
return true;
}
bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info,
SplitterInfo::InInfoPrams& header,
const std::vector<u8>& input,
const std::size_t& input_offset) {
// Clear our current destinations
auto* current_head = info.GetHead();
while (current_head != nullptr) {
auto next_head = current_head->GetNextDestination();
current_head->SetNextDestination(nullptr);
current_head = next_head;
}
info.SetHead(nullptr);
s32 size = header.length;
// If the splitter bug is present, calculate fixed size
if (!bug_fixed) {
if (info_count > 0) {
const auto factor = data_count / info_count;
size = std::min(header.length, static_cast<s32>(factor));
} else {
size = 0;
}
}
if (size < 1) {
LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size);
return true;
}
auto* start_head = &GetData(header.resource_id_base);
current_head = start_head;
std::vector<s32_le> resource_ids(size - 1);
if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
resource_ids.size() * sizeof(s32_le))) {
LOG_ERROR(Audio, "Buffer is an invalid size!");
return false;
}
std::memcpy(resource_ids.data(), input.data() + input_offset,
resource_ids.size() * sizeof(s32_le));
for (auto resource_id : resource_ids) {
auto* head = &GetData(resource_id);
current_head->SetNextDestination(head);
current_head = head;
}
info.SetHead(start_head);
info.SetHeadDepth(size);
return true;
}
NodeStates::NodeStates() = default;
NodeStates::~NodeStates() = default;
void NodeStates::Initialize(std::size_t node_count_) {
// Setup our work parameters
node_count = node_count_;
was_node_found.resize(node_count);
was_node_completed.resize(node_count);
index_list.resize(node_count);
index_stack.Reset(node_count * node_count);
}
bool NodeStates::Tsort(EdgeMatrix& edge_matrix) {
return DepthFirstSearch(edge_matrix);
}
std::size_t NodeStates::GetIndexPos() const {
return index_pos;
}
const std::vector<s32>& NodeStates::GetIndexList() const {
return index_list;
}
void NodeStates::PushTsortResult(s32 index) {
ASSERT(index < node_count);
index_list[index_pos++] = index;
}
bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) {
ResetState();
for (std::size_t i = 0; i < node_count; i++) {
const auto node_id = static_cast<s32>(i);
// If we don't have a state, send to our index stack for work
if (GetState(i) == NodeStates::State::NoState) {
index_stack.push(node_id);
}
// While we have work to do in our stack
while (index_stack.Count() > 0) {
// Get the current node
const auto current_stack_index = index_stack.top();
// Check if we've seen the node yet
const auto index_state = GetState(current_stack_index);
if (index_state == NodeStates::State::NoState) {
// Mark the node as seen
UpdateState(NodeStates::State::InFound, current_stack_index);
} else if (index_state == NodeStates::State::InFound) {
// We've seen this node before, mark it as completed
UpdateState(NodeStates::State::InCompleted, current_stack_index);
// Update our index list
PushTsortResult(current_stack_index);
// Pop the stack
index_stack.pop();
continue;
} else if (index_state == NodeStates::State::InCompleted) {
// If our node is already sorted, clear it
index_stack.pop();
continue;
}
const auto node_count = edge_matrix.GetNodeCount();
for (s32 j = 0; j < static_cast<s32>(node_count); j++) {
// Check if our node is connected to our edge matrix
if (!edge_matrix.Connected(current_stack_index, j)) {
continue;
}
// Check if our node exists
const auto node_state = GetState(j);
if (node_state == NodeStates::State::NoState) {
// Add more work
index_stack.push(j);
} else if (node_state == NodeStates::State::InFound) {
UNREACHABLE_MSG("Node start marked as found");
ResetState();
return false;
}
}
}
}
return true;
}
void NodeStates::ResetState() {
// Reset to the start of our index stack
index_pos = 0;
for (std::size_t i = 0; i < node_count; i++) {
// Mark all nodes as not found
was_node_found[i] = false;
// Mark all nodes as uncompleted
was_node_completed[i] = false;
// Mark all indexes as invalid
index_list[i] = -1;
}
}
void NodeStates::UpdateState(NodeStates::State state, std::size_t i) {
switch (state) {
case NodeStates::State::NoState:
was_node_found[i] = false;
was_node_completed[i] = false;
break;
case NodeStates::State::InFound:
was_node_found[i] = true;
was_node_completed[i] = false;
break;
case NodeStates::State::InCompleted:
was_node_found[i] = false;
was_node_completed[i] = true;
break;
}
}
NodeStates::State NodeStates::GetState(std::size_t i) {
ASSERT(i < node_count);
if (was_node_found[i]) {
// If our node exists in our found list
return NodeStates::State::InFound;
} else if (was_node_completed[i]) {
// If node is in the completed list
return NodeStates::State::InCompleted;
} else {
// If in neither
return NodeStates::State::NoState;
}
}
NodeStates::Stack::Stack() = default;
NodeStates::Stack::~Stack() = default;
void NodeStates::Stack::Reset(std::size_t size) {
// Mark our stack as empty
stack.resize(size);
stack_size = size;
stack_pos = 0;
std::fill(stack.begin(), stack.end(), 0);
}
void NodeStates::Stack::push(s32 val) {
ASSERT(stack_pos < stack_size);
stack[stack_pos++] = val;
}
std::size_t NodeStates::Stack::Count() const {
return stack_pos;
}
s32 NodeStates::Stack::top() const {
ASSERT(stack_pos > 0);
return stack[stack_pos - 1];
}
s32 NodeStates::Stack::pop() {
ASSERT(stack_pos > 0);
stack_pos--;
return stack[stack_pos];
}
EdgeMatrix::EdgeMatrix() = default;
EdgeMatrix::~EdgeMatrix() = default;
void EdgeMatrix::Initialize(std::size_t _node_count) {
node_count = _node_count;
edge_matrix.resize(node_count * node_count);
}
bool EdgeMatrix::Connected(s32 a, s32 b) {
return GetState(a, b);
}
void EdgeMatrix::Connect(s32 a, s32 b) {
SetState(a, b, true);
}
void EdgeMatrix::Disconnect(s32 a, s32 b) {
SetState(a, b, false);
}
void EdgeMatrix::RemoveEdges(s32 edge) {
for (std::size_t i = 0; i < node_count; i++) {
SetState(edge, static_cast<s32>(i), false);
}
}
std::size_t EdgeMatrix::GetNodeCount() const {
return node_count;
}
void EdgeMatrix::SetState(s32 a, s32 b, bool state) {
ASSERT(InRange(a, b));
edge_matrix.at(a * node_count + b) = state;
}
bool EdgeMatrix::GetState(s32 a, s32 b) {
ASSERT(InRange(a, b));
return edge_matrix.at(a * node_count + b);
}
bool EdgeMatrix::InRange(s32 a, s32 b) const {
const std::size_t pos = a * node_count + b;
return pos < (node_count * node_count);
}
} // namespace AudioCore
+221
View File
@@ -0,0 +1,221 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <stack>
#include <vector>
#include "audio_core/common.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace AudioCore {
class BehaviorInfo;
class EdgeMatrix {
public:
EdgeMatrix();
~EdgeMatrix();
void Initialize(std::size_t _node_count);
bool Connected(s32 a, s32 b);
void Connect(s32 a, s32 b);
void Disconnect(s32 a, s32 b);
void RemoveEdges(s32 edge);
std::size_t GetNodeCount() const;
private:
void SetState(s32 a, s32 b, bool state);
bool GetState(s32 a, s32 b);
bool InRange(s32 a, s32 b) const;
std::vector<bool> edge_matrix{};
std::size_t node_count{};
};
class NodeStates {
public:
enum class State {
NoState = 0,
InFound = 1,
InCompleted = 2,
};
// Looks to be a fixed size stack. Placed within the NodeStates class based on symbols
class Stack {
public:
Stack();
~Stack();
void Reset(std::size_t size);
void push(s32 val);
std::size_t Count() const;
s32 top() const;
s32 pop();
private:
std::vector<s32> stack{};
std::size_t stack_size{};
std::size_t stack_pos{};
};
NodeStates();
~NodeStates();
void Initialize(std::size_t _node_count);
bool Tsort(EdgeMatrix& edge_matrix);
std::size_t GetIndexPos() const;
const std::vector<s32>& GetIndexList() const;
private:
void PushTsortResult(s32 index);
bool DepthFirstSearch(EdgeMatrix& edge_matrix);
void ResetState();
void UpdateState(NodeStates::State state, std::size_t i);
NodeStates::State GetState(std::size_t i);
std::size_t node_count{};
std::vector<bool> was_node_found{};
std::vector<bool> was_node_completed{};
std::size_t index_pos{};
std::vector<s32> index_list{};
NodeStates::Stack index_stack{};
};
enum class SplitterMagic : u32_le {
SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'),
DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'),
InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'),
};
class SplitterInfo {
public:
struct InHeader {
SplitterMagic magic{};
s32_le info_count{};
s32_le data_count{};
INSERT_PADDING_WORDS(5);
};
static_assert(sizeof(SplitterInfo::InHeader) == 0x20,
"SplitterInfo::InHeader is an invalid size");
struct InInfoPrams {
SplitterMagic magic{};
s32_le send_id{};
s32_le sample_rate{};
s32_le length{};
s32_le resource_id_base{};
};
static_assert(sizeof(SplitterInfo::InInfoPrams) == 0x14,
"SplitterInfo::InInfoPrams is an invalid size");
struct InDestinationParams {
SplitterMagic magic{};
s32_le splitter_id{};
std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{};
s32_le mix_id{};
bool in_use{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(SplitterInfo::InDestinationParams) == 0x70,
"SplitterInfo::InDestinationParams is an invalid size");
};
class ServerSplitterDestinationData {
public:
explicit ServerSplitterDestinationData(s32 id);
~ServerSplitterDestinationData();
void Update(SplitterInfo::InDestinationParams& header);
ServerSplitterDestinationData* GetNextDestination();
const ServerSplitterDestinationData* GetNextDestination() const;
void SetNextDestination(ServerSplitterDestinationData* dest);
bool ValidMixId() const;
s32 GetMixId() const;
bool IsConfigured() const;
float GetMixVolume(std::size_t i) const;
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const;
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const;
void MarkDirty();
void UpdateInternalState();
private:
bool needs_update{};
bool in_use{};
s32 id{};
s32 mix_id{};
std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{};
std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{};
ServerSplitterDestinationData* next = nullptr;
};
class ServerSplitterInfo {
public:
explicit ServerSplitterInfo(s32 id);
~ServerSplitterInfo();
void InitializeInfos();
void ClearNewConnectionFlag();
std::size_t Update(SplitterInfo::InInfoPrams& header);
ServerSplitterDestinationData* GetHead();
const ServerSplitterDestinationData* GetHead() const;
ServerSplitterDestinationData* GetData(std::size_t depth);
const ServerSplitterDestinationData* GetData(std::size_t depth) const;
bool HasNewConnection() const;
s32 GetLength() const;
void SetHead(ServerSplitterDestinationData* new_head);
void SetHeadDepth(s32 length);
private:
s32 sample_rate{};
s32 id{};
s32 send_length{};
ServerSplitterDestinationData* head = nullptr;
bool new_connection{};
};
class SplitterContext {
public:
SplitterContext();
~SplitterContext();
void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count,
std::size_t data_count);
bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read);
bool UsingSplitter() const;
ServerSplitterInfo& GetInfo(std::size_t i);
const ServerSplitterInfo& GetInfo(std::size_t i) const;
ServerSplitterDestinationData& GetData(std::size_t i);
const ServerSplitterDestinationData& GetData(std::size_t i) const;
ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data);
const ServerSplitterDestinationData* GetDestinationData(std::size_t info,
std::size_t data) const;
void UpdateInternalState();
std::size_t GetInfoCount() const;
std::size_t GetDataCount() const;
private:
void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed);
bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
std::size_t& bytes_read, s32 in_splitter_count);
bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
std::size_t& bytes_read, s32 in_data_count);
bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header,
const std::vector<u8>& input, const std::size_t& input_offset);
std::vector<ServerSplitterInfo> infos{};
std::vector<ServerSplitterDestinationData> datas{};
std::size_t info_count{};
std::size_t data_count{};
bool bug_fixed{};
};
} // namespace AudioCore
+1 -5
View File
@@ -104,11 +104,7 @@ void Stream::PlayNextBuffer(std::chrono::nanoseconds ns_late) {
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
const auto time_stretch_delta = Settings::values.enable_audio_stretching.GetValue()
? std::chrono::nanoseconds::zero()
: ns_late;
const auto future_time = GetBufferReleaseNS(*active_buffer) - time_stretch_delta;
core_timing.ScheduleEvent(future_time, release_event, {});
core_timing.ScheduleEvent(GetBufferReleaseNS(*active_buffer) - ns_late, release_event, {});
}
void Stream::ReleaseActiveBuffer(std::chrono::nanoseconds ns_late) {
+526
View File
@@ -0,0 +1,526 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "audio_core/behavior_info.h"
#include "audio_core/voice_context.h"
#include "core/memory.h"
namespace AudioCore {
ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id) : id(id) {}
ServerVoiceChannelResource::~ServerVoiceChannelResource() = default;
bool ServerVoiceChannelResource::InUse() const {
return in_use;
}
float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const {
ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
return mix_volume.at(i);
}
float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const {
ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
return last_mix_volume.at(i);
}
void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) {
in_use = in_params.in_use;
// Update our mix volumes only if it's in use
if (in_params.in_use) {
mix_volume = in_params.mix_volume;
}
}
void ServerVoiceChannelResource::UpdateLastMixVolumes() {
last_mix_volume = mix_volume;
}
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
ServerVoiceChannelResource::GetCurrentMixVolume() const {
return mix_volume;
}
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
ServerVoiceChannelResource::GetLastMixVolume() const {
return last_mix_volume;
}
ServerVoiceInfo::ServerVoiceInfo() {
Initialize();
}
ServerVoiceInfo::~ServerVoiceInfo() = default;
void ServerVoiceInfo::Initialize() {
in_params.in_use = false;
in_params.node_id = 0;
in_params.id = 0;
in_params.current_playstate = ServerPlayState::Stop;
in_params.priority = 255;
in_params.sample_rate = 0;
in_params.sample_format = SampleFormat::Invalid;
in_params.channel_count = 0;
in_params.pitch = 0.0f;
in_params.volume = 0.0f;
in_params.last_volume = 0.0f;
in_params.biquad_filter.fill({});
in_params.wave_buffer_count = 0;
in_params.wave_bufffer_head = 0;
in_params.mix_id = AudioCommon::NO_MIX;
in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
in_params.additional_params_address = 0;
in_params.additional_params_size = 0;
in_params.is_new = false;
out_params.played_sample_count = 0;
out_params.wave_buffer_consumed = 0;
in_params.voice_drop_flag = false;
in_params.buffer_mapped = false;
in_params.wave_buffer_flush_request_count = 0;
in_params.was_biquad_filter_enabled.fill(false);
for (auto& wave_buffer : in_params.wave_buffer) {
wave_buffer.start_sample_offset = 0;
wave_buffer.end_sample_offset = 0;
wave_buffer.is_looping = false;
wave_buffer.end_of_stream = false;
wave_buffer.buffer_address = 0;
wave_buffer.buffer_size = 0;
wave_buffer.context_address = 0;
wave_buffer.context_size = 0;
wave_buffer.sent_to_dsp = true;
}
stored_samples.clear();
}
void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
BehaviorInfo& behavior_info) {
in_params.in_use = voice_in.is_in_use;
in_params.id = voice_in.id;
in_params.node_id = voice_in.node_id;
in_params.last_playstate = in_params.current_playstate;
switch (voice_in.play_state) {
case PlayState::Paused:
in_params.current_playstate = ServerPlayState::Paused;
break;
case PlayState::Stopped:
if (in_params.current_playstate != ServerPlayState::Stop) {
in_params.current_playstate = ServerPlayState::RequestStop;
}
break;
case PlayState::Started:
in_params.current_playstate = ServerPlayState::Play;
break;
default:
UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state);
break;
}
in_params.priority = voice_in.priority;
in_params.sorting_order = voice_in.sorting_order;
in_params.sample_rate = voice_in.sample_rate;
in_params.sample_format = voice_in.sample_format;
in_params.channel_count = voice_in.channel_count;
in_params.pitch = voice_in.pitch;
in_params.volume = voice_in.volume;
in_params.biquad_filter = voice_in.biquad_filter;
in_params.wave_buffer_count = voice_in.wave_buffer_count;
in_params.wave_bufffer_head = voice_in.wave_buffer_head;
if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
in_params.wave_buffer_flush_request_count += voice_in.wave_buffer_flush_request_count;
}
in_params.mix_id = voice_in.mix_id;
if (behavior_info.IsSplitterSupported()) {
in_params.splitter_info_id = voice_in.splitter_info_id;
} else {
in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
}
std::memcpy(in_params.voice_channel_resource_id.data(),
voice_in.voice_channel_resource_ids.data(),
sizeof(s32) * in_params.voice_channel_resource_id.size());
if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
in_params.behavior_flags.is_played_samples_reset_at_loop_point =
voice_in.behavior_flags.is_played_samples_reset_at_loop_point;
} else {
in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0);
}
if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) {
in_params.behavior_flags.is_pitch_and_src_skipped =
voice_in.behavior_flags.is_pitch_and_src_skipped;
} else {
in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0);
}
if (voice_in.is_voice_drop_flag_clear_requested) {
in_params.voice_drop_flag = false;
}
if (in_params.additional_params_address != voice_in.additional_params_address ||
in_params.additional_params_size != voice_in.additional_params_size) {
in_params.additional_params_address = voice_in.additional_params_address;
in_params.additional_params_size = voice_in.additional_params_size;
// TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that
// our context is new
}
}
void ServerVoiceInfo::UpdateWaveBuffers(
const VoiceInfo::InParams& voice_in,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
BehaviorInfo& behavior_info) {
if (voice_in.is_new) {
// Initialize our wave buffers
for (auto& wave_buffer : in_params.wave_buffer) {
wave_buffer.start_sample_offset = 0;
wave_buffer.end_sample_offset = 0;
wave_buffer.is_looping = false;
wave_buffer.end_of_stream = false;
wave_buffer.buffer_address = 0;
wave_buffer.buffer_size = 0;
wave_buffer.context_address = 0;
wave_buffer.context_size = 0;
wave_buffer.sent_to_dsp = true;
}
// Mark all our wave buffers as invalid
for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
channel++) {
for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) {
is_valid = false;
}
}
}
// Update our wave buffers
for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
// Assume that we have at least 1 channel voice state
const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i];
UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format,
have_valid_wave_buffer, behavior_info);
}
}
void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
bool is_buffer_valid, BehaviorInfo& behavior_info) {
if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) {
out_wavebuffer.buffer_address = 0;
out_wavebuffer.buffer_size = 0;
}
if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
// Validate sample offset sizings
if (sample_format == SampleFormat::Pcm16) {
const auto buffer_size = in_wave_buffer.buffer_size;
if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 ||
(buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) ||
(buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) {
// TODO(ogniK): Write error info
return;
}
}
// TODO(ogniK): ADPCM Size error
out_wavebuffer.sent_to_dsp = false;
out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset;
out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset;
out_wavebuffer.is_looping = in_wave_buffer.is_looping;
out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream;
out_wavebuffer.buffer_address = in_wave_buffer.buffer_address;
out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
out_wavebuffer.context_address = in_wave_buffer.context_address;
out_wavebuffer.context_size = in_wave_buffer.context_size;
in_params.buffer_mapped =
in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
// TODO(ogniK): Pool mapper attachment
// TODO(ogniK): IsAdpcmLoopContextBugFixed
}
}
void ServerVoiceInfo::WriteOutStatus(
VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
if (voice_in.is_new) {
in_params.is_new = true;
voice_out.wave_buffer_consumed = 0;
voice_out.played_sample_count = 0;
voice_out.voice_dropped = false;
} else if (!in_params.is_new) {
voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed;
voice_out.played_sample_count = voice_states[0]->played_sample_count;
voice_out.voice_dropped = in_params.voice_drop_flag;
} else {
voice_out.wave_buffer_consumed = 0;
voice_out.played_sample_count = 0;
voice_out.voice_dropped = false;
}
}
const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const {
return in_params;
}
ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() {
return in_params;
}
const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const {
return out_params;
}
ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
return out_params;
}
bool ServerVoiceInfo::ShouldSkip() const {
// TODO(ogniK): Handle unmapped wave buffers or parameters
return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag;
}
bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{};
if (in_params.is_new) {
ResetResources(voice_context);
in_params.last_volume = in_params.volume;
in_params.is_new = false;
}
const s32 channel_count = in_params.channel_count;
for (s32 i = 0; i < channel_count; i++) {
const auto channel_resource = in_params.voice_channel_resource_id[i];
dsp_voice_states[i] =
&voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
}
return UpdateParametersForCommandGeneration(dsp_voice_states);
}
void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) {
const s32 channel_count = in_params.channel_count;
for (s32 i = 0; i < channel_count; i++) {
const auto channel_resource = in_params.voice_channel_resource_id[i];
auto& dsp_state =
voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
dsp_state = {};
voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource))
.UpdateLastMixVolumes();
}
}
bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) {
const s32 channel_count = in_params.channel_count;
if (in_params.wave_buffer_flush_request_count > 0) {
FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states,
channel_count);
in_params.wave_buffer_flush_request_count = 0;
}
switch (in_params.current_playstate) {
case ServerPlayState::Play: {
for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
if (!in_params.wave_buffer[i].sent_to_dsp) {
for (s32 channel = 0; channel < channel_count; channel++) {
dsp_voice_states[channel]->is_wave_buffer_valid[i] = true;
}
in_params.wave_buffer[i].sent_to_dsp = true;
}
}
in_params.should_depop = false;
return HasValidWaveBuffer(dsp_voice_states[0]);
}
case ServerPlayState::Paused:
case ServerPlayState::Stop: {
in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
return in_params.should_depop;
}
case ServerPlayState::RequestStop: {
for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
in_params.wave_buffer[i].sent_to_dsp = true;
for (s32 channel = 0; channel < channel_count; channel++) {
auto* dsp_state = dsp_voice_states[channel];
if (dsp_state->is_wave_buffer_valid[i]) {
dsp_state->wave_buffer_index =
(dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
dsp_state->wave_buffer_consumed++;
}
dsp_state->is_wave_buffer_valid[i] = false;
}
}
for (s32 channel = 0; channel < channel_count; channel++) {
auto* dsp_state = dsp_voice_states[channel];
dsp_state->offset = 0;
dsp_state->played_sample_count = 0;
dsp_state->fraction = 0;
dsp_state->sample_history.fill(0);
dsp_state->context = {};
}
in_params.current_playstate = ServerPlayState::Stop;
in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
return in_params.should_depop;
}
default:
UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate);
}
return false;
}
void ServerVoiceInfo::FlushWaveBuffers(
u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
s32 channel_count) {
auto wave_head = in_params.wave_bufffer_head;
for (u8 i = 0; i < flush_count; i++) {
in_params.wave_buffer[wave_head].sent_to_dsp = true;
for (s32 channel = 0; channel < channel_count; channel++) {
auto* dsp_state = dsp_voice_states[channel];
dsp_state->wave_buffer_consumed++;
dsp_state->is_wave_buffer_valid[wave_head] = false;
dsp_state->wave_buffer_index =
(dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
}
wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS;
}
}
bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
const auto& valid_wb = state->is_wave_buffer_valid;
return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
}
VoiceContext::VoiceContext(std::size_t voice_count) : voice_count(voice_count) {
for (std::size_t i = 0; i < voice_count; i++) {
voice_channel_resources.emplace_back(static_cast<s32>(i));
sorted_voice_info.push_back(&voice_info.emplace_back());
voice_states.emplace_back();
dsp_voice_states.emplace_back();
}
}
VoiceContext::~VoiceContext() {
sorted_voice_info.clear();
}
std::size_t VoiceContext::GetVoiceCount() const {
return voice_count;
}
ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) {
ASSERT(i < voice_count);
return voice_channel_resources.at(i);
}
const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const {
ASSERT(i < voice_count);
return voice_channel_resources.at(i);
}
VoiceState& VoiceContext::GetState(std::size_t i) {
ASSERT(i < voice_count);
return voice_states.at(i);
}
const VoiceState& VoiceContext::GetState(std::size_t i) const {
ASSERT(i < voice_count);
return voice_states.at(i);
}
VoiceState& VoiceContext::GetDspSharedState(std::size_t i) {
ASSERT(i < voice_count);
return dsp_voice_states.at(i);
}
const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const {
ASSERT(i < voice_count);
return dsp_voice_states.at(i);
}
ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) {
ASSERT(i < voice_count);
return voice_info.at(i);
}
const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const {
ASSERT(i < voice_count);
return voice_info.at(i);
}
ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) {
ASSERT(i < voice_count);
return *sorted_voice_info.at(i);
}
const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const {
ASSERT(i < voice_count);
return *sorted_voice_info.at(i);
}
s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
s32 channel_count, s32 buffer_offset, s32 sample_count,
Core::Memory::Memory& memory) {
if (wave_buffer->buffer_address == 0) {
return 0;
}
if (wave_buffer->buffer_size == 0) {
return 0;
}
if (wave_buffer->end_sample_offset < wave_buffer->start_sample_offset) {
return 0;
}
const auto samples_remaining =
(wave_buffer->end_sample_offset - wave_buffer->start_sample_offset) - buffer_offset;
const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count;
const auto buffer_pos = wave_buffer->buffer_address + start_offset;
s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos));
const auto samples_processed = std::min(sample_count, samples_remaining);
// Fast path
if (channel_count == 1) {
for (std::size_t i = 0; i < samples_processed; i++) {
output_buffer[i] = buffer_data[i];
}
} else {
for (std::size_t i = 0; i < samples_processed; i++) {
output_buffer[i] = buffer_data[i * channel_count + channel];
}
}
return samples_processed;
}
void VoiceContext::SortInfo() {
for (std::size_t i = 0; i < voice_count; i++) {
sorted_voice_info[i] = &voice_info[i];
}
std::sort(sorted_voice_info.begin(), sorted_voice_info.end(),
[](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) {
const auto& lhs_in = lhs->GetInParams();
const auto& rhs_in = rhs->GetInParams();
// Sort by priority
if (lhs_in.priority != rhs_in.priority) {
return lhs_in.priority > rhs_in.priority;
} else {
// If the priorities match, sort by sorting order
return lhs_in.sorting_order > rhs_in.sorting_order;
}
});
}
void VoiceContext::UpdateStateByDspShared() {
voice_states = dsp_voice_states;
}
} // namespace AudioCore
+296
View File
@@ -0,0 +1,296 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "audio_core/algorithm/interpolate.h"
#include "audio_core/codec.h"
#include "audio_core/common.h"
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace Core::Memory {
class Memory;
}
namespace AudioCore {
class BehaviorInfo;
class VoiceContext;
enum class SampleFormat : u8 {
Invalid = 0,
Pcm8 = 1,
Pcm16 = 2,
Pcm24 = 3,
Pcm32 = 4,
PcmFloat = 5,
Adpcm = 6,
};
enum class PlayState : u8 {
Started = 0,
Stopped = 1,
Paused = 2,
};
enum class ServerPlayState {
Play = 0,
Stop = 1,
RequestStop = 2,
Paused = 3,
};
struct BiquadFilterParameter {
bool enabled{};
INSERT_PADDING_BYTES(1);
std::array<s16, 3> numerator{};
std::array<s16, 2> denominator{};
};
static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size");
struct WaveBuffer {
u64_le buffer_address{};
u64_le buffer_size{};
s32_le start_sample_offset{};
s32_le end_sample_offset{};
u8 is_looping{};
u8 end_of_stream{};
u8 sent_to_server{};
INSERT_PADDING_BYTES(5);
u64 context_address{};
u64 context_size{};
INSERT_PADDING_BYTES(8);
};
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
struct ServerWaveBuffer {
VAddr buffer_address{};
std::size_t buffer_size{};
s32 start_sample_offset{};
s32 end_sample_offset{};
bool is_looping{};
bool end_of_stream{};
VAddr context_address{};
std::size_t context_size{};
bool sent_to_dsp{true};
};
struct BehaviorFlags {
BitField<0, 1, u16> is_played_samples_reset_at_loop_point;
BitField<1, 1, u16> is_pitch_and_src_skipped;
};
static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size");
struct ADPCMContext {
u16 header{};
s16 yn1{};
s16 yn2{};
};
static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size");
struct VoiceState {
s64 played_sample_count{};
s32 offset{};
s32 wave_buffer_index{};
std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid{};
s32 wave_buffer_consumed{};
std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history{};
s32 fraction{};
VAddr context_address{};
Codec::ADPCM_Coeff coeff{};
ADPCMContext context{};
std::array<s64, 2> biquad_filter_state{};
std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples{};
u32 external_context_size{};
bool is_external_context_used{};
bool voice_dropped{};
};
class VoiceChannelResource {
public:
struct InParams {
s32_le id{};
std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
bool in_use{};
INSERT_PADDING_BYTES(11);
};
static_assert(sizeof(VoiceChannelResource::InParams) == 0x70, "InParams is an invalid size");
};
class ServerVoiceChannelResource {
public:
explicit ServerVoiceChannelResource(s32 id);
~ServerVoiceChannelResource();
bool InUse() const;
float GetCurrentMixVolumeAt(std::size_t i) const;
float GetLastMixVolumeAt(std::size_t i) const;
void Update(VoiceChannelResource::InParams& in_params);
void UpdateLastMixVolumes();
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const;
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const;
private:
s32 id{};
std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{};
bool in_use{};
};
class VoiceInfo {
public:
struct InParams {
s32_le id{};
u32_le node_id{};
u8 is_new{};
u8 is_in_use{};
PlayState play_state{};
SampleFormat sample_format{};
s32_le sample_rate{};
s32_le priority{};
s32_le sorting_order{};
s32_le channel_count{};
float_le pitch{};
float_le volume{};
std::array<BiquadFilterParameter, 2> biquad_filter{};
s32_le wave_buffer_count{};
s16_le wave_buffer_head{};
INSERT_PADDING_BYTES(6);
u64_le additional_params_address{};
u64_le additional_params_size{};
s32_le mix_id{};
s32_le splitter_info_id{};
std::array<WaveBuffer, 4> wave_buffer{};
std::array<u32_le, 6> voice_channel_resource_ids{};
// TODO(ogniK): Remaining flags
u8 is_voice_drop_flag_clear_requested{};
u8 wave_buffer_flush_request_count{};
INSERT_PADDING_BYTES(2);
BehaviorFlags behavior_flags{};
INSERT_PADDING_BYTES(16);
};
static_assert(sizeof(VoiceInfo::InParams) == 0x170, "InParams is an invalid size");
struct OutParams {
u64_le played_sample_count{};
u32_le wave_buffer_consumed{};
u8 voice_dropped{};
INSERT_PADDING_BYTES(3);
};
static_assert(sizeof(VoiceInfo::OutParams) == 0x10, "OutParams is an invalid size");
};
class ServerVoiceInfo {
public:
struct InParams {
bool in_use{};
bool is_new{};
bool should_depop{};
SampleFormat sample_format{};
s32 sample_rate{};
s32 channel_count{};
s32 id{};
s32 node_id{};
s32 mix_id{};
ServerPlayState current_playstate{};
ServerPlayState last_playstate{};
s32 priority{};
s32 sorting_order{};
float pitch{};
float volume{};
float last_volume{};
std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
s32 wave_buffer_count{};
s16 wave_bufffer_head{};
INSERT_PADDING_BYTES(2);
BehaviorFlags behavior_flags{};
VAddr additional_params_address{};
std::size_t additional_params_size{};
std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{};
std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{};
s32 splitter_info_id{};
u8 wave_buffer_flush_request_count{};
bool voice_drop_flag{};
bool buffer_mapped{};
std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{};
};
struct OutParams {
s64 played_sample_count{};
s32 wave_buffer_consumed{};
};
ServerVoiceInfo();
~ServerVoiceInfo();
void Initialize();
void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info);
void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
BehaviorInfo& behavior_info);
void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer,
SampleFormat sample_format, bool is_buffer_valid,
BehaviorInfo& behavior_info);
void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states);
const InParams& GetInParams() const;
InParams& GetInParams();
const OutParams& GetOutParams() const;
OutParams& GetOutParams();
bool ShouldSkip() const;
bool UpdateForCommandGeneration(VoiceContext& voice_context);
void ResetResources(VoiceContext& voice_context);
bool UpdateParametersForCommandGeneration(
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states);
void FlushWaveBuffers(u8 flush_count,
std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
s32 channel_count);
private:
std::vector<s16> stored_samples;
InParams in_params{};
OutParams out_params{};
bool HasValidWaveBuffer(const VoiceState* state) const;
};
class VoiceContext {
public:
VoiceContext(std::size_t voice_count);
~VoiceContext();
std::size_t GetVoiceCount() const;
ServerVoiceChannelResource& GetChannelResource(std::size_t i);
const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const;
VoiceState& GetState(std::size_t i);
const VoiceState& GetState(std::size_t i) const;
VoiceState& GetDspSharedState(std::size_t i);
const VoiceState& GetDspSharedState(std::size_t i) const;
ServerVoiceInfo& GetInfo(std::size_t i);
const ServerVoiceInfo& GetInfo(std::size_t i) const;
ServerVoiceInfo& GetSortedInfo(std::size_t i);
const ServerVoiceInfo& GetSortedInfo(std::size_t i) const;
s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
s32 channel_count, s32 buffer_offset, s32 sample_count,
Core::Memory::Memory& memory);
void SortInfo();
void UpdateStateByDspShared();
private:
std::size_t voice_count{};
std::vector<ServerVoiceChannelResource> voice_channel_resources{};
std::vector<VoiceState> voice_states{};
std::vector<VoiceState> dsp_voice_states{};
std::vector<ServerVoiceInfo> voice_info{};
std::vector<ServerVoiceInfo*> sorted_voice_info{};
};
} // namespace AudioCore
+1 -1
View File
@@ -221,7 +221,7 @@ struct System::Impl {
telemetry_session->AddInitialInfo(*app_loader);
auto main_process =
Kernel::Process::Create(system, "main", Kernel::Process::ProcessType::Userland);
const auto [load_result, load_parameters] = app_loader->Load(*main_process);
const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
if (load_result != Loader::ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
Shutdown();
+3 -4
View File
@@ -23,7 +23,6 @@
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
@@ -1022,10 +1021,10 @@ void KeyManager::DeriveBase() {
}
}
void KeyManager::DeriveETicket(PartitionDataManager& data) {
void KeyManager::DeriveETicket(PartitionDataManager& data,
const FileSys::ContentProvider& provider) {
// ETicket keys
const auto es = Core::System::GetInstance().GetContentProvider().GetEntry(
0x0100000000000033, FileSys::ContentRecordType::Program);
const auto es = provider.GetEntry(0x0100000000000033, FileSys::ContentRecordType::Program);
if (es == nullptr) {
return;
+5 -1
View File
@@ -20,6 +20,10 @@ namespace Common::FS {
class IOFile;
}
namespace FileSys {
class ContentProvider;
}
namespace Loader {
enum class ResultStatus : u16;
}
@@ -252,7 +256,7 @@ public:
bool BaseDeriveNecessary() const;
void DeriveBase();
void DeriveETicket(PartitionDataManager& data);
void DeriveETicket(PartitionDataManager& data, const FileSys::ContentProvider& provider);
void PopulateTickets();
void SynthesizeTickets();
+43 -40
View File
@@ -12,6 +12,49 @@
#include "core/file_sys/nca_patch.h"
namespace FileSys {
namespace {
template <bool Subsection, typename BlockType, typename BucketType>
std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block,
const BucketType& buckets) {
if constexpr (Subsection) {
const auto& last_bucket = buckets[block.number_buckets - 1];
if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) {
return {block.number_buckets - 1, last_bucket.number_entries};
}
} else {
ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
}
std::size_t bucket_id = std::count_if(
block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets,
[&offset](u64 base_offset) { return base_offset <= offset; });
const auto& bucket = buckets[bucket_id];
if (bucket.number_entries == 1) {
return {bucket_id, 0};
}
std::size_t low = 0;
std::size_t mid = 0;
std::size_t high = bucket.number_entries - 1;
while (low <= high) {
mid = (low + high) / 2;
if (bucket.entries[mid].address_patch > offset) {
high = mid - 1;
} else {
if (mid == bucket.number_entries - 1 ||
bucket.entries[mid + 1].address_patch > offset) {
return {bucket_id, mid};
}
low = mid + 1;
}
}
UNREACHABLE_MSG("Offset could not be found in BKTR block.");
}
} // Anonymous namespace
BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
@@ -110,46 +153,6 @@ std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const {
return raw_read;
}
template <bool Subsection, typename BlockType, typename BucketType>
std::pair<std::size_t, std::size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block,
BucketType buckets) const {
if constexpr (Subsection) {
const auto last_bucket = buckets[block.number_buckets - 1];
if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch)
return {block.number_buckets - 1, last_bucket.number_entries};
} else {
ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
}
std::size_t bucket_id = std::count_if(
block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets,
[&offset](u64 base_offset) { return base_offset <= offset; });
const auto bucket = buckets[bucket_id];
if (bucket.number_entries == 1)
return {bucket_id, 0};
std::size_t low = 0;
std::size_t mid = 0;
std::size_t high = bucket.number_entries - 1;
while (low <= high) {
mid = (low + high) / 2;
if (bucket.entries[mid].address_patch > offset) {
high = mid - 1;
} else {
if (mid == bucket.number_entries - 1 ||
bucket.entries[mid + 1].address_patch > offset) {
return {bucket_id, mid};
}
low = mid + 1;
}
}
UNREACHABLE_MSG("Offset could not be found in BKTR block.");
}
RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
return relocation_buckets[res.first].entries[res.second];
-4
View File
@@ -117,10 +117,6 @@ public:
bool Rename(std::string_view name) override;
private:
template <bool Subsection, typename BlockType, typename BucketType>
std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, BlockType block,
BucketType buckets) const;
RelocationEntry GetRelocationEntry(u64 offset) const;
RelocationEntry GetNextRelocationEntry(u64 offset) const;
+75 -63
View File
@@ -27,6 +27,7 @@
#include "core/settings.h"
namespace FileSys {
namespace {
constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
@@ -36,19 +37,28 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
};
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
enum class TitleVersionFormat : u8 {
ThreeElements, ///< vX.Y.Z
FourElements, ///< vX.Y.Z.W
};
std::string FormatTitleVersion(u32 version,
TitleVersionFormat format = TitleVersionFormat::ThreeElements) {
std::array<u8, sizeof(u32)> bytes{};
bytes[0] = version % SINGLE_BYTE_MODULUS;
bytes[0] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
for (std::size_t i = 1; i < bytes.size(); ++i) {
version /= SINGLE_BYTE_MODULUS;
bytes[i] = version % SINGLE_BYTE_MODULUS;
bytes[i] = static_cast<u8>(version % SINGLE_BYTE_MODULUS);
}
if (format == TitleVersionFormat::FourElements)
if (format == TitleVersionFormat::FourElements) {
return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
}
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
}
// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
// doesn't have a directory with name.
VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
#ifdef _WIN32
return dir->GetSubdirectory(name);
@@ -65,6 +75,43 @@ VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name)
#endif
}
std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
u64 title_id, const PatchManager::BuildID& build_id_, const VirtualDir& base_path, bool upper) {
const auto build_id_raw = Common::HexToString(build_id_, upper);
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
if (file == nullptr) {
LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
title_id, build_id);
return std::nullopt;
}
std::vector<u8> data(file->GetSize());
if (file->Read(data.data(), data.size()) != data.size()) {
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
title_id, build_id);
return std::nullopt;
}
const Core::Memory::TextCheatParser parser;
return parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
}
void AppendCommaIfNotEmpty(std::string& to, std::string_view with) {
if (to.empty()) {
to += with;
} else {
to += ", ";
to += with;
}
}
bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
}
} // Anonymous namespace
PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
PatchManager::~PatchManager() = default;
@@ -245,7 +292,7 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
return out;
}
bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
bool PatchManager::HasNSOPatch(const BuildID& build_id_) const {
const auto build_id_raw = Common::HexToString(build_id_);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
@@ -265,36 +312,8 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
return !CollectPatches(patch_dirs, build_id).empty();
}
namespace {
std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_,
const VirtualDir& base_path, bool upper) {
const auto build_id_raw = Common::HexToString(build_id_, upper);
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
if (file == nullptr) {
LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
title_id, build_id);
return std::nullopt;
}
std::vector<u8> data(file->GetSize());
if (file->Read(data.data(), data.size()) != data.size()) {
LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
title_id, build_id);
return std::nullopt;
}
Core::Memory::TextCheatParser parser;
return parser.Parse(system,
std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
}
} // Anonymous namespace
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
const Core::System& system, const std::array<u8, 32>& build_id_) const {
const Core::System& system, const BuildID& build_id_) const {
const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id);
if (load_dir == nullptr) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
@@ -314,14 +333,12 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
auto cheats_dir = FindSubdirectoryCaseless(subdir, "cheats");
if (cheats_dir != nullptr) {
auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
if (res.has_value()) {
if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true)) {
std::copy(res->begin(), res->end(), std::back_inserter(out));
continue;
}
res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
if (res.has_value()) {
if (const auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false)) {
std::copy(res->begin(), res->end(), std::back_inserter(out));
}
}
@@ -435,21 +452,11 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
return romfs;
}
static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) {
if (to.empty())
to += with;
else
to += ", " + with;
}
static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
}
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
VirtualFile update_raw) const {
if (title_id == 0)
PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile update_raw) const {
if (title_id == 0) {
return {};
}
std::map<std::string, std::string, std::less<>> out;
const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto& disabled = Settings::values.disabled_addons[title_id];
@@ -472,8 +479,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (meta_ver.value_or(0) == 0) {
out.insert_or_assign(update_label, "");
} else {
out.insert_or_assign(
update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
out.insert_or_assign(update_label, FormatTitleVersion(*meta_ver));
}
} else if (update_raw != nullptr) {
out.insert_or_assign(update_label, "PACKED");
@@ -562,40 +568,46 @@ std::optional<u32> PatchManager::GetGameVersion() const {
return installed.GetEntryVersion(title_id);
}
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
PatchManager::Metadata PatchManager::GetControlMetadata() const {
const auto& installed = Core::System::GetInstance().GetContentProvider();
const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
if (base_control_nca == nullptr)
if (base_control_nca == nullptr) {
return {};
}
return ParseControlNCA(*base_control_nca);
}
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const {
PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const {
const auto base_romfs = nca.GetRomFS();
if (base_romfs == nullptr)
if (base_romfs == nullptr) {
return {};
}
const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
if (romfs == nullptr)
if (romfs == nullptr) {
return {};
}
const auto extracted = ExtractRomFS(romfs);
if (extracted == nullptr)
if (extracted == nullptr) {
return {};
}
auto nacp_file = extracted->GetFile("control.nacp");
if (nacp_file == nullptr)
if (nacp_file == nullptr) {
nacp_file = extracted->GetFile("Control.nacp");
}
auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
VirtualFile icon_file;
for (const auto& language : FileSys::LANGUAGE_NAMES) {
icon_file = extracted->GetFile("icon_" + std::string(language) + ".dat");
if (icon_file != nullptr)
icon_file = extracted->GetFile(std::string("icon_").append(language).append(".dat"));
if (icon_file != nullptr) {
break;
}
}
return {std::move(nacp), icon_file};
+20 -28
View File
@@ -22,70 +22,62 @@ namespace FileSys {
class NCA;
class NACP;
enum class TitleVersionFormat : u8 {
ThreeElements, ///< vX.Y.Z
FourElements, ///< vX.Y.Z.W
};
std::string FormatTitleVersion(u32 version,
TitleVersionFormat format = TitleVersionFormat::ThreeElements);
// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
// doesn't have a directory with name.
VirtualDir FindSubdirectoryCaseless(VirtualDir dir, std::string_view name);
// A centralized class to manage patches to games.
class PatchManager {
public:
using BuildID = std::array<u8, 0x20>;
using Metadata = std::pair<std::unique_ptr<NACP>, VirtualFile>;
using PatchVersionNames = std::map<std::string, std::string, std::less<>>;
explicit PatchManager(u64 title_id);
~PatchManager();
u64 GetTitleID() const;
[[nodiscard]] u64 GetTitleID() const;
// Currently tracked ExeFS patches:
// - Game Updates
VirtualDir PatchExeFS(VirtualDir exefs) const;
[[nodiscard]] VirtualDir PatchExeFS(VirtualDir exefs) const;
// Currently tracked NSO patches:
// - IPS
// - IPSwitch
std::vector<u8> PatchNSO(const std::vector<u8>& nso, const std::string& name) const;
[[nodiscard]] std::vector<u8> PatchNSO(const std::vector<u8>& nso,
const std::string& name) const;
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
// Used to prevent expensive copies in NSO loader.
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
[[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const;
// Creates a CheatList object with all
std::vector<Core::Memory::CheatEntry> CreateCheatList(
const Core::System& system, const std::array<u8, 0x20>& build_id) const;
[[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
const Core::System& system, const BuildID& build_id) const;
// Currently tracked RomFS patches:
// - Game Updates
// - LayeredFS
VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
ContentRecordType type = ContentRecordType::Program,
VirtualFile update_raw = nullptr) const;
[[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
ContentRecordType type = ContentRecordType::Program,
VirtualFile update_raw = nullptr) const;
// Returns a vector of pairs between patch names and patch versions.
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
VirtualFile update_raw = nullptr) const;
[[nodiscard]] PatchVersionNames GetPatchVersionNames(VirtualFile update_raw = nullptr) const;
// If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
// it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
// std::nullopt
std::optional<u32> GetGameVersion() const;
[[nodiscard]] std::optional<u32> GetGameVersion() const;
// Given title_id of the program, attempts to get the control data of the update and parse
// it, falling back to the base control data.
std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
[[nodiscard]] Metadata GetControlMetadata() const;
// Version of GetControlMetadata that takes an arbitrary NCA
std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
[[nodiscard]] Metadata ParseControlNCA(const NCA& nca) const;
private:
std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const;
[[nodiscard]] std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) const;
u64 title_id;
};
+3 -2
View File
@@ -48,14 +48,15 @@ ResultVal<std::shared_ptr<ClientSession>> ClientSession::Create(KernelCore& kern
}
ResultCode ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread,
Core::Memory::Memory& memory) {
Core::Memory::Memory& memory,
Core::Timing::CoreTiming& core_timing) {
// Keep ServerSession alive until we're done working with it.
if (!parent->Server()) {
return ERR_SESSION_CLOSED_BY_REMOTE;
}
// Signal the server session that new data is available
return parent->Server()->HandleSyncRequest(std::move(thread), memory);
return parent->Server()->HandleSyncRequest(std::move(thread), memory, core_timing);
}
} // namespace Kernel
+6 -1
View File
@@ -16,6 +16,10 @@ namespace Core::Memory {
class Memory;
}
namespace Core::Timing {
class CoreTiming;
}
namespace Kernel {
class KernelCore;
@@ -42,7 +46,8 @@ public:
return HANDLE_TYPE;
}
ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory);
ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory,
Core::Timing::CoreTiming& core_timing);
bool ShouldWait(const Thread* thread) const override;
+1 -1
View File
@@ -188,7 +188,7 @@ private:
/// Scheduler lock mechanisms.
bool is_locked{};
Common::SpinLock inner_lock{};
std::mutex inner_lock;
std::atomic<s64> scope_lock{};
Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()};
+3 -3
View File
@@ -8,7 +8,6 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
@@ -185,10 +184,11 @@ ResultCode ServerSession::CompleteSyncRequest() {
}
ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
Core::Memory::Memory& memory) {
Core::Memory::Memory& memory,
Core::Timing::CoreTiming& core_timing) {
const ResultCode result = QueueSyncRequest(std::move(thread), memory);
const auto delay = std::chrono::nanoseconds{kernel.IsMulticore() ? 0 : 20000};
Core::System::GetInstance().CoreTiming().ScheduleEvent(delay, request_event, {});
core_timing.ScheduleEvent(delay, request_event, {});
return result;
}
+7 -4
View File
@@ -18,8 +18,9 @@ class Memory;
}
namespace Core::Timing {
class CoreTiming;
struct EventType;
}
} // namespace Core::Timing
namespace Kernel {
@@ -87,12 +88,14 @@ public:
/**
* Handle a sync request from the emulated application.
*
* @param thread Thread that initiated the request.
* @param memory Memory context to handle the sync request under.
* @param thread Thread that initiated the request.
* @param memory Memory context to handle the sync request under.
* @param core_timing Core timing context to schedule the request event under.
*
* @returns ResultCode from the operation.
*/
ResultCode HandleSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory);
ResultCode HandleSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory,
Core::Timing::CoreTiming& core_timing);
bool ShouldWait(const Thread* thread) const override;
+1 -1
View File
@@ -346,7 +346,7 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
SchedulerLock lock(system.Kernel());
thread->InvalidateHLECallback();
thread->SetStatus(ThreadStatus::WaitIPC);
session->SendSyncRequest(SharedFrom(thread), system.Memory());
session->SendSyncRequest(SharedFrom(thread), system.Memory(), system.CoreTiming());
}
if (thread->HasHLECallback()) {
+77 -72
View File
@@ -26,7 +26,7 @@ namespace Service::Audio {
class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
public:
explicit IAudioRenderer(Core::System& system, AudioCore::AudioRendererParameter audren_params,
explicit IAudioRenderer(Core::System& system, AudioCommon::AudioRendererParameter audren_params,
const std::size_t instance_number)
: ServiceFramework("IAudioRenderer") {
// clang-format off
@@ -94,14 +94,15 @@ private:
void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "(STUBBED) called");
auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer());
std::vector<u8> output_params(ctx.GetWriteBufferSize());
auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
if (result.Succeeded()) {
ctx.WriteBuffer(result.Unwrap());
if (result.IsSuccess()) {
ctx.WriteBuffer(output_params);
}
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result.Code());
rb.Push(result);
}
void Start(Kernel::HLERequestContext& ctx) {
@@ -346,7 +347,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
OpenAudioRendererImpl(ctx);
}
static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) {
static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) {
// +1 represents the final mix.
return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
1;
@@ -375,7 +376,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
constexpr u64 upsampler_manager_size = 0x48;
// Calculates the part of the size that relates to mix buffers.
const auto calculate_mix_buffer_sizes = [](const AudioCore::AudioRendererParameter& params) {
const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) {
// As of 8.0.0 this is the maximum on voice channels.
constexpr u64 max_voice_channels = 6;
@@ -397,7 +398,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the portion of the size related to the mix data (and the sorting thereof).
const auto calculate_mix_info_size = [](const AudioCore::AudioRendererParameter& params) {
const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) {
// The size of the mixing info data structure.
constexpr u64 mix_info_size = 0x940;
@@ -447,7 +448,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to voice channel info.
const auto calculate_voice_info_size = [](const AudioCore::AudioRendererParameter& params) {
const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 voice_info_size = 0x220;
constexpr u64 voice_resource_size = 0xD0;
@@ -461,7 +462,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to memory pools.
const auto calculate_memory_pools_size = [](const AudioCore::AudioRendererParameter& params) {
const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count);
const u64 memory_pool_info_size = 0x20;
return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size);
@@ -469,7 +470,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
// Calculates the part of the size related to the splitter context.
const auto calculate_splitter_context_size =
[](const AudioCore::AudioRendererParameter& params) -> u64 {
[](const AudioCommon::AudioRendererParameter& params) -> u64 {
if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
return 0;
}
@@ -488,27 +489,29 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to the upsampler info.
const auto calculate_upsampler_info_size = [](const AudioCore::AudioRendererParameter& params) {
constexpr u64 upsampler_info_size = 0x280;
// Yes, using the buffer size over info alignment size is intentional here.
return Common::AlignUp(upsampler_info_size * (u64{params.submix_count} + params.sink_count),
buffer_alignment_size);
};
const auto calculate_upsampler_info_size =
[](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 upsampler_info_size = 0x280;
// Yes, using the buffer size over info alignment size is intentional here.
return Common::AlignUp(upsampler_info_size *
(u64{params.submix_count} + params.sink_count),
buffer_alignment_size);
};
// Calculates the part of the size related to effect info.
const auto calculate_effect_info_size = [](const AudioCore::AudioRendererParameter& params) {
const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 effect_info_size = 0x2B0;
return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size);
};
// Calculates the part of the size related to audio sink info.
const auto calculate_sink_info_size = [](const AudioCore::AudioRendererParameter& params) {
const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 sink_info_size = 0x170;
return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size);
};
// Calculates the part of the size related to voice state info.
const auto calculate_voice_state_size = [](const AudioCore::AudioRendererParameter& params) {
const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) {
const u64 voice_state_size = 0x100;
const u64 additional_size = buffer_alignment_size - 1;
return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
@@ -516,7 +519,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size related to performance statistics.
const auto calculate_perf_size = [](const AudioCore::AudioRendererParameter& params) {
const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) {
// Extra size value appended to the end of the calculation.
constexpr u64 appended = 128;
@@ -543,79 +546,81 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
};
// Calculates the part of the size that relates to the audio command buffer.
const auto calculate_command_buffer_size = [](const AudioCore::AudioRendererParameter& params) {
constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
const auto calculate_command_buffer_size =
[](const AudioCommon::AudioRendererParameter& params) {
constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
constexpr u64 command_buffer_size = 0x18000;
if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
constexpr u64 command_buffer_size = 0x18000;
return command_buffer_size + alignment;
}
return command_buffer_size + alignment;
}
// When the variadic command buffer is supported, this means
// the command generator for the audio renderer can issue commands
// that are (as one would expect), variable in size. So what we need to do
// is determine the maximum possible size for a few command data structures
// then multiply them by the amount of present commands indicated by the given
// respective audio parameters.
// When the variadic command buffer is supported, this means
// the command generator for the audio renderer can issue commands
// that are (as one would expect), variable in size. So what we need to do
// is determine the maximum possible size for a few command data structures
// then multiply them by the amount of present commands indicated by the given
// respective audio parameters.
constexpr u64 max_biquad_filters = 2;
constexpr u64 max_mix_buffers = 24;
constexpr u64 max_biquad_filters = 2;
constexpr u64 max_mix_buffers = 24;
constexpr u64 biquad_filter_command_size = 0x2C;
constexpr u64 biquad_filter_command_size = 0x2C;
constexpr u64 depop_mix_command_size = 0x24;
constexpr u64 depop_setup_command_size = 0x50;
constexpr u64 depop_mix_command_size = 0x24;
constexpr u64 depop_setup_command_size = 0x50;
constexpr u64 effect_command_max_size = 0x540;
constexpr u64 effect_command_max_size = 0x540;
constexpr u64 mix_command_size = 0x1C;
constexpr u64 mix_ramp_command_size = 0x24;
constexpr u64 mix_ramp_grouped_command_size = 0x13C;
constexpr u64 mix_command_size = 0x1C;
constexpr u64 mix_ramp_command_size = 0x24;
constexpr u64 mix_ramp_grouped_command_size = 0x13C;
constexpr u64 perf_command_size = 0x28;
constexpr u64 perf_command_size = 0x28;
constexpr u64 sink_command_size = 0x130;
constexpr u64 sink_command_size = 0x130;
constexpr u64 submix_command_max_size =
depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
constexpr u64 submix_command_max_size =
depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
constexpr u64 volume_command_size = 0x1C;
constexpr u64 volume_ramp_command_size = 0x20;
constexpr u64 volume_command_size = 0x1C;
constexpr u64 volume_ramp_command_size = 0x20;
constexpr u64 voice_biquad_filter_command_size =
biquad_filter_command_size * max_biquad_filters;
constexpr u64 voice_data_command_size = 0x9C;
const u64 voice_command_max_size =
(params.splitter_count * depop_setup_command_size) +
(voice_data_command_size + voice_biquad_filter_command_size + volume_ramp_command_size +
mix_ramp_grouped_command_size);
constexpr u64 voice_biquad_filter_command_size =
biquad_filter_command_size * max_biquad_filters;
constexpr u64 voice_data_command_size = 0x9C;
const u64 voice_command_max_size =
(params.splitter_count * depop_setup_command_size) +
(voice_data_command_size + voice_biquad_filter_command_size +
volume_ramp_command_size + mix_ramp_grouped_command_size);
// Now calculate the individual elements that comprise the size and add them together.
const u64 effect_commands_size = params.effect_count * effect_command_max_size;
// Now calculate the individual elements that comprise the size and add them together.
const u64 effect_commands_size = params.effect_count * effect_command_max_size;
const u64 final_mix_commands_size =
depop_mix_command_size + volume_command_size * max_mix_buffers;
const u64 final_mix_commands_size =
depop_mix_command_size + volume_command_size * max_mix_buffers;
const u64 perf_commands_size =
perf_command_size * (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
const u64 perf_commands_size =
perf_command_size *
(CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
const u64 sink_commands_size = params.sink_count * sink_command_size;
const u64 sink_commands_size = params.sink_count * sink_command_size;
const u64 splitter_commands_size =
params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
const u64 splitter_commands_size =
params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
const u64 submix_commands_size = params.submix_count * submix_command_max_size;
const u64 submix_commands_size = params.submix_count * submix_command_max_size;
const u64 voice_commands_size = params.voice_count * voice_command_max_size;
const u64 voice_commands_size = params.voice_count * voice_command_max_size;
return effect_commands_size + final_mix_commands_size + perf_commands_size +
sink_commands_size + splitter_commands_size + submix_commands_size +
voice_commands_size + alignment;
};
return effect_commands_size + final_mix_commands_size + perf_commands_size +
sink_commands_size + splitter_commands_size + submix_commands_size +
voice_commands_size + alignment;
};
IPC::RequestParser rp{ctx};
const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
u64 size = 0;
size += calculate_mix_buffer_sizes(params);
@@ -681,7 +686,7 @@ void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& c
void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
+2 -4
View File
@@ -105,10 +105,9 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager)
port_installed = true;
}
void ServiceFrameworkBase::InstallAsNamedPort() {
void ServiceFrameworkBase::InstallAsNamedPort(Kernel::KernelCore& kernel) {
ASSERT(!port_installed);
auto& kernel = Core::System::GetInstance().Kernel();
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
server_port->SetHleHandler(shared_from_this());
@@ -116,10 +115,9 @@ void ServiceFrameworkBase::InstallAsNamedPort() {
port_installed = true;
}
std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() {
std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort(Kernel::KernelCore& kernel) {
ASSERT(!port_installed);
auto& kernel = Core::System::GetInstance().Kernel();
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
auto port = MakeResult(std::move(server_port)).Unwrap();
+2 -2
View File
@@ -63,9 +63,9 @@ public:
/// Creates a port pair and registers this service with the given ServiceManager.
void InstallAsService(SM::ServiceManager& service_manager);
/// Creates a port pair and registers it on the kernel's global port registry.
void InstallAsNamedPort();
void InstallAsNamedPort(Kernel::KernelCore& kernel);
/// Creates and returns an unregistered port for the service.
std::shared_ptr<Kernel::ClientPort> CreatePort();
std::shared_ptr<Kernel::ClientPort> CreatePort(Kernel::KernelCore& kernel);
void InvokeRequest(Kernel::HLERequestContext& ctx);
+1 -1
View File
@@ -43,7 +43,7 @@ void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self,
ASSERT(self->sm_interface.expired());
auto sm = std::make_shared<SM>(self, kernel);
sm->InstallAsNamedPort();
sm->InstallAsNamedPort(kernel);
self->sm_interface = sm;
self->controller_interface = std::make_unique<Controller>();
}
@@ -29,7 +29,7 @@ namespace Service::Sockets {
* Worker abstraction to execute blocking calls on host without blocking the guest thread
*
* @tparam Service Service where the work is executed
* @tparam ...Types Types of work to execute
* @tparam Types Types of work to execute
*/
template <class Service, class... Types>
class BlockingWorker {
@@ -109,9 +109,8 @@ private:
while (keep_running) {
work_event.Wait();
const auto visit_fn = [service, &keep_running](auto&& w) {
using T = std::decay_t<decltype(w)>;
if constexpr (std::is_same_v<T, std::monostate>) {
const auto visit_fn = [service, &keep_running]<typename T>(T&& w) {
if constexpr (std::is_same_v<std::decay_t<T>, std::monostate>) {
keep_running = false;
} else {
w.Execute(service);
+4 -3
View File
@@ -491,7 +491,7 @@ std::pair<s32, Errno> BSD::PollImpl(std::vector<u8>& write_buffer, std::vector<u
for (PollFD& pollfd : fds) {
ASSERT(pollfd.revents == 0);
if (pollfd.fd > MAX_FD || pollfd.fd < 0) {
if (pollfd.fd > static_cast<s32>(MAX_FD) || pollfd.fd < 0) {
LOG_ERROR(Service, "File descriptor handle={} is invalid", pollfd.fd);
pollfd.revents = 0;
return {0, Errno::SUCCESS};
@@ -764,6 +764,7 @@ std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, const std::vector<u8>&
SockAddrIn guest_addr_in;
std::memcpy(&guest_addr_in, addr.data(), sizeof(guest_addr_in));
addr_in = Translate(guest_addr_in);
p_addr_in = &addr_in;
}
return Translate(file_descriptors[fd]->socket->SendTo(flags, message, p_addr_in));
@@ -795,7 +796,7 @@ s32 BSD::FindFreeFileDescriptorHandle() noexcept {
}
bool BSD::IsFileDescriptorValid(s32 fd) const noexcept {
if (fd > MAX_FD || fd < 0) {
if (fd > static_cast<s32>(MAX_FD) || fd < 0) {
LOG_ERROR(Service, "Invalid file descriptor handle={}", fd);
return false;
}
@@ -809,7 +810,7 @@ bool BSD::IsFileDescriptorValid(s32 fd) const noexcept {
bool BSD::IsBlockingSocket(s32 fd) const noexcept {
// Inform invalid sockets as non-blocking
// This way we avoid using a worker thread as it will fail without blocking host
if (fd > MAX_FD || fd < 0) {
if (fd > static_cast<s32>(MAX_FD) || fd < 0) {
return false;
}
if (!file_descriptors[fd]) {
@@ -131,21 +131,21 @@ u16 TranslatePollEventsToGuest(u16 flags) {
Network::SockAddrIn Translate(SockAddrIn value) {
ASSERT(value.len == 0 || value.len == sizeof(value));
Network::SockAddrIn result;
result.family = Translate(static_cast<Domain>(value.family));
result.ip = value.ip;
result.portno = value.portno >> 8 | value.portno << 8;
return result;
return {
.family = Translate(static_cast<Domain>(value.family)),
.ip = value.ip,
.portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
};
}
SockAddrIn Translate(Network::SockAddrIn value) {
SockAddrIn result;
result.len = sizeof(result);
result.family = static_cast<u8>(Translate(value.family));
result.portno = value.portno >> 8 | value.portno << 8;
result.ip = value.ip;
result.zeroes = {};
return result;
return {
.len = sizeof(SockAddrIn),
.family = static_cast<u8>(Translate(value.family)),
.portno = static_cast<u16>(value.portno >> 8 | value.portno << 8),
.ip = value.ip,
.zeroes = {},
};
}
Network::ShutdownHow Translate(ShutdownHow how) {
@@ -89,7 +89,7 @@ FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::Virtua
}
AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirectory::Load(
Kernel::Process& process) {
Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -141,9 +141,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
continue;
}
const bool should_pass_arguments{std::strcmp(module, "rtld") == 0};
const auto tentative_next_load_addr{AppLoader_NSO::LoadModule(
process, *module_file, code_size, should_pass_arguments, false)};
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
process, system, *module_file, code_size, should_pass_arguments, false);
if (!tentative_next_load_addr) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
@@ -168,9 +168,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
}
const VAddr load_addr{next_load_addr};
const bool should_pass_arguments{std::strcmp(module, "rtld") == 0};
const auto tentative_next_load_addr{AppLoader_NSO::LoadModule(
process, *module_file, load_addr, should_pass_arguments, true, pm)};
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
process, system, *module_file, load_addr, should_pass_arguments, true, pm);
if (!tentative_next_load_addr) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
@@ -192,7 +192,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
// Register the RomFS if a ".romfs" file was found
if (romfs_iter != files.end() && *romfs_iter != nullptr) {
romfs = *romfs_iter;
Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
system.GetFileSystemController().RegisterRomFS(
std::make_unique<FileSys::RomFSFactory>(*this));
}
@@ -9,6 +9,10 @@
#include "core/file_sys/program_metadata.h"
#include "core/loader/loader.h"
namespace Core {
class System;
}
namespace Loader {
/**
@@ -37,7 +41,7 @@ public:
return IdentifyType(file);
}
LoadResult Load(Kernel::Process& process) override;
LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
+2 -1
View File
@@ -383,7 +383,8 @@ FileType AppLoader_ELF::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process) {
AppLoader_ELF::LoadResult AppLoader_ELF::Load(Kernel::Process& process,
[[maybe_unused]] Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
+5 -1
View File
@@ -8,6 +8,10 @@
#include "common/common_types.h"
#include "core/loader/loader.h"
namespace Core {
class System;
}
namespace Loader {
/// Loads an ELF/AXF file
@@ -26,7 +30,7 @@ public:
return IdentifyType(file);
}
LoadResult Load(Kernel::Process& process) override;
LoadResult Load(Kernel::Process& process, Core::System& system) override;
};
} // namespace Loader
+2 -1
View File
@@ -43,7 +43,8 @@ FileType AppLoader_KIP::GetFileType() const {
: FileType::Error;
}
AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process) {
AppLoader::LoadResult AppLoader_KIP::Load(Kernel::Process& process,
[[maybe_unused]] Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
+5 -1
View File
@@ -6,6 +6,10 @@
#include "core/loader/loader.h"
namespace Core {
class System;
}
namespace FileSys {
class KIP;
}
@@ -26,7 +30,7 @@ public:
FileType GetFileType() const override;
LoadResult Load(Kernel::Process& process) override;
LoadResult Load(Kernel::Process& process, Core::System& system) override;
private:
std::unique_ptr<FileSys::KIP> kip;
+6 -1
View File
@@ -15,6 +15,10 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/vfs.h"
namespace Core {
class System;
}
namespace FileSys {
class NACP;
} // namespace FileSys
@@ -154,9 +158,10 @@ public:
/**
* Load the application and return the created Process instance
* @param process The newly created process.
* @param system The system that this process is being loaded under.
* @return The status result of the operation.
*/
virtual LoadResult Load(Kernel::Process& process) = 0;
virtual LoadResult Load(Kernel::Process& process, Core::System& system) = 0;
/**
* Get the code (typically .code section) of the application
+3 -2
View File
@@ -41,7 +41,8 @@ FileType AppLoader_NAX::GetFileType() const {
return IdentifyTypeImpl(*nax);
}
AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) {
AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process,
[[maybe_unused]] Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -65,7 +66,7 @@ AppLoader_NAX::LoadResult AppLoader_NAX::Load(Kernel::Process& process) {
return {nca_status, {}};
}
const auto result = nca_loader->Load(process);
const auto result = nca_loader->Load(process, system);
if (result.first != ResultStatus::Success) {
return result;
}
+5 -3
View File
@@ -8,10 +8,12 @@
#include "common/common_types.h"
#include "core/loader/loader.h"
namespace Core {
class System;
}
namespace FileSys {
class NAX;
} // namespace FileSys
namespace Loader {
@@ -33,7 +35,7 @@ public:
FileType GetFileType() const override;
LoadResult Load(Kernel::Process& process) override;
LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
u64 ReadRomFSIVFCOffset() const override;
+3 -3
View File
@@ -31,7 +31,7 @@ FileType AppLoader_NCA::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) {
AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -52,13 +52,13 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::Process& process) {
directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true);
const auto load_result = directory_loader->Load(process);
const auto load_result = directory_loader->Load(process, system);
if (load_result.first != ResultStatus::Success) {
return load_result;
}
if (nca->GetRomFS() != nullptr && nca->GetRomFS()->GetSize() > 0) {
Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
system.GetFileSystemController().RegisterRomFS(
std::make_unique<FileSys::RomFSFactory>(*this));
}
+5 -1
View File
@@ -8,6 +8,10 @@
#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
namespace Core {
class System;
}
namespace FileSys {
class NCA;
}
@@ -33,7 +37,7 @@ public:
return IdentifyType(file);
}
LoadResult Load(Kernel::Process& process) override;
LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
u64 ReadRomFSIVFCOffset() const override;
+2 -2
View File
@@ -208,7 +208,7 @@ bool AppLoader_NRO::LoadNro(Kernel::Process& process, const FileSys::VfsFile& fi
return LoadNroImpl(process, file.ReadAllBytes(), file.GetName());
}
AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) {
AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -218,7 +218,7 @@ AppLoader_NRO::LoadResult AppLoader_NRO::Load(Kernel::Process& process) {
}
if (romfs != nullptr) {
Core::System::GetInstance().GetFileSystemController().RegisterRomFS(
system.GetFileSystemController().RegisterRomFS(
std::make_unique<FileSys::RomFSFactory>(*this));
}
+5 -1
View File
@@ -10,6 +10,10 @@
#include "common/common_types.h"
#include "core/loader/loader.h"
namespace Core {
class System;
}
namespace FileSys {
class NACP;
}
@@ -37,7 +41,7 @@ public:
return IdentifyType(file);
}
LoadResult Load(Kernel::Process& process) override;
LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
+4 -4
View File
@@ -71,7 +71,7 @@ FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::NSO;
}
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, Core::System& system,
const FileSys::VfsFile& file, VAddr load_base,
bool should_pass_arguments, bool load_into_process,
std::optional<FileSys::PatchManager> pm) {
@@ -148,7 +148,6 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
// Apply cheats if they exist and the program has a valid title ID
if (pm) {
auto& system = Core::System::GetInstance();
system.SetCurrentProcessBuildID(nso_header.build_id);
const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
if (!cheats.empty()) {
@@ -166,7 +165,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
return load_base + image_size;
}
AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process,
[[maybe_unused]] Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -175,7 +175,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::Process& process) {
// Load module
const VAddr base_address = process.PageTable().GetCodeRegionStart();
if (!LoadModule(process, *file, base_address, true, true)) {
if (!LoadModule(process, system, *file, base_address, true, true)) {
return {ResultStatus::ErrorLoadingNSO, {}};
}
+8 -4
View File
@@ -12,6 +12,10 @@
#include "core/file_sys/patch_manager.h"
#include "core/loader/loader.h"
namespace Core {
class System;
}
namespace Kernel {
class Process;
}
@@ -80,12 +84,12 @@ public:
return IdentifyType(file);
}
static std::optional<VAddr> LoadModule(Kernel::Process& process, const FileSys::VfsFile& file,
VAddr load_base, bool should_pass_arguments,
bool load_into_process,
static std::optional<VAddr> LoadModule(Kernel::Process& process, Core::System& system,
const FileSys::VfsFile& file, VAddr load_base,
bool should_pass_arguments, bool load_into_process,
std::optional<FileSys::PatchManager> pm = {});
LoadResult Load(Kernel::Process& process) override;
LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadNSOModules(Modules& modules) override;
+3 -4
View File
@@ -71,7 +71,7 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -99,15 +99,14 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::Process& process) {
return {ResultStatus::ErrorNSPMissingProgramNCA, {}};
}
const auto result = secondary_loader->Load(process);
const auto result = secondary_loader->Load(process, system);
if (result.first != ResultStatus::Success) {
return result;
}
FileSys::VirtualFile update_raw;
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
Core::System::GetInstance().GetFileSystemController().SetPackedUpdate(
std::move(update_raw));
system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
}
is_loaded = true;
+5 -1
View File
@@ -9,6 +9,10 @@
#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
namespace Core {
class System;
}
namespace FileSys {
class NACP;
class NSP;
@@ -35,7 +39,7 @@ public:
return IdentifyType(file);
}
LoadResult Load(Kernel::Process& process) override;
LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
u64 ReadRomFSIVFCOffset() const override;
+3 -4
View File
@@ -49,7 +49,7 @@ FileType AppLoader_XCI::IdentifyType(const FileSys::VirtualFile& file) {
return FileType::Error;
}
AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) {
AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process, Core::System& system) {
if (is_loaded) {
return {ResultStatus::ErrorAlreadyLoaded, {}};
}
@@ -66,15 +66,14 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::Process& process) {
return {ResultStatus::ErrorMissingProductionKeyFile, {}};
}
const auto result = nca_loader->Load(process);
const auto result = nca_loader->Load(process, system);
if (result.first != ResultStatus::Success) {
return result;
}
FileSys::VirtualFile update_raw;
if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr) {
Core::System::GetInstance().GetFileSystemController().SetPackedUpdate(
std::move(update_raw));
system.GetFileSystemController().SetPackedUpdate(std::move(update_raw));
}
is_loaded = true;
+5 -1
View File
@@ -9,6 +9,10 @@
#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
namespace Core {
class System;
}
namespace FileSys {
class NACP;
class XCI;
@@ -35,7 +39,7 @@ public:
return IdentifyType(file);
}
LoadResult Load(Kernel::Process& process) override;
LoadResult Load(Kernel::Process& process, Core::System& system) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
u64 ReadRomFSIVFCOffset() const override;
+19 -22
View File
@@ -19,10 +19,24 @@
#include "core/memory/cheat_engine.h"
namespace Core::Memory {
namespace {
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
std::string_view ExtractName(std::string_view data, std::size_t start_index, char match) {
auto end_index = start_index;
while (data[end_index] != match) {
++end_index;
if (end_index > data.size() ||
(end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
return {};
}
}
return data.substr(start_index, end_index - start_index);
}
} // Anonymous namespace
StandardVmCallbacks::StandardVmCallbacks(Core::System& system, const CheatProcessMetadata& metadata)
: metadata(metadata), system(system) {}
@@ -82,26 +96,9 @@ CheatParser::~CheatParser() = default;
TextCheatParser::~TextCheatParser() = default;
namespace {
template <char match>
std::string_view ExtractName(std::string_view data, std::size_t start_index) {
auto end_index = start_index;
while (data[end_index] != match) {
++end_index;
if (end_index > data.size() ||
(end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
return {};
}
}
return data.substr(start_index, end_index - start_index);
}
} // Anonymous namespace
std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
std::string_view data) const {
std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
std::vector<CheatEntry> out(1);
std::optional<u64> current_entry = std::nullopt;
std::optional<u64> current_entry;
for (std::size_t i = 0; i < data.size(); ++i) {
if (::isspace(data[i])) {
@@ -115,7 +112,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
return {};
}
const auto name = ExtractName<'}'>(data, i + 1);
const auto name = ExtractName(data, i + 1, '}');
if (name.empty()) {
return {};
}
@@ -132,7 +129,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
current_entry = out.size();
out.emplace_back();
const auto name = ExtractName<']'>(data, i + 1);
const auto name = ExtractName(data, i + 1, ']');
if (name.empty()) {
return {};
}
+2 -3
View File
@@ -47,8 +47,7 @@ class CheatParser {
public:
virtual ~CheatParser();
virtual std::vector<CheatEntry> Parse(const Core::System& system,
std::string_view data) const = 0;
[[nodiscard]] virtual std::vector<CheatEntry> Parse(std::string_view data) const = 0;
};
// CheatParser implementation that parses text files
@@ -56,7 +55,7 @@ class TextCheatParser final : public CheatParser {
public:
~TextCheatParser() override;
std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override;
[[nodiscard]] std::vector<CheatEntry> Parse(std::string_view data) const override;
};
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
+1 -1
View File
@@ -269,5 +269,5 @@ endif()
if (MSVC)
target_compile_options(video_core PRIVATE /we4267)
else()
target_compile_options(video_core PRIVATE -Werror=conversion -Wno-error=sign-conversion)
target_compile_options(video_core PRIVATE -Werror=conversion -Wno-error=sign-conversion -Werror=switch)
endif()
@@ -1443,8 +1443,10 @@ private:
return expr + ", vec2(0.0), vec2(0.0))";
case TextureType::TextureCube:
return expr + ", vec3(0.0), vec3(0.0))";
default:
UNREACHABLE();
break;
}
UNREACHABLE();
}
for (const auto& variant : extras) {
@@ -47,6 +47,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
return GL_UNSIGNED_INT;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return GL_UNSIGNED_INT_2_10_10_10_REV;
default:
break;
}
break;
case Maxwell::VertexAttribute::Type::SignedNorm:
@@ -70,6 +72,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
return GL_INT;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return GL_INT_2_10_10_10_REV;
default:
break;
}
break;
case Maxwell::VertexAttribute::Type::Float:
@@ -84,6 +88,8 @@ inline GLenum VertexFormat(Maxwell::VertexAttribute attrib) {
case Maxwell::VertexAttribute::Size::Size_32_32_32:
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
return GL_FLOAT;
default:
break;
}
break;
}
@@ -78,9 +78,10 @@ VkSamplerAddressMode WrapMode(const VKDevice& device, Tegra::Texture::WrapMode w
case Tegra::Texture::WrapMode::MirrorOnceBorder:
UNIMPLEMENTED();
return VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE;
default:
UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", static_cast<u32>(wrap_mode));
return {};
}
UNIMPLEMENTED_MSG("Unimplemented wrap mode={}", static_cast<u32>(wrap_mode));
return {};
}
VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_func) {
@@ -298,9 +299,10 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const VKDevice& device,
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
case Maxwell::PrimitiveTopology::Patches:
return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
default:
UNIMPLEMENTED_MSG("Unimplemented topology={}", static_cast<u32>(topology));
return {};
}
UNIMPLEMENTED_MSG("Unimplemented topology={}", static_cast<u32>(topology));
return {};
}
VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttribute::Size size) {
@@ -325,6 +327,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R16G16B16A16_UNORM;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
default:
break;
}
break;
case Maxwell::VertexAttribute::Type::SignedNorm:
@@ -347,6 +351,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R16G16B16A16_SNORM;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
default:
break;
}
break;
case Maxwell::VertexAttribute::Type::UnsignedScaled:
@@ -369,6 +375,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R16G16B16A16_USCALED;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_USCALED_PACK32;
default:
break;
}
break;
case Maxwell::VertexAttribute::Type::SignedScaled:
@@ -391,6 +399,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R16G16B16A16_SSCALED;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
default:
break;
}
break;
case Maxwell::VertexAttribute::Type::UnsignedInt:
@@ -421,6 +431,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R32G32B32A32_UINT;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_UINT_PACK32;
default:
break;
}
break;
case Maxwell::VertexAttribute::Type::SignedInt:
@@ -451,6 +463,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R32G32B32A32_SINT;
case Maxwell::VertexAttribute::Size::Size_10_10_10_2:
return VK_FORMAT_A2B10G10R10_SINT_PACK32;
default:
break;
}
break;
case Maxwell::VertexAttribute::Type::Float:
@@ -471,6 +485,8 @@ VkFormat VertexFormat(Maxwell::VertexAttribute::Type type, Maxwell::VertexAttrib
return VK_FORMAT_R32G32B32_SFLOAT;
case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
return VK_FORMAT_R32G32B32A32_SFLOAT;
default:
break;
}
break;
}
@@ -262,6 +262,22 @@ const char* ToString(VkResult result) noexcept {
return "VK_ERROR_INVALID_DEVICE_ADDRESS_EXT";
case VkResult::VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
case VkResult::VK_ERROR_UNKNOWN:
return "VK_ERROR_UNKNOWN";
case VkResult::VK_ERROR_INCOMPATIBLE_VERSION_KHR:
return "VK_ERROR_INCOMPATIBLE_VERSION_KHR";
case VkResult::VK_THREAD_IDLE_KHR:
return "VK_THREAD_IDLE_KHR";
case VkResult::VK_THREAD_DONE_KHR:
return "VK_THREAD_DONE_KHR";
case VkResult::VK_OPERATION_DEFERRED_KHR:
return "VK_OPERATION_DEFERRED_KHR";
case VkResult::VK_OPERATION_NOT_DEFERRED_KHR:
return "VK_OPERATION_NOT_DEFERRED_KHR";
case VkResult::VK_PIPELINE_COMPILE_REQUIRED_EXT:
return "VK_PIPELINE_COMPILE_REQUIRED_EXT";
case VkResult::VK_RESULT_MAX_ENUM:
return "VK_RESULT_MAX_ENUM";
}
return "Unknown";
}
@@ -53,6 +53,9 @@ u32 ShaderIR::DecodeArithmeticHalf(NodeBlock& bb, u32 pc) {
absolute_a = ((instr.value >> 44) & 1) != 0;
absolute_b = ((instr.value >> 54) & 1) != 0;
break;
default:
UNREACHABLE();
break;
}
Node op_a = UnpackHalfFloat(GetRegister(instr.gpr8), instr.alu_half.type_a);
+10 -4
View File
@@ -119,6 +119,8 @@ ComponentType GetComponentType(Tegra::Engines::SamplerDescriptor descriptor,
return descriptor.r_type;
}
break;
default:
break;
}
UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
return ComponentType::FLOAT;
@@ -220,9 +222,10 @@ u32 GetComponentSize(TextureFormat format, std::size_t component) {
return (component == 0 || component == 1) ? 8 : 0;
case TextureFormat::G4R4:
return (component == 0 || component == 1) ? 4 : 0;
default:
UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
return 0;
}
UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
return 0;
}
std::size_t GetImageComponentMask(TextureFormat format) {
@@ -257,9 +260,10 @@ std::size_t GetImageComponentMask(TextureFormat format) {
case TextureFormat::R8:
case TextureFormat::R1:
return std::size_t{R};
default:
UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
return std::size_t{R | G | B | A};
}
UNIMPLEMENTED_MSG("Texture format not implemented={}", format);
return std::size_t{R | G | B | A};
}
std::size_t GetImageTypeNumCoordinates(Tegra::Shader::ImageType image_type) {
@@ -463,6 +467,8 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
return OperationCode::AtomicImageXor;
case Tegra::Shader::ImageAtomicOperation::Exch:
return OperationCode::AtomicImageExchange;
default:
break;
}
default:
break;
+4 -2
View File
@@ -2592,8 +2592,10 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
const auto function = [this, &keys, &pdm] {
keys.PopulateFromPartitionData(pdm);
Core::System::GetInstance().GetFileSystemController().CreateFactories(*vfs);
keys.DeriveETicket(pdm);
auto& system = Core::System::GetInstance();
system.GetFileSystemController().CreateFactories(*vfs);
keys.DeriveETicket(pdm, system.GetContentProvider());
};
QString errors;