Project Andio
This commit is contained in:
parent
6e36f4d230
commit
458da8a948
270 changed files with 33712 additions and 8445 deletions
38
src/audio_core/renderer/voice/voice_channel_resource.h
Normal file
38
src/audio_core/renderer/voice/voice_channel_resource.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Represents one channel for mixing a voice.
|
||||
*/
|
||||
class VoiceChannelResource {
|
||||
public:
|
||||
struct InParameter {
|
||||
/* 0x00 */ u32 id;
|
||||
/* 0x04 */ std::array<f32, MaxMixBuffers> mix_volumes;
|
||||
/* 0x64 */ bool in_use;
|
||||
/* 0x65 */ char unk65[0xB];
|
||||
};
|
||||
static_assert(sizeof(InParameter) == 0x70,
|
||||
"VoiceChannelResource::InParameter has the wrong size!");
|
||||
|
||||
explicit VoiceChannelResource(u32 id_) : id{id_} {}
|
||||
|
||||
/// Current volume for each mix buffer
|
||||
std::array<f32, MaxMixBuffers> mix_volumes{};
|
||||
/// Previous volume for each mix buffer
|
||||
std::array<f32, MaxMixBuffers> prev_mix_volumes{};
|
||||
/// Id of this resource
|
||||
const u32 id;
|
||||
/// Is this resource in use?
|
||||
bool in_use{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
86
src/audio_core/renderer/voice/voice_context.cpp
Normal file
86
src/audio_core/renderer/voice/voice_context.cpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "audio_core/renderer/voice/voice_context.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
VoiceState& VoiceContext::GetDspSharedState(const u32 index) {
|
||||
if (index >= dsp_states.size()) {
|
||||
LOG_ERROR(Service_Audio, "Invalid voice dsp state index {:04X}", index);
|
||||
}
|
||||
return dsp_states[index];
|
||||
}
|
||||
|
||||
VoiceChannelResource& VoiceContext::GetChannelResource(const u32 index) {
|
||||
if (index >= channel_resources.size()) {
|
||||
LOG_ERROR(Service_Audio, "Invalid voice channel resource index {:04X}", index);
|
||||
}
|
||||
return channel_resources[index];
|
||||
}
|
||||
|
||||
void VoiceContext::Initialize(std::span<VoiceInfo*> sorted_voice_infos_,
|
||||
std::span<VoiceInfo> voice_infos_,
|
||||
std::span<VoiceChannelResource> voice_channel_resources_,
|
||||
std::span<VoiceState> cpu_states_, std::span<VoiceState> dsp_states_,
|
||||
const u32 voice_count_) {
|
||||
sorted_voice_info = sorted_voice_infos_;
|
||||
voices = voice_infos_;
|
||||
channel_resources = voice_channel_resources_;
|
||||
cpu_states = cpu_states_;
|
||||
dsp_states = dsp_states_;
|
||||
voice_count = voice_count_;
|
||||
active_count = 0;
|
||||
}
|
||||
|
||||
VoiceInfo* VoiceContext::GetSortedInfo(const u32 index) {
|
||||
if (index >= sorted_voice_info.size()) {
|
||||
LOG_ERROR(Service_Audio, "Invalid voice sorted info index {:04X}", index);
|
||||
}
|
||||
return sorted_voice_info[index];
|
||||
}
|
||||
|
||||
VoiceInfo& VoiceContext::GetInfo(const u32 index) {
|
||||
if (index >= voices.size()) {
|
||||
LOG_ERROR(Service_Audio, "Invalid voice info index {:04X}", index);
|
||||
}
|
||||
return voices[index];
|
||||
}
|
||||
|
||||
VoiceState& VoiceContext::GetState(const u32 index) {
|
||||
if (index >= cpu_states.size()) {
|
||||
LOG_ERROR(Service_Audio, "Invalid voice cpu state index {:04X}", index);
|
||||
}
|
||||
return cpu_states[index];
|
||||
}
|
||||
|
||||
u32 VoiceContext::GetCount() const {
|
||||
return voice_count;
|
||||
}
|
||||
|
||||
u32 VoiceContext::GetActiveCount() const {
|
||||
return active_count;
|
||||
}
|
||||
|
||||
void VoiceContext::SetActiveCount(const u32 active_count_) {
|
||||
active_count = active_count_;
|
||||
}
|
||||
|
||||
void VoiceContext::SortInfo() {
|
||||
for (u32 i = 0; i < voice_count; i++) {
|
||||
sorted_voice_info[i] = &voices[i];
|
||||
}
|
||||
|
||||
std::ranges::sort(sorted_voice_info, [](const VoiceInfo* a, const VoiceInfo* b) {
|
||||
return a->priority != b->priority ? a->priority < b->priority
|
||||
: a->sort_order < b->sort_order;
|
||||
});
|
||||
}
|
||||
|
||||
void VoiceContext::UpdateStateByDspShared() {
|
||||
std::memcpy(cpu_states.data(), dsp_states.data(), voice_count * sizeof(VoiceState));
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
126
src/audio_core/renderer/voice/voice_context.h
Normal file
126
src/audio_core/renderer/voice/voice_context.h
Normal file
|
@ -0,0 +1,126 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/voice/voice_channel_resource.h"
|
||||
#include "audio_core/renderer/voice/voice_info.h"
|
||||
#include "audio_core/renderer/voice/voice_state.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Contains all voices, with utility functions for managing them.
|
||||
*/
|
||||
class VoiceContext {
|
||||
public:
|
||||
/**
|
||||
* Get the AudioRenderer state for a given index
|
||||
*
|
||||
* @param index - State index to get.
|
||||
* @return The requested voice state.
|
||||
*/
|
||||
VoiceState& GetDspSharedState(u32 index);
|
||||
|
||||
/**
|
||||
* Get the channel resource for a given index
|
||||
*
|
||||
* @param index - Resource index to get.
|
||||
* @return The requested voice resource.
|
||||
*/
|
||||
VoiceChannelResource& GetChannelResource(u32 index);
|
||||
|
||||
/**
|
||||
* Initialize the voice context.
|
||||
*
|
||||
* @param sorted_voice_infos - Workbuffer for the sorted voices.
|
||||
* @param voice_infos - Workbuffer for the voices.
|
||||
* @param voice_channel_resources - Workbuffer for the voice channel resources.
|
||||
* @param cpu_states - Workbuffer for the host-side voice states.
|
||||
* @param dsp_states - Workbuffer for the AudioRenderer-side voice states.
|
||||
* @param voice_count - The number of voices in each workbuffer.
|
||||
*/
|
||||
void Initialize(std::span<VoiceInfo*> sorted_voice_infos, std::span<VoiceInfo> voice_infos,
|
||||
std::span<VoiceChannelResource> voice_channel_resources,
|
||||
std::span<VoiceState> cpu_states, std::span<VoiceState> dsp_states,
|
||||
u32 voice_count);
|
||||
|
||||
/**
|
||||
* Get a sorted voice with the given index.
|
||||
*
|
||||
* @param index - The sorted voice index to get.
|
||||
* @return The sorted voice.
|
||||
*/
|
||||
VoiceInfo* GetSortedInfo(u32 index);
|
||||
|
||||
/**
|
||||
* Get a voice with the given index.
|
||||
*
|
||||
* @param index - The voice index to get.
|
||||
* @return The voice.
|
||||
*/
|
||||
VoiceInfo& GetInfo(u32 index);
|
||||
|
||||
/**
|
||||
* Get a host voice state with the given index.
|
||||
*
|
||||
* @param index - The host voice state index to get.
|
||||
* @return The voice state.
|
||||
*/
|
||||
VoiceState& GetState(u32 index);
|
||||
|
||||
/**
|
||||
* Get the maximum number of voices.
|
||||
* Not all voices in the buffers may be in use, see GetActiveCount.
|
||||
*
|
||||
* @return The maximum number of voices.
|
||||
*/
|
||||
u32 GetCount() const;
|
||||
|
||||
/**
|
||||
* Get the number of active voices.
|
||||
* Can be less than or equal to the maximum number of voices.
|
||||
*
|
||||
* @return The number of active voices.
|
||||
*/
|
||||
u32 GetActiveCount() const;
|
||||
|
||||
/**
|
||||
* Set the number of active voices.
|
||||
* Can be less than or equal to the maximum number of voices.
|
||||
*
|
||||
* @param active_count - The new number of active voices.
|
||||
*/
|
||||
void SetActiveCount(u32 active_count);
|
||||
|
||||
/**
|
||||
* Sort all voices. Results are available via GetSortedInfo.
|
||||
* Voices are sorted descendingly, according to priority, and then sort order.
|
||||
*/
|
||||
void SortInfo();
|
||||
|
||||
/**
|
||||
* Update all voice states, copying AudioRenderer-side states to host-side states.
|
||||
*/
|
||||
void UpdateStateByDspShared();
|
||||
|
||||
private:
|
||||
/// Sorted voices
|
||||
std::span<VoiceInfo*> sorted_voice_info{};
|
||||
/// Voices
|
||||
std::span<VoiceInfo> voices{};
|
||||
/// Channel resources
|
||||
std::span<VoiceChannelResource> channel_resources{};
|
||||
/// Host-side voice states
|
||||
std::span<VoiceState> cpu_states{};
|
||||
/// AudioRenderer-side voice states
|
||||
std::span<VoiceState> dsp_states{};
|
||||
/// Maximum number of voices
|
||||
u32 voice_count{};
|
||||
/// Number of active voices
|
||||
u32 active_count{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
408
src/audio_core/renderer/voice/voice_info.cpp
Normal file
408
src/audio_core/renderer/voice/voice_info.cpp
Normal file
|
@ -0,0 +1,408 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/memory/pool_mapper.h"
|
||||
#include "audio_core/renderer/voice/voice_context.h"
|
||||
#include "audio_core/renderer/voice/voice_info.h"
|
||||
#include "audio_core/renderer/voice/voice_state.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
VoiceInfo::VoiceInfo() {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
void VoiceInfo::Initialize() {
|
||||
in_use = false;
|
||||
is_new = false;
|
||||
id = 0;
|
||||
node_id = 0;
|
||||
current_play_state = ServerPlayState::Stopped;
|
||||
src_quality = SrcQuality::Medium;
|
||||
priority = LowestVoicePriority;
|
||||
sample_format = SampleFormat::Invalid;
|
||||
sample_rate = 0;
|
||||
channel_count = 0;
|
||||
wave_buffer_count = 0;
|
||||
wave_buffer_index = 0;
|
||||
pitch = 0.0f;
|
||||
volume = 0.0f;
|
||||
prev_volume = 0.0f;
|
||||
mix_id = UnusedMixId;
|
||||
splitter_id = UnusedSplitterId;
|
||||
biquads = {};
|
||||
biquad_initialized = {};
|
||||
voice_dropped = false;
|
||||
data_unmapped = false;
|
||||
buffer_unmapped = false;
|
||||
flush_buffer_count = 0;
|
||||
|
||||
data_address.Setup(0, 0);
|
||||
for (auto& wavebuffer : wavebuffers) {
|
||||
wavebuffer.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
bool VoiceInfo::ShouldUpdateParameters(const InParameter& params) const {
|
||||
return data_address.GetCpuAddr() != params.src_data_address ||
|
||||
data_address.GetSize() != params.src_data_size || data_unmapped;
|
||||
}
|
||||
|
||||
void VoiceInfo::UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
|
||||
const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
|
||||
in_use = params.in_use;
|
||||
id = params.id;
|
||||
node_id = params.node_id;
|
||||
UpdatePlayState(params.play_state);
|
||||
UpdateSrcQuality(params.src_quality);
|
||||
priority = params.priority;
|
||||
sort_order = params.sort_order;
|
||||
sample_rate = params.sample_rate;
|
||||
sample_format = params.sample_format;
|
||||
channel_count = static_cast<s8>(params.channel_count);
|
||||
pitch = params.pitch;
|
||||
volume = params.volume;
|
||||
biquads = params.biquads;
|
||||
wave_buffer_count = params.wave_buffer_count;
|
||||
wave_buffer_index = params.wave_buffer_index;
|
||||
|
||||
if (behavior.IsFlushVoiceWaveBuffersSupported()) {
|
||||
flush_buffer_count += params.flush_buffer_count;
|
||||
}
|
||||
|
||||
mix_id = params.mix_id;
|
||||
|
||||
if (behavior.IsSplitterSupported()) {
|
||||
splitter_id = params.splitter_id;
|
||||
} else {
|
||||
splitter_id = UnusedSplitterId;
|
||||
}
|
||||
|
||||
channel_resource_ids = params.channel_resource_ids;
|
||||
|
||||
flags &= u16(~0b11);
|
||||
if (behavior.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
|
||||
flags |= u16(params.flags.IsVoicePlayedSampleCountResetAtLoopPointSupported);
|
||||
}
|
||||
|
||||
if (behavior.IsVoicePitchAndSrcSkippedSupported()) {
|
||||
flags |= u16(params.flags.IsVoicePitchAndSrcSkippedSupported);
|
||||
}
|
||||
|
||||
if (params.clear_voice_drop) {
|
||||
voice_dropped = false;
|
||||
}
|
||||
|
||||
if (ShouldUpdateParameters(params)) {
|
||||
data_unmapped = !pool_mapper.TryAttachBuffer(error_info, data_address,
|
||||
params.src_data_address, params.src_data_size);
|
||||
} else {
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceInfo::UpdatePlayState(const PlayState state) {
|
||||
last_play_state = current_play_state;
|
||||
|
||||
switch (state) {
|
||||
case PlayState::Started:
|
||||
current_play_state = ServerPlayState::Started;
|
||||
break;
|
||||
case PlayState::Stopped:
|
||||
if (current_play_state != ServerPlayState::Stopped) {
|
||||
current_play_state = ServerPlayState::RequestStop;
|
||||
}
|
||||
break;
|
||||
case PlayState::Paused:
|
||||
current_play_state = ServerPlayState::Paused;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid input play state {}", static_cast<u32>(state));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceInfo::UpdateSrcQuality(const SrcQuality quality) {
|
||||
switch (quality) {
|
||||
case SrcQuality::Medium:
|
||||
src_quality = quality;
|
||||
break;
|
||||
case SrcQuality::High:
|
||||
src_quality = quality;
|
||||
break;
|
||||
case SrcQuality::Low:
|
||||
src_quality = quality;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Service_Audio, "Invalid input src quality {}", static_cast<u32>(quality));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceInfo::UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
|
||||
[[maybe_unused]] u32 error_count, const InParameter& params,
|
||||
std::span<VoiceState*> voice_states,
|
||||
const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
|
||||
if (params.is_new) {
|
||||
for (size_t i = 0; i < wavebuffers.size(); i++) {
|
||||
wavebuffers[i].Initialize();
|
||||
}
|
||||
|
||||
for (s8 channel = 0; channel < static_cast<s8>(params.channel_count); channel++) {
|
||||
voice_states[channel]->wave_buffer_valid.fill(false);
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < MaxWaveBuffers; i++) {
|
||||
UpdateWaveBuffer(error_infos[i], wavebuffers[i], params.wave_buffer_internal[i],
|
||||
params.sample_format, voice_states[0]->wave_buffer_valid[i], pool_mapper,
|
||||
behavior);
|
||||
}
|
||||
}
|
||||
|
||||
void VoiceInfo::UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info,
|
||||
WaveBuffer& wave_buffer,
|
||||
const WaveBufferInternal& wave_buffer_internal,
|
||||
const SampleFormat sample_format_, const bool valid,
|
||||
const PoolMapper& pool_mapper, const BehaviorInfo& behavior) {
|
||||
if (!valid && wave_buffer.sent_to_DSP && wave_buffer.buffer_address.GetCpuAddr() != 0) {
|
||||
pool_mapper.ForceUnmapPointer(wave_buffer.buffer_address);
|
||||
wave_buffer.buffer_address.Setup(0, 0);
|
||||
}
|
||||
|
||||
if (!ShouldUpdateWaveBuffer(wave_buffer_internal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (sample_format_) {
|
||||
case SampleFormat::PcmInt16: {
|
||||
constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmInt16)};
|
||||
if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
|
||||
wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
|
||||
LOG_ERROR(Service_Audio, "Invalid PCM16 start/end wavebuffer sizes!");
|
||||
error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
|
||||
error_info[0].address = wave_buffer_internal.address;
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SampleFormat::PcmFloat: {
|
||||
constexpr auto byte_size{GetSampleFormatByteSize(SampleFormat::PcmFloat)};
|
||||
if (wave_buffer_internal.start_offset * byte_size > wave_buffer_internal.size ||
|
||||
wave_buffer_internal.end_offset * byte_size > wave_buffer_internal.size) {
|
||||
LOG_ERROR(Service_Audio, "Invalid PCMFloat start/end wavebuffer sizes!");
|
||||
error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
|
||||
error_info[0].address = wave_buffer_internal.address;
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
|
||||
case SampleFormat::Adpcm: {
|
||||
const auto start_frame{wave_buffer_internal.start_offset / 14};
|
||||
auto start_extra{wave_buffer_internal.start_offset % 14 == 0
|
||||
? 0
|
||||
: (wave_buffer_internal.start_offset % 14) / 2 + 1 +
|
||||
((wave_buffer_internal.start_offset % 14) % 2)};
|
||||
const auto start{start_frame * 8 + start_extra};
|
||||
|
||||
const auto end_frame{wave_buffer_internal.end_offset / 14};
|
||||
const auto end_extra{wave_buffer_internal.end_offset % 14 == 0
|
||||
? 0
|
||||
: (wave_buffer_internal.end_offset % 14) / 2 + 1 +
|
||||
((wave_buffer_internal.end_offset % 14) % 2)};
|
||||
const auto end{end_frame * 8 + end_extra};
|
||||
|
||||
if (start > static_cast<s64>(wave_buffer_internal.size) ||
|
||||
end > static_cast<s64>(wave_buffer_internal.size)) {
|
||||
LOG_ERROR(Service_Audio, "Invalid ADPCM start/end wavebuffer sizes!");
|
||||
error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
|
||||
error_info[0].address = wave_buffer_internal.address;
|
||||
return;
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (wave_buffer_internal.start_offset < 0 || wave_buffer_internal.end_offset < 0) {
|
||||
LOG_ERROR(Service_Audio, "Invalid input start/end wavebuffer sizes!");
|
||||
error_info[0].error_code = Service::Audio::ERR_INVALID_UPDATE_DATA;
|
||||
error_info[0].address = wave_buffer_internal.address;
|
||||
return;
|
||||
}
|
||||
|
||||
wave_buffer.start_offset = wave_buffer_internal.start_offset;
|
||||
wave_buffer.end_offset = wave_buffer_internal.end_offset;
|
||||
wave_buffer.loop = wave_buffer_internal.loop;
|
||||
wave_buffer.stream_ended = wave_buffer_internal.stream_ended;
|
||||
wave_buffer.sent_to_DSP = false;
|
||||
wave_buffer.loop_start_offset = wave_buffer_internal.loop_start;
|
||||
wave_buffer.loop_end_offset = wave_buffer_internal.loop_end;
|
||||
wave_buffer.loop_count = wave_buffer_internal.loop_count;
|
||||
|
||||
buffer_unmapped =
|
||||
!pool_mapper.TryAttachBuffer(error_info[0], wave_buffer.buffer_address,
|
||||
wave_buffer_internal.address, wave_buffer_internal.size);
|
||||
|
||||
if (sample_format_ == SampleFormat::Adpcm && behavior.IsAdpcmLoopContextBugFixed() &&
|
||||
wave_buffer_internal.context_address != 0) {
|
||||
buffer_unmapped = !pool_mapper.TryAttachBuffer(error_info[1], wave_buffer.context_address,
|
||||
wave_buffer_internal.context_address,
|
||||
wave_buffer_internal.context_size) ||
|
||||
data_unmapped;
|
||||
} else {
|
||||
wave_buffer.context_address.Setup(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool VoiceInfo::ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const {
|
||||
return !wave_buffer_internal.sent_to_DSP || buffer_unmapped;
|
||||
}
|
||||
|
||||
void VoiceInfo::WriteOutStatus(OutStatus& out_status, const InParameter& params,
|
||||
std::span<VoiceState*> voice_states) {
|
||||
if (params.is_new) {
|
||||
is_new = true;
|
||||
}
|
||||
|
||||
if (params.is_new || is_new) {
|
||||
out_status.played_sample_count = 0;
|
||||
out_status.wave_buffers_consumed = 0;
|
||||
out_status.voice_dropped = false;
|
||||
} else {
|
||||
out_status.played_sample_count = voice_states[0]->played_sample_count;
|
||||
out_status.wave_buffers_consumed = voice_states[0]->wave_buffers_consumed;
|
||||
out_status.voice_dropped = voice_dropped;
|
||||
}
|
||||
}
|
||||
|
||||
bool VoiceInfo::ShouldSkip() const {
|
||||
return !in_use || wave_buffer_count == 0 || data_unmapped || buffer_unmapped || voice_dropped;
|
||||
}
|
||||
|
||||
bool VoiceInfo::HasAnyConnection() const {
|
||||
return mix_id != UnusedMixId || splitter_id != UnusedSplitterId;
|
||||
}
|
||||
|
||||
void VoiceInfo::FlushWaveBuffers(const u32 flush_count, std::span<VoiceState*> voice_states,
|
||||
const s8 channel_count_) {
|
||||
auto wave_index{wave_buffer_index};
|
||||
|
||||
for (size_t i = 0; i < flush_count; i++) {
|
||||
wavebuffers[wave_index].sent_to_DSP = true;
|
||||
|
||||
for (s8 j = 0; j < channel_count_; j++) {
|
||||
auto voice_state{voice_states[j]};
|
||||
if (voice_state->wave_buffer_index == wave_index) {
|
||||
voice_state->wave_buffer_index =
|
||||
(voice_state->wave_buffer_index + 1) % MaxWaveBuffers;
|
||||
voice_state->wave_buffers_consumed++;
|
||||
}
|
||||
voice_state->wave_buffer_valid[wave_index] = false;
|
||||
}
|
||||
|
||||
wave_index = (wave_index + 1) % MaxWaveBuffers;
|
||||
}
|
||||
}
|
||||
|
||||
bool VoiceInfo::UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states) {
|
||||
if (flush_buffer_count > 0) {
|
||||
FlushWaveBuffers(flush_buffer_count, voice_states, channel_count);
|
||||
flush_buffer_count = 0;
|
||||
}
|
||||
|
||||
switch (current_play_state) {
|
||||
case ServerPlayState::Started:
|
||||
for (u32 i = 0; i < MaxWaveBuffers; i++) {
|
||||
if (!wavebuffers[i].sent_to_DSP) {
|
||||
for (s8 channel = 0; channel < channel_count; channel++) {
|
||||
voice_states[channel]->wave_buffer_valid[i] = true;
|
||||
}
|
||||
wavebuffers[i].sent_to_DSP = true;
|
||||
}
|
||||
}
|
||||
|
||||
was_playing = false;
|
||||
|
||||
for (u32 i = 0; i < MaxWaveBuffers; i++) {
|
||||
if (voice_states[0]->wave_buffer_valid[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ServerPlayState::Stopped:
|
||||
case ServerPlayState::Paused:
|
||||
for (auto& wavebuffer : wavebuffers) {
|
||||
if (!wavebuffer.sent_to_DSP) {
|
||||
wavebuffer.buffer_address.GetReference(true);
|
||||
wavebuffer.context_address.GetReference(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (sample_format == SampleFormat::Adpcm && data_address.GetCpuAddr() != 0) {
|
||||
data_address.GetReference(true);
|
||||
}
|
||||
|
||||
was_playing = last_play_state == ServerPlayState::Started;
|
||||
break;
|
||||
|
||||
case ServerPlayState::RequestStop:
|
||||
for (u32 i = 0; i < MaxWaveBuffers; i++) {
|
||||
wavebuffers[i].sent_to_DSP = true;
|
||||
|
||||
for (s8 channel = 0; channel < channel_count; channel++) {
|
||||
if (voice_states[channel]->wave_buffer_valid[i]) {
|
||||
voice_states[channel]->wave_buffer_index =
|
||||
(voice_states[channel]->wave_buffer_index + 1) % MaxWaveBuffers;
|
||||
voice_states[channel]->wave_buffers_consumed++;
|
||||
}
|
||||
voice_states[channel]->wave_buffer_valid[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (s8 channel = 0; channel < channel_count; channel++) {
|
||||
voice_states[channel]->offset = 0;
|
||||
voice_states[channel]->played_sample_count = 0;
|
||||
voice_states[channel]->adpcm_context = {};
|
||||
voice_states[channel]->sample_history.fill(0);
|
||||
voice_states[channel]->fraction = 0;
|
||||
}
|
||||
|
||||
current_play_state = ServerPlayState::Stopped;
|
||||
was_playing = last_play_state == ServerPlayState::Started;
|
||||
break;
|
||||
}
|
||||
|
||||
return was_playing;
|
||||
}
|
||||
|
||||
bool VoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
|
||||
std::array<VoiceState*, MaxChannels> voice_states{};
|
||||
|
||||
if (is_new) {
|
||||
ResetResources(voice_context);
|
||||
prev_volume = volume;
|
||||
is_new = false;
|
||||
}
|
||||
|
||||
for (s8 channel = 0; channel < channel_count; channel++) {
|
||||
voice_states[channel] = &voice_context.GetDspSharedState(channel_resource_ids[channel]);
|
||||
}
|
||||
|
||||
return UpdateParametersForCommandGeneration(voice_states);
|
||||
}
|
||||
|
||||
void VoiceInfo::ResetResources(VoiceContext& voice_context) const {
|
||||
for (s8 channel = 0; channel < channel_count; channel++) {
|
||||
auto& state{voice_context.GetDspSharedState(channel_resource_ids[channel])};
|
||||
state = {};
|
||||
|
||||
auto& channel_resource{voice_context.GetChannelResource(channel_resource_ids[channel])};
|
||||
channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
378
src/audio_core/renderer/voice/voice_info.h
Normal file
378
src/audio_core/renderer/voice/voice_info.h
Normal file
|
@ -0,0 +1,378 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/common/wave_buffer.h"
|
||||
#include "audio_core/renderer/behavior/behavior_info.h"
|
||||
#include "audio_core/renderer/memory/address_info.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
class PoolMapper;
|
||||
class VoiceContext;
|
||||
struct VoiceState;
|
||||
|
||||
/**
|
||||
* Represents one voice. Voices are essentially noises, and they can be further mixed and have
|
||||
* effects applied to them, but voices are the basis of all sounds.
|
||||
*/
|
||||
class VoiceInfo {
|
||||
public:
|
||||
enum class ServerPlayState {
|
||||
Started,
|
||||
Stopped,
|
||||
RequestStop,
|
||||
Paused,
|
||||
};
|
||||
|
||||
struct Flags {
|
||||
u8 IsVoicePlayedSampleCountResetAtLoopPointSupported : 1;
|
||||
u8 IsVoicePitchAndSrcSkippedSupported : 1;
|
||||
};
|
||||
|
||||
/**
|
||||
* A wavebuffer contains information on the data source buffers.
|
||||
*/
|
||||
struct WaveBuffer {
|
||||
void Copy(WaveBufferVersion1& other) {
|
||||
other.buffer = buffer_address.GetReference(true);
|
||||
other.buffer_size = buffer_address.GetSize();
|
||||
other.start_offset = start_offset;
|
||||
other.end_offset = end_offset;
|
||||
other.loop = loop;
|
||||
other.stream_ended = stream_ended;
|
||||
|
||||
if (context_address.GetCpuAddr()) {
|
||||
other.context = context_address.GetReference(true);
|
||||
other.context_size = context_address.GetSize();
|
||||
} else {
|
||||
other.context = CpuAddr(0);
|
||||
other.context_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Copy(WaveBufferVersion2& other) {
|
||||
other.buffer = buffer_address.GetReference(true);
|
||||
other.buffer_size = buffer_address.GetSize();
|
||||
other.start_offset = start_offset;
|
||||
other.end_offset = end_offset;
|
||||
other.loop_start_offset = loop_start_offset;
|
||||
other.loop_end_offset = loop_end_offset;
|
||||
other.loop = loop;
|
||||
other.loop_count = loop_count;
|
||||
other.stream_ended = stream_ended;
|
||||
|
||||
if (context_address.GetCpuAddr()) {
|
||||
other.context = context_address.GetReference(true);
|
||||
other.context_size = context_address.GetSize();
|
||||
} else {
|
||||
other.context = CpuAddr(0);
|
||||
other.context_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
buffer_address.Setup(0, 0);
|
||||
context_address.Setup(0, 0);
|
||||
start_offset = 0;
|
||||
end_offset = 0;
|
||||
loop = false;
|
||||
stream_ended = false;
|
||||
sent_to_DSP = true;
|
||||
loop_start_offset = 0;
|
||||
loop_end_offset = 0;
|
||||
loop_count = 0;
|
||||
}
|
||||
/// Game memory address of the wavebuffer data
|
||||
AddressInfo buffer_address{0, 0};
|
||||
/// Context for decoding, used for ADPCM
|
||||
AddressInfo context_address{0, 0};
|
||||
/// Starting offset for the wavebuffer
|
||||
u32 start_offset{};
|
||||
/// Ending offset the wavebuffer
|
||||
u32 end_offset{};
|
||||
/// Should this wavebuffer loop?
|
||||
bool loop{};
|
||||
/// Has this wavebuffer ended?
|
||||
bool stream_ended{};
|
||||
/// Has this wavebuffer been sent to the AudioRenderer?
|
||||
bool sent_to_DSP{true};
|
||||
/// Starting offset when looping, can differ from start_offset
|
||||
u32 loop_start_offset{};
|
||||
/// Ending offset when looping, can differ from end_offset
|
||||
u32 loop_end_offset{};
|
||||
/// Number of times to loop this wavebuffer
|
||||
s32 loop_count{};
|
||||
};
|
||||
|
||||
struct WaveBufferInternal {
|
||||
/* 0x00 */ CpuAddr address;
|
||||
/* 0x08 */ u64 size;
|
||||
/* 0x10 */ s32 start_offset;
|
||||
/* 0x14 */ s32 end_offset;
|
||||
/* 0x18 */ bool loop;
|
||||
/* 0x19 */ bool stream_ended;
|
||||
/* 0x1A */ bool sent_to_DSP;
|
||||
/* 0x1C */ s32 loop_count;
|
||||
/* 0x20 */ CpuAddr context_address;
|
||||
/* 0x28 */ u64 context_size;
|
||||
/* 0x30 */ u32 loop_start;
|
||||
/* 0x34 */ u32 loop_end;
|
||||
};
|
||||
static_assert(sizeof(WaveBufferInternal) == 0x38,
|
||||
"VoiceInfo::WaveBufferInternal has the wrong size!");
|
||||
|
||||
struct BiquadFilterParameter {
|
||||
/* 0x00 */ bool enabled;
|
||||
/* 0x02 */ std::array<s16, 3> b;
|
||||
/* 0x08 */ std::array<s16, 2> a;
|
||||
};
|
||||
static_assert(sizeof(BiquadFilterParameter) == 0xC,
|
||||
"VoiceInfo::BiquadFilterParameter has the wrong size!");
|
||||
|
||||
struct InParameter {
|
||||
/* 0x000 */ u32 id;
|
||||
/* 0x004 */ u32 node_id;
|
||||
/* 0x008 */ bool is_new;
|
||||
/* 0x009 */ bool in_use;
|
||||
/* 0x00A */ PlayState play_state;
|
||||
/* 0x00B */ SampleFormat sample_format;
|
||||
/* 0x00C */ u32 sample_rate;
|
||||
/* 0x010 */ s32 priority;
|
||||
/* 0x014 */ s32 sort_order;
|
||||
/* 0x018 */ u32 channel_count;
|
||||
/* 0x01C */ f32 pitch;
|
||||
/* 0x020 */ f32 volume;
|
||||
/* 0x024 */ std::array<BiquadFilterParameter, MaxBiquadFilters> biquads;
|
||||
/* 0x03C */ u32 wave_buffer_count;
|
||||
/* 0x040 */ u16 wave_buffer_index;
|
||||
/* 0x042 */ char unk042[0x6];
|
||||
/* 0x048 */ CpuAddr src_data_address;
|
||||
/* 0x050 */ u64 src_data_size;
|
||||
/* 0x058 */ u32 mix_id;
|
||||
/* 0x05C */ u32 splitter_id;
|
||||
/* 0x060 */ std::array<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal;
|
||||
/* 0x140 */ std::array<u32, MaxChannels> channel_resource_ids;
|
||||
/* 0x158 */ bool clear_voice_drop;
|
||||
/* 0x159 */ u8 flush_buffer_count;
|
||||
/* 0x15A */ char unk15A[0x2];
|
||||
/* 0x15C */ Flags flags;
|
||||
/* 0x15D */ char unk15D[0x1];
|
||||
/* 0x15E */ SrcQuality src_quality;
|
||||
/* 0x15F */ char unk15F[0x11];
|
||||
};
|
||||
static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!");
|
||||
|
||||
struct OutStatus {
|
||||
/* 0x00 */ u64 played_sample_count;
|
||||
/* 0x08 */ u32 wave_buffers_consumed;
|
||||
/* 0x0C */ bool voice_dropped;
|
||||
};
|
||||
static_assert(sizeof(OutStatus) == 0x10, "OutStatus::InParameter has the wrong size!");
|
||||
|
||||
VoiceInfo();
|
||||
|
||||
/**
|
||||
* Initialize this voice.
|
||||
*/
|
||||
void Initialize();
|
||||
|
||||
/**
|
||||
* Does this voice ned an update?
|
||||
*
|
||||
* @param params - Input parametetrs to check matching.
|
||||
* @return True if this voice needs an update, otherwise false.
|
||||
*/
|
||||
bool ShouldUpdateParameters(const InParameter& params) const;
|
||||
|
||||
/**
|
||||
* Update the parameters of this voice.
|
||||
*
|
||||
* @param error_info - Output error code.
|
||||
* @param params - Input parametters to udpate from.
|
||||
* @param pool_mapper - Used to map buffers.
|
||||
* @param behavior - behavior to check supported features.
|
||||
*/
|
||||
void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
|
||||
const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
|
||||
|
||||
/**
|
||||
* Update the current play state.
|
||||
*
|
||||
* @param state - New play state for this voice.
|
||||
*/
|
||||
void UpdatePlayState(PlayState state);
|
||||
|
||||
/**
|
||||
* Update the current sample rate conversion quality.
|
||||
*
|
||||
* @param quality - New quality.
|
||||
*/
|
||||
void UpdateSrcQuality(SrcQuality quality);
|
||||
|
||||
/**
|
||||
* Update all wavebuffers.
|
||||
*
|
||||
* @param error_infos - Output 2D array of errors, 2 per wavebuffer.
|
||||
* @param error_count - Number of errors provided. Unused.
|
||||
* @param params - Input parametters to be used for the update.
|
||||
* @param voice_states - The voice states for each channel in this voice to be updated.
|
||||
* @param pool_mapper - Used to map the wavebuffers.
|
||||
* @param behavior - Used to check for supported features.
|
||||
*/
|
||||
void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
|
||||
u32 error_count, const InParameter& params,
|
||||
std::span<VoiceState*> voice_states, const PoolMapper& pool_mapper,
|
||||
const BehaviorInfo& behavior);
|
||||
|
||||
/**
|
||||
* Update a wavebuffer.
|
||||
*
|
||||
* @param error_infos - Output array of errors.
|
||||
* @param wave_buffer - The wavebuffer to be updated.
|
||||
* @param wave_buffer_internal - Input parametters to be used for the update.
|
||||
* @param sample_format - Sample format of the wavebuffer.
|
||||
* @param valid - Is this wavebuffer valid?
|
||||
* @param pool_mapper - Used to map the wavebuffers.
|
||||
* @param behavior - Used to check for supported features.
|
||||
*/
|
||||
void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
|
||||
const WaveBufferInternal& wave_buffer_internal,
|
||||
SampleFormat sample_format, bool valid, const PoolMapper& pool_mapper,
|
||||
const BehaviorInfo& behavior);
|
||||
|
||||
/**
|
||||
* Check if the input wavebuffer needs an update.
|
||||
*
|
||||
* @param wave_buffer_internal - Input wavebuffer parameters to check.
|
||||
* @return True if the given wavebuffer needs an update, otherwise false.
|
||||
*/
|
||||
bool ShouldUpdateWaveBuffer(const WaveBufferInternal& wave_buffer_internal) const;
|
||||
|
||||
/**
|
||||
* Write the number of played samples, number of consumed wavebuffers and if this voice was
|
||||
* dropped, to the given out_status.
|
||||
*
|
||||
* @param out_status - Output status to be written to.
|
||||
* @param in_params - Input parameters to check if the wavebuffer is new.
|
||||
* @param voice_states - Current host voice states for this voice, source of the output.
|
||||
*/
|
||||
void WriteOutStatus(OutStatus& out_status, const InParameter& in_params,
|
||||
std::span<VoiceState*> voice_states);
|
||||
|
||||
/**
|
||||
* Check if this voice should be skipped for command generation.
|
||||
* Checks various things such as usage state, whether data is mapped etc.
|
||||
*
|
||||
* @return True if this voice should not be generated, otherwise false.
|
||||
*/
|
||||
bool ShouldSkip() const;
|
||||
|
||||
/**
|
||||
* Check if this voice has any mixing connections.
|
||||
*
|
||||
* @return True if this voice participes in mixing, otherwise false.
|
||||
*/
|
||||
bool HasAnyConnection() const;
|
||||
|
||||
/**
|
||||
* Flush flush_count wavebuffers, marking them as consumed.
|
||||
*
|
||||
* @param flush_count - Number of wavebuffers to flush.
|
||||
* @param voice_states - Voice states for these wavebuffers.
|
||||
* @param channel_count - Number of active channels.
|
||||
*/
|
||||
void FlushWaveBuffers(u32 flush_count, std::span<VoiceState*> voice_states, s8 channel_count);
|
||||
|
||||
/**
|
||||
* Update this voice's parameters on command generation,
|
||||
* updating voice states and flushing if needed.
|
||||
*
|
||||
* @param voice_states - Voice states for these wavebuffers.
|
||||
* @return True if this voice should be generated, otherwise false.
|
||||
*/
|
||||
bool UpdateParametersForCommandGeneration(std::span<VoiceState*> voice_states);
|
||||
|
||||
/**
|
||||
* Update this voice on command generation.
|
||||
*
|
||||
* @param voice_states - Voice states for these wavebuffers.
|
||||
* @return True if this voice should be generated, otherwise false.
|
||||
*/
|
||||
bool UpdateForCommandGeneration(VoiceContext& voice_context);
|
||||
|
||||
/**
|
||||
* Reset the AudioRenderer-side voice states, and the channel resources for this voice.
|
||||
*
|
||||
* @param voice_context - Context from which to get the resources.
|
||||
*/
|
||||
void ResetResources(VoiceContext& voice_context) const;
|
||||
|
||||
/// Is this voice in use?
|
||||
bool in_use{};
|
||||
/// Is this voice new?
|
||||
bool is_new{};
|
||||
/// Was this voice last playing? Used for depopping
|
||||
bool was_playing{};
|
||||
/// Sample format of the wavebuffers in this voice
|
||||
SampleFormat sample_format{};
|
||||
/// Sample rate of the wavebuffers in this voice
|
||||
u32 sample_rate{};
|
||||
/// Number of channels in this voice
|
||||
s8 channel_count{};
|
||||
/// Id of this voice
|
||||
u32 id{};
|
||||
/// Node id of this voice
|
||||
u32 node_id{};
|
||||
/// Mix id this voice is mixed to
|
||||
u32 mix_id{};
|
||||
/// Play state of this voice
|
||||
ServerPlayState current_play_state{ServerPlayState::Stopped};
|
||||
/// Last play state of this voice
|
||||
ServerPlayState last_play_state{ServerPlayState::Started};
|
||||
/// Priority of this voice, lower is higher
|
||||
s32 priority{};
|
||||
/// Sort order of this voice, used when same priority
|
||||
s32 sort_order{};
|
||||
/// Pitch of this voice (for sample rate conversion)
|
||||
f32 pitch{};
|
||||
/// Current volume of this voice
|
||||
f32 volume{};
|
||||
/// Previous volume of this voice
|
||||
f32 prev_volume{};
|
||||
/// Biquad filters for generating filter commands on this voice
|
||||
std::array<BiquadFilterParameter, MaxBiquadFilters> biquads{};
|
||||
/// Number of active wavebuffers
|
||||
u32 wave_buffer_count{};
|
||||
/// Current playing wavebuffer index
|
||||
u16 wave_buffer_index{};
|
||||
/// Flags controlling decode behavior
|
||||
u16 flags{};
|
||||
/// Game memory for ADPCM coefficients
|
||||
AddressInfo data_address{0, 0};
|
||||
/// Wavebuffers
|
||||
std::array<WaveBuffer, MaxWaveBuffers> wavebuffers{};
|
||||
/// Channel resources for this voice
|
||||
std::array<u32, MaxChannels> channel_resource_ids{};
|
||||
/// Splitter id this voice is connected with
|
||||
s32 splitter_id{UnusedSplitterId};
|
||||
/// Sample rate conversion quality
|
||||
SrcQuality src_quality{SrcQuality::Medium};
|
||||
/// Was this voice dropped due to limited time?
|
||||
bool voice_dropped{};
|
||||
/// Is this voice's coefficient (data_address) unmapped?
|
||||
bool data_unmapped{};
|
||||
/// Is this voice's buffers (wavebuffer data and ADPCM context) unmapped?
|
||||
bool buffer_unmapped{};
|
||||
/// Initialisation state of the biquads
|
||||
std::array<bool, MaxBiquadFilters> biquad_initialized{};
|
||||
/// Number of wavebuffers to flush
|
||||
u8 flush_buffer_count{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
70
src/audio_core/renderer/voice/voice_state.h
Normal file
70
src/audio_core/renderer/voice/voice_state.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/fixed_point.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
/**
|
||||
* Holds a state for a voice. One is kept host-side, and one is used by the AudioRenderer,
|
||||
* host-side is updated on the next iteration.
|
||||
*/
|
||||
struct VoiceState {
|
||||
/**
|
||||
* State of the voice's biquad filter.
|
||||
*/
|
||||
struct BiquadFilterState {
|
||||
Common::FixedPoint<50, 14> s0;
|
||||
Common::FixedPoint<50, 14> s1;
|
||||
Common::FixedPoint<50, 14> s2;
|
||||
Common::FixedPoint<50, 14> s3;
|
||||
};
|
||||
|
||||
/**
|
||||
* Context for ADPCM decoding.
|
||||
*/
|
||||
struct AdpcmContext {
|
||||
u16 header;
|
||||
s16 yn0;
|
||||
s16 yn1;
|
||||
};
|
||||
|
||||
/// Number of samples played
|
||||
u64 played_sample_count;
|
||||
/// Current offset from the starting offset
|
||||
u32 offset;
|
||||
/// Currently active wavebuffer index
|
||||
u32 wave_buffer_index;
|
||||
/// Array of which wavebuffers are currently valid
|
||||
|
||||
std::array<bool, MaxWaveBuffers> wave_buffer_valid;
|
||||
/// Number of wavebuffers consumed, given back to the game
|
||||
u32 wave_buffers_consumed;
|
||||
/// History of samples, used for rate conversion
|
||||
|
||||
std::array<s16, MaxWaveBuffers * 2> sample_history;
|
||||
/// Current read fraction, used for resampling
|
||||
Common::FixedPoint<49, 15> fraction;
|
||||
/// Current adpcm context
|
||||
AdpcmContext adpcm_context;
|
||||
/// Current biquad states, used when filtering
|
||||
|
||||
std::array<std::array<BiquadFilterState, MaxBiquadFilters>, MaxBiquadFilters> biquad_states;
|
||||
/// Previous samples
|
||||
std::array<s32, MaxMixBuffers> previous_samples;
|
||||
/// Unused
|
||||
u32 external_context_size;
|
||||
/// Unused
|
||||
bool external_context_enabled;
|
||||
/// Was this voice dropped?
|
||||
bool voice_dropped;
|
||||
/// Number of times the wavebuffer has looped
|
||||
s32 loop_count;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
Loading…
Add table
Add a link
Reference in a new issue