From b718baa002584ce7a3083ad327b5e5ba32a55ae6 Mon Sep 17 00:00:00 2001 From: KorewaWatchful Date: Mon, 25 May 2026 21:34:06 -0400 Subject: [PATCH] ngs: refactor ngs/audio state ownership & some cleanup --- vita3k/modules/SceNgsUser/SceNgs.cpp | 21 +- vita3k/ngs/CMakeLists.txt | 1 + vita3k/ngs/include/ngs/modules/atrac9.h | 42 ++- vita3k/ngs/include/ngs/modules/output.h | 1 + vita3k/ngs/include/ngs/modules/player.h | 37 +- vita3k/ngs/include/ngs/rate_resampler.h | 54 +++ vita3k/ngs/include/ngs/system.h | 173 +++++++++- vita3k/ngs/src/modules/atrac9.cpp | 216 ++++++------ vita3k/ngs/src/modules/output.cpp | 13 +- vita3k/ngs/src/modules/player.cpp | 436 +++++++++++------------- vita3k/ngs/src/ngs.cpp | 137 +++++++- vita3k/ngs/src/rate_resampler.cpp | 195 +++++++++++ vita3k/ngs/src/route.cpp | 10 +- vita3k/ngs/src/scheduler.cpp | 14 +- 14 files changed, 923 insertions(+), 427 deletions(-) create mode 100644 vita3k/ngs/include/ngs/rate_resampler.h create mode 100644 vita3k/ngs/src/rate_resampler.cpp diff --git a/vita3k/modules/SceNgsUser/SceNgs.cpp b/vita3k/modules/SceNgsUser/SceNgs.cpp index 52d00397e..ad2e462db 100644 --- a/vita3k/modules/SceNgsUser/SceNgs.cpp +++ b/vita3k/modules/SceNgsUser/SceNgs.cpp @@ -218,18 +218,25 @@ EXPORT(SceInt32, sceNgsPatchGetInfo, ngs::Patch *patch, SceNgsPatchAudioPropInfo return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } + patch->refresh_endpoints(emuenv.mem); + ngs::Voice *source = patch->resolve_source(emuenv.mem); + ngs::Voice *dest = patch->resolve_dest(emuenv.mem); + if (!source || !dest) { + return RET_ERROR(SCE_NGS_ERROR); + } + if (prop_info) { memcpy(prop_info->volume_matrix.matrix, patch->volume_matrix, sizeof(patch->volume_matrix)); - prop_info->in_channels = patch->dest->rack->channels_per_voice; - prop_info->out_channels = patch->source->rack->channels_per_voice; + prop_info->in_channels = dest->rack->channels_per_voice; + prop_info->out_channels = source->rack->channels_per_voice; } if (deli_info) { deli_info->input_index = patch->dest_index; deli_info->output_index = patch->output_index; deli_info->output_subindex = patch->output_sub_index; - deli_info->source_voice_handle = Ptr(patch->source, emuenv.mem); - deli_info->dest_voice_handle = Ptr(patch->dest, emuenv.mem); + deli_info->source_voice_handle = Ptr(source, emuenv.mem); + deli_info->dest_voice_handle = Ptr(dest, emuenv.mem); } return SCE_NGS_OK; @@ -245,7 +252,9 @@ EXPORT(int, sceNgsPatchRemoveRouting, Ptr patch) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } - if (!patch.get(emuenv.mem)->source->remove_patch(emuenv.mem, patch)) { + patch.get(emuenv.mem)->refresh_endpoints(emuenv.mem); + ngs::Voice *source = patch.get(emuenv.mem)->resolve_source(emuenv.mem); + if (!source || !source->remove_patch(emuenv.mem, patch)) { return RET_ERROR(SCE_NGS_ERROR); } @@ -749,7 +758,7 @@ EXPORT(SceInt32, sceNgsVoiceGetStateData, ngs::Voice *voice, const SceUInt32 mod if (mem) { memset(mem, 0, mem_size); - memcpy(mem, storage->voice_state_data.data(), std::min(mem_size, storage->voice_state_data.size())); + memcpy(mem, storage->guest_state_data.data(), std::min(mem_size, storage->guest_state_data.size())); } return SCE_NGS_OK; diff --git a/vita3k/ngs/CMakeLists.txt b/vita3k/ngs/CMakeLists.txt index 9fc182a71..f0431f114 100644 --- a/vita3k/ngs/CMakeLists.txt +++ b/vita3k/ngs/CMakeLists.txt @@ -17,6 +17,7 @@ add_library( src/modules/reverb.cpp src/definitions.cpp src/ngs.cpp + src/rate_resampler.cpp src/route.cpp src/scheduler.cpp) diff --git a/vita3k/ngs/include/ngs/modules/atrac9.h b/vita3k/ngs/include/ngs/modules/atrac9.h index c9794fe8f..6871f731a 100644 --- a/vita3k/ngs/include/ngs/modules/atrac9.h +++ b/vita3k/ngs/include/ngs/modules/atrac9.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include @@ -62,35 +63,44 @@ struct SceNgsAT9States { SceInt32 bytes_consumed_since_key_on = 0; SceInt32 samples_generated_total = 0; SceInt32 total_bytes_consumed = 0; - - // INTERNAL - uint32_t decoded_samples_pending = 0; - uint32_t decoded_passed = 0; - uint32_t nb_channels = 0; - // used if the input must be resampled - SwrContext *swr = nullptr; - int8_t current_loop_count = 0; - // necessary if the decoder is using multiple states - Atrac9DecoderSavedState saved_state{}; }; namespace ngs { + +struct Atrac9LogicalState : public ModuleLogicalState { + PCMFrameQueue decoded_pcm; + // preserve enough recent resampler input to rebuild the swresample state later + StereoRateResamplerLogicalState rate_resampler; + std::vector superframe_staging; + // INTERNAL + int8_t current_loop_count = 0; + // tracks which config the saved decoder history belongs to + uint32_t decoder_config = 0; + // preserve the decoder's MDCT history so the runtime decoder can be rebuilt + Atrac9DecoderSavedState saved_state{}; +}; + +struct Atrac9RuntimeState : public ModuleRuntimeState { + std::unique_ptr decoder; + StereoRateResamplerRuntimeState rate_resampler; + std::vector decoded_superframe_samples; + std::vector temporary_bytes; +}; + class Atrac9Module : public Module { private: - std::unique_ptr decoder; - uint32_t last_config = 0; - std::vector temp_buffer; - SceNgsAT9States *last_state = nullptr; - static SwrContext *swr_mono_to_stereo; static SwrContext *swr_stereo; // return false if data could not be decoded (error or no more data available) - bool decode_more_data(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, const SceNgsAT9Params *params, SceNgsAT9States *state, std::unique_lock &scheduler_lock, std::unique_lock &voice_lock); + bool decode_more_data(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, const SceNgsAT9Params *params, SceNgsAT9States *state, Atrac9LogicalState *logical, Atrac9RuntimeState *runtime, std::unique_lock &scheduler_lock, std::unique_lock &voice_lock); public: bool process(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, std::unique_lock &scheduler_lock, std::unique_lock &voice_lock) override; uint32_t module_id() const override { return 0x5CAA; } + uint32_t get_guest_state_size() const override { return sizeof(SceNgsAT9States); } + std::unique_ptr create_logical_state() const override; + std::unique_ptr create_runtime_state() const override; void on_state_change(const MemState &mem, ModuleData &v, const VoiceState previous) override; void on_param_change(const MemState &mem, ModuleData &data) override; void cleanup_voice_state(ModuleData &data) override; diff --git a/vita3k/ngs/include/ngs/modules/output.h b/vita3k/ngs/include/ngs/modules/output.h index b28b267b5..3655001d4 100644 --- a/vita3k/ngs/include/ngs/modules/output.h +++ b/vita3k/ngs/include/ngs/modules/output.h @@ -22,6 +22,7 @@ namespace ngs { class OutputModule : public Module { public: + void initialize_voice_data(ModuleData &data) const override; bool process(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, std::unique_lock &scheduler_lock, std::unique_lock &voice_lock) override; static constexpr uint32_t get_max_parameter_size() { diff --git a/vita3k/ngs/include/ngs/modules/player.h b/vita3k/ngs/include/ngs/modules/player.h index 570bad38b..0eb48f997 100644 --- a/vita3k/ngs/include/ngs/modules/player.h +++ b/vita3k/ngs/include/ngs/modules/player.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include @@ -48,19 +49,6 @@ struct SceNgsPlayerStates { SceInt32 bytes_consumed_since_key_on = 0; SceInt32 samples_generated_total = 0; SceInt32 total_bytes_consumed = 0; - - std::vector adpcm_buffer; - - // INTERNAL - int8_t current_loop_count = 0; - uint32_t decoded_samples_pending = 0; - uint32_t decoded_samples_passed = 0; - // needed for he_adpcm because a same decoder can be used for many voices - ADPCMHistory adpcm_history[SCE_NGS_PLAYER_MAX_PCM_CHANNELS] = {}; - // used if the input must be resampled - SwrContext *swr = nullptr; - // if we need at some point to reset the resampler params - bool reset_swr = false; }; struct SceNgsPlayerParams { @@ -86,14 +74,31 @@ struct SceNgsPlayerParamsBlock { namespace ngs { -class PlayerModule : public Module { -private: - std::unique_ptr decoder; +struct PlayerLogicalState : public ModuleLogicalState { + PCMFrameQueue decoded_pcm; + // preserve enough recent resampler input to rebuild the swresample state later + StereoRateResamplerLogicalState rate_resampler; + std::vector adpcm_buffer; + // INTERNAL + int8_t current_loop_count = 0; + // preserve HE-ADPCM predictor history so the runtime decoder can be rebuilt + ADPCMHistory adpcm_history[SCE_NGS_PLAYER_MAX_PCM_CHANNELS] = {}; +}; +struct PlayerRuntimeState : public ModuleRuntimeState { + std::unique_ptr decoder; + StereoRateResamplerRuntimeState rate_resampler; + std::vector decoded_chunk; +}; + +class PlayerModule : public Module { public: void set_default_preset(const MemState &mem, ModuleData &data) override; bool process(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, std::unique_lock &scheduler_lock, std::unique_lock &voice_lock) override; uint32_t module_id() const override { return 0x5CE6; } + uint32_t get_guest_state_size() const override { return sizeof(SceNgsPlayerStates); } + std::unique_ptr create_logical_state() const override; + std::unique_ptr create_runtime_state() const override; void on_state_change(const MemState &mem, ModuleData &v, const VoiceState previous) override; void on_param_change(const MemState &mem, ModuleData &data) override; void cleanup_voice_state(ModuleData &data) override; diff --git a/vita3k/ngs/include/ngs/rate_resampler.h b/vita3k/ngs/include/ngs/rate_resampler.h new file mode 100644 index 000000000..34576d0f1 --- /dev/null +++ b/vita3k/ngs/include/ngs/rate_resampler.h @@ -0,0 +1,54 @@ +// Vita3K emulator project +// Copyright (C) 2026 Vita3K team +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#pragma once + +#include + +struct SwrContext; + +namespace ngs { + +struct StereoRateResamplerLogicalState { + PCMFrameQueue input_history; + bool needs_reset = false; + + void clear() { + input_history.clear(); + needs_reset = false; + } + + void reset() { + input_history.clear(); + needs_reset = true; + } +}; + +struct StereoRateResamplerRuntimeState { + SwrContext *context = nullptr; + int source_rate = 0; + int dest_rate = 0; + std::vector scratch_buffer; +}; + +void destroy_stereo_rate_resampler(StereoRateResamplerRuntimeState &runtime); +bool ensure_stereo_rate_resampler(StereoRateResamplerRuntimeState &runtime, StereoRateResamplerLogicalState &logical, + int source_rate, int dest_rate); +uint32_t process_stereo_rate_resampler(StereoRateResamplerRuntimeState &runtime, StereoRateResamplerLogicalState &logical, + const uint8_t *input, uint32_t input_frames, PCMFrameQueue &output); + +} // namespace ngs diff --git a/vita3k/ngs/include/ngs/system.h b/vita3k/ngs/include/ngs/system.h index ff9b49bca..61361bbd4 100644 --- a/vita3k/ngs/include/ngs/system.h +++ b/vita3k/ngs/include/ngs/system.h @@ -24,8 +24,12 @@ #include #include +#include #include +#include #include +#include +#include #include #include @@ -39,6 +43,7 @@ constexpr uint32_t default_normal_parameter_size = 100; struct State; struct Voice; +struct System; enum VoiceState { VOICE_STATE_AVAILABLE, @@ -47,14 +52,117 @@ enum VoiceState { VOICE_STATE_UNLOADING, }; +struct VoiceAddress { + int32_t rack_index = -1; + int32_t voice_index = -1; + + explicit operator bool() const { + return rack_index >= 0 && voice_index >= 0; + } +}; + struct Patch { int32_t output_index; int32_t output_sub_index; int32_t dest_index; + // runtime-only caches + ngs::System *system; ngs::Voice *dest; ngs::Voice *source; + VoiceAddress dest_address; + VoiceAddress source_address; + float volume_matrix[2][2]; + + bool is_active() const; + Voice *resolve_source(const MemState &mem) const; + Voice *resolve_dest(const MemState &mem) const; + void refresh_endpoints(const MemState &mem); +}; + +struct ModuleLogicalState { + virtual ~ModuleLogicalState() = default; +}; + +struct ModuleRuntimeState { + virtual ~ModuleRuntimeState() = default; +}; + +struct PCMFrameQueue { + std::vector samples; + uint32_t read_offset_frames = 0; + + void clear() { + samples.clear(); + read_offset_frames = 0; + } + + bool empty() const { + return available_frames() == 0; + } + + uint32_t total_frames() const { + return static_cast(samples.size() / 2); + } + + uint32_t available_frames() const { + const uint32_t total = total_frames(); + return (read_offset_frames >= total) ? 0 : (total - read_offset_frames); + } + + void compact() { + if (read_offset_frames == 0) { + return; + } + + if (read_offset_frames >= total_frames()) { + clear(); + return; + } + + samples.erase(samples.begin(), samples.begin() + static_cast(read_offset_frames) * 2); + read_offset_frames = 0; + } + + uint8_t *append_uninitialized_bytes(const uint32_t frames) { + const size_t old_samples = samples.size(); + samples.resize(old_samples + static_cast(frames) * 2); + return reinterpret_cast(samples.data() + old_samples); + } + + void append_bytes(const uint8_t *source, const uint32_t frames) { + if (frames == 0) { + return; + } + + uint8_t *dest = append_uninitialized_bytes(frames); + std::memcpy(dest, source, static_cast(frames) * sizeof(float) * 2); + } + + void append_frame(const float left, const float right) { + samples.push_back(left); + samples.push_back(right); + } + + uint8_t *read_bytes() { + if (samples.empty()) { + return nullptr; + } + + return reinterpret_cast(samples.data() + static_cast(read_offset_frames) * 2); + } + + void ensure_available_frames(const uint32_t frames) { + const size_t required_samples = static_cast(read_offset_frames + frames) * 2; + if (samples.size() < required_samples) { + samples.resize(required_samples, 0.0f); + } + } + + void consume_frames(const uint32_t frames) { + read_offset_frames += std::min(frames, available_frames()); + } }; struct ModuleData { @@ -66,8 +174,10 @@ struct ModuleData { bool is_bypassed; - std::vector voice_state_data; ///< Voice state. - std::vector extra_storage; ///< Local data storage for module. + std::vector guest_state_data; ///< guest-visible voice state + std::vector scratch_data; ///< temp local data storage for module + std::unique_ptr logical_state; ///< non-guest state needed to resume processing later + std::unique_ptr runtime_state; ///< host runtime objects rebuilt from logical state SceNgsBufferInfo info; std::vector last_info; @@ -82,12 +192,30 @@ struct ModuleData { template T *get_state() { - if (voice_state_data.empty()) { - voice_state_data.resize(sizeof(T)); - new (&voice_state_data[0]) T(); + if (guest_state_data.empty()) { + guest_state_data.resize(sizeof(T)); + new (&guest_state_data[0]) T(); } - return reinterpret_cast(&voice_state_data[0]); + return reinterpret_cast(&guest_state_data[0]); + } + + template + T *get_logical_state() { + if (!logical_state) { + logical_state = std::make_unique(); + } + + return static_cast(logical_state.get()); + } + + template + T *get_runtime_state() { + if (!runtime_state) { + runtime_state = std::make_unique(); + } + + return static_cast(runtime_state.get()); } template @@ -100,13 +228,17 @@ struct ModuleData { return info.data.cast().get(mem); } - void fill_to_fit_granularity(); - void invoke_callback(KernelState &kern, const MemState &mem, const SceUID thread_id, const uint32_t reason1, const uint32_t reason2, Address reason_ptr); SceNgsBufferInfo *lock_params(const MemState &mem); bool unlock_params(const MemState &mem); + + void ensure_scratch_size(const size_t size) { + if (scratch_data.size() < size) { + scratch_data.resize(size); + } + } }; class Module { @@ -117,9 +249,25 @@ public: virtual bool process(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, std::unique_lock &scheduler_lock, std::unique_lock &voice_lock) = 0; virtual uint32_t module_id() const { return 0; } virtual uint32_t get_buffer_parameter_size() const = 0; + virtual uint32_t get_guest_state_size() const { return 0; } + virtual std::unique_ptr create_logical_state() const { return nullptr; } + virtual std::unique_ptr create_runtime_state() const { return nullptr; } virtual void on_state_change(const MemState &mem, ModuleData &v, const VoiceState previous) {} virtual void on_param_change(const MemState &mem, ModuleData &data) {} virtual void cleanup_voice_state(ModuleData &data) {} + + virtual void initialize_voice_data(ModuleData &data) const { + const uint32_t guest_state_size = get_guest_state_size(); + if (guest_state_size != 0) { + data.guest_state_data.assign(guest_state_size, 0); + } else { + data.guest_state_data.clear(); + } + + data.scratch_data.clear(); + data.logical_state = create_logical_state(); + data.runtime_state = create_runtime_state(); + } }; static constexpr uint32_t MAX_VOICE_OUTPUT = 4; @@ -144,7 +292,7 @@ struct VoiceInputManager { void reset_inputs(); PCMInput *get_input_buffer_queue(const int32_t index); - int32_t receive(Patch *patch, const VoiceProduct &data); + int32_t receive(const MemState &mem, Patch *patch, const VoiceProduct &data); }; struct Voice { @@ -184,6 +332,8 @@ struct Voice { void invoke_callback(KernelState &kernel, const MemState &mem, const SceUID thread_id, Ptr callback, Ptr user_data, const uint32_t module_id, const uint32_t reason = 0, const uint32_t reason2 = 0, Address reason_ptr = 0); + + VoiceAddress address(const MemState &mem) const; }; struct System; @@ -201,6 +351,8 @@ struct Rack : public MempoolObject { explicit Rack(System *mama, const Ptr memspace, const uint32_t memspace_size); + int32_t index_of_voice(const MemState &mem, const Voice *voice) const; + static uint32_t get_required_memspace_size(MemState &mem, SceNgsRackDescription *description); }; @@ -213,6 +365,9 @@ struct System : public MempoolObject { VoiceScheduler voice_scheduler; explicit System(const Ptr memspace, const uint32_t memspace_size); + int32_t index_of_rack(const Rack *rack) const; + VoiceAddress address_of(const MemState &mem, const Voice *voice) const; + Voice *find_voice(const MemState &mem, const VoiceAddress &address) const; static uint32_t get_required_memspace_size(SceNgsSystemInitParams *parameters); }; diff --git a/vita3k/ngs/src/modules/atrac9.cpp b/vita3k/ngs/src/modules/atrac9.cpp index fbaa1de40..89409a638 100644 --- a/vita3k/ngs/src/modules/atrac9.cpp +++ b/vita3k/ngs/src/modules/atrac9.cpp @@ -22,65 +22,72 @@ extern "C" { #include } +#include +#include +#include + namespace ngs { SwrContext *Atrac9Module::swr_mono_to_stereo = nullptr; SwrContext *Atrac9Module::swr_stereo = nullptr; +std::unique_ptr Atrac9Module::create_logical_state() const { + return std::make_unique(); +} + +std::unique_ptr Atrac9Module::create_runtime_state() const { + return std::make_unique(); +} + void Atrac9Module::on_state_change(const MemState &mem, ModuleData &data, const VoiceState previous) { SceNgsAT9States *state = data.get_state(); + Atrac9LogicalState *logical = data.get_logical_state(); + if (data.parent->state == VOICE_STATE_ACTIVE && previous == VOICE_STATE_AVAILABLE) { state->samples_generated_since_key_on = 0; state->bytes_consumed_since_key_on = 0; state->current_byte_position_in_buffer = 0; - state->current_loop_count = 0; + logical->current_loop_count = 0; state->current_buffer = 0; - - memset(&state->saved_state, 0, sizeof(state->saved_state)); - if (last_state == state) - last_state = nullptr; + logical->decoded_pcm.clear(); + logical->rate_resampler.reset(); + logical->superframe_staging.clear(); + std::memset(&logical->saved_state, 0, sizeof(logical->saved_state)); } else if (data.parent->is_keyed_off) { state->current_byte_position_in_buffer = 0; - state->current_loop_count = 0; + logical->current_loop_count = 0; state->current_buffer = 0; + logical->rate_resampler.reset(); } } void Atrac9Module::on_param_change(const MemState &mem, ModuleData &data) { - SceNgsAT9States *state = data.get_state(); + Atrac9LogicalState *logical = data.get_logical_state(); const SceNgsAT9Params *old_params = reinterpret_cast(data.last_info.data()); const SceNgsAT9Params *new_params = static_cast(data.info.data.get(mem)); - // if playback scaling changed, reset the resampler - if (state->swr && (old_params->playback_frequency != new_params->playback_frequency || old_params->playback_scalar != new_params->playback_scalar)) { - swr_free(&state->swr); + if (old_params->playback_frequency != new_params->playback_frequency || old_params->playback_scalar != new_params->playback_scalar) { + logical->rate_resampler.reset(); } } -bool Atrac9Module::decode_more_data(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, const SceNgsAT9Params *params, SceNgsAT9States *state, std::unique_lock &scheduler_lock, std::unique_lock &voice_lock) { +bool Atrac9Module::decode_more_data(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, const SceNgsAT9Params *params, SceNgsAT9States *state, Atrac9LogicalState *logical, Atrac9RuntimeState *runtime, std::unique_lock &scheduler_lock, std::unique_lock &voice_lock) { const SceNgsAT9BufferParams &bufparam = params->buffer_params[state->current_buffer]; - if (!data.extra_storage.empty()) { - data.extra_storage.erase(data.extra_storage.begin(), data.extra_storage.begin() + state->decoded_passed * sizeof(float) * 2); - state->decoded_passed = 0; - } - - if (decoder && last_state != nullptr && last_state != state) { - // we changed voices, need to export the previous data - decoder->export_state(&last_state->saved_state); - } + const bool config_changed = (params->config_data != logical->decoder_config); // re-create the decoder if necessary - if (!decoder || params->config_data != last_config) { - decoder = std::make_unique(params->config_data); - last_config = params->config_data; + if (!runtime->decoder || config_changed) { + runtime->decoder = std::make_unique(params->config_data); + if (config_changed) { + logical->decoder_config = params->config_data; + std::memset(&logical->saved_state, 0, sizeof(logical->saved_state)); + logical->superframe_staging.clear(); + } } - if (last_state != state) { - // we changed voices, need to import the new decoder state - decoder->load_state(&state->saved_state); - last_state = state; - } + // re-apply the logical decoder state before we resume decoding + runtime->decoder->load_state(&logical->saved_state); if (state->current_byte_position_in_buffer >= bufparam.bytes_count) { const int32_t prev_index = state->current_buffer; @@ -88,12 +95,12 @@ bool Atrac9Module::decode_more_data(KernelState &kern, const MemState &mem, cons voice_lock.unlock(); scheduler_lock.unlock(); - state->current_loop_count++; + logical->current_loop_count++; state->current_byte_position_in_buffer = 0; - if ((bufparam.loop_count != -1) && (state->current_loop_count > bufparam.loop_count)) { + if ((bufparam.loop_count != -1) && (logical->current_loop_count > bufparam.loop_count)) { state->current_buffer = bufparam.next_buffer_index; - state->current_loop_count = 0; + logical->current_loop_count = 0; if ((state->current_buffer == -1) || !params->buffer_params[state->current_buffer].buffer @@ -109,7 +116,7 @@ bool Atrac9Module::decode_more_data(KernelState &kern, const MemState &mem, cons params->buffer_params[state->current_buffer].buffer.address()); } } else { - data.invoke_callback(kern, mem, thread_id, SCE_NGS_AT9_LOOPED_BUFFER, state->current_loop_count, + data.invoke_callback(kern, mem, thread_id, SCE_NGS_AT9_LOOPED_BUFFER, logical->current_loop_count, params->buffer_params[state->current_buffer].buffer.address()); } @@ -119,44 +126,44 @@ bool Atrac9Module::decode_more_data(KernelState &kern, const MemState &mem, cons // re-call this function return true; } + // now we are sure we have a buffer with some data in it uint8_t *input = bufparam.buffer.cast().get(mem) + state->current_byte_position_in_buffer; - const uint32_t superframe_size = decoder->get(DecoderQuery::AT9_SUPERFRAME_SIZE); + const uint32_t superframe_size = runtime->decoder->get(DecoderQuery::AT9_SUPERFRAME_SIZE); uint32_t frame_bytes_gotten = bufparam.bytes_count - state->current_byte_position_in_buffer; - if (frame_bytes_gotten < superframe_size || !temp_buffer.empty()) { + if (frame_bytes_gotten < superframe_size || !logical->superframe_staging.empty()) { // the superframe overlaps two buffers... - uint32_t bytes_transferred = std::min(frame_bytes_gotten, superframe_size - temp_buffer.size()); - uint32_t old_size = temp_buffer.size(); - temp_buffer.resize(old_size + bytes_transferred); - memcpy(temp_buffer.data() + old_size, input, bytes_transferred); + uint32_t bytes_transferred = std::min(frame_bytes_gotten, superframe_size - static_cast(logical->superframe_staging.size())); + uint32_t old_size = static_cast(logical->superframe_staging.size()); + logical->superframe_staging.resize(old_size + bytes_transferred); + std::memcpy(logical->superframe_staging.data() + old_size, input, bytes_transferred); - if (temp_buffer.size() < superframe_size) { + if (logical->superframe_staging.size() < superframe_size) { // continue getting data state->current_byte_position_in_buffer = bufparam.bytes_count; return true; } + // make the byte position negative, will be positive at the end state->current_byte_position_in_buffer = -(int32_t)old_size; - input = temp_buffer.data(); + input = logical->superframe_staging.data(); } - size_t curr_pos = state->decoded_samples_pending * sizeof(float) * 2; - - const uint32_t samples_per_frame = decoder->get(DecoderQuery::AT9_SAMPLE_PER_FRAME); - const uint32_t samples_per_superframe = decoder->get(DecoderQuery::AT9_SAMPLE_PER_SUPERFRAME); + const uint32_t samples_per_frame = runtime->decoder->get(DecoderQuery::AT9_SAMPLE_PER_FRAME); + const uint32_t samples_per_superframe = runtime->decoder->get(DecoderQuery::AT9_SAMPLE_PER_SUPERFRAME); // we need to account for sampled skipped at the beginning or the end of the buffer uint32_t decoded_size = samples_per_superframe; uint32_t decoded_start_offset = 0; // some games (like Muramasa) send the atrac9 files with the header, we need to skip it - if (memcmp(input, "RIFF", 4) == 0 - && memcmp(input + 8, "WAVE", 4) == 0) { + if (std::memcmp(input, "RIFF", 4) == 0 + && std::memcmp(input + 8, "WAVE", 4) == 0) { // file header is 12 bytes long input += 3 * sizeof(uint32_t); state->current_byte_position_in_buffer += 3 * sizeof(uint32_t); - while (memcmp(input, "data", 4) != 0) { + while (std::memcmp(input, "data", 4) != 0) { // each chunk has a 4-byte identifier followed by its size (minus 8) in an int32 const int32_t header_data = 2 * sizeof(uint32_t) + *reinterpret_cast(input + 4); state->current_byte_position_in_buffer += header_data; @@ -168,13 +175,13 @@ bool Atrac9Module::decode_more_data(KernelState &kern, const MemState &mem, cons } // if the superframe is across two buffers, I don't know how to interpret the skipped samples (which are in the middle of the frame)... - if (temp_buffer.empty()) { + if (logical->superframe_staging.empty()) { // remove skipped samples at the beginning and the end of the buffer // in case you have more than a superframe of samples skipped (I don't know if this can happen) const uint32_t sample_index = (state->current_byte_position_in_buffer / superframe_size) * samples_per_superframe; if (bufparam.samples_discard_start_off > sample_index) { // first chunk - const uint32_t skipped_samples = std::min(samples_per_superframe, bufparam.samples_discard_start_off - sample_index); + const uint32_t skipped_samples = std::min(samples_per_superframe, static_cast(bufparam.samples_discard_start_off - sample_index)); decoded_start_offset += skipped_samples; decoded_size -= skipped_samples; } @@ -182,25 +189,25 @@ bool Atrac9Module::decode_more_data(KernelState &kern, const MemState &mem, cons const uint32_t samples_left_after = (frame_bytes_gotten / superframe_size - 1) * samples_per_superframe; if (bufparam.samples_discard_end_off > samples_left_after) { // last chunk - decoded_size -= std::min(decoded_size, bufparam.samples_discard_end_off - samples_left_after); + decoded_size -= std::min(decoded_size, static_cast(bufparam.samples_discard_end_off - samples_left_after)); } } - std::vector decoded_superframe_samples(decoder->get(DecoderQuery::AT9_SAMPLE_PER_SUPERFRAME) * sizeof(float) * 2); + runtime->decoded_superframe_samples.resize(static_cast(runtime->decoder->get(DecoderQuery::AT9_SAMPLE_PER_SUPERFRAME)) * sizeof(float) * 2); uint32_t decoded_superframe_pos = 0; bool got_decode_error = false; // decode a whole superframe at a time - for (uint32_t frame = 0; frame < decoder->get(DecoderQuery::AT9_FRAMES_IN_SUPERFRAME); frame++) { - if (!decoder->send(input, 0)) { + for (uint32_t frame = 0; frame < runtime->decoder->get(DecoderQuery::AT9_FRAMES_IN_SUPERFRAME); frame++) { + if (!runtime->decoder->send(input, 0)) { got_decode_error = true; break; } // convert from int16 to float - uint32_t const channel_count = decoder->get(DecoderQuery::CHANNELS); - std::vector temporary_bytes(samples_per_frame * sizeof(int16_t) * channel_count); + const uint32_t channel_count = runtime->decoder->get(DecoderQuery::CHANNELS); + runtime->temporary_bytes.resize(static_cast(samples_per_frame) * sizeof(int16_t) * channel_count); DecoderSize decoder_size; - decoder->receive(temporary_bytes.data(), &decoder_size); + runtime->decoder->receive(runtime->temporary_bytes.data(), &decoder_size); SwrContext *swr; if (channel_count == 1) { @@ -233,55 +240,32 @@ bool Atrac9Module::decode_more_data(KernelState &kern, const MemState &mem, cons swr = swr_stereo; } - const uint8_t *swr_data_in = temporary_bytes.data(); - uint8_t *swr_data_out = decoded_superframe_samples.data() + decoded_superframe_pos; - const int result = swr_convert(swr, &swr_data_out, decoder_size.samples, &swr_data_in, decoder_size.samples); + const uint8_t *swr_data_in = runtime->temporary_bytes.data(); + uint8_t *swr_data_out = runtime->decoded_superframe_samples.data() + decoded_superframe_pos; + swr_convert(swr, &swr_data_out, decoder_size.samples, &swr_data_in, decoder_size.samples); decoded_superframe_pos += decoder_size.samples * sizeof(float) * 2; - input += decoder->get_es_size(); - state->current_byte_position_in_buffer += decoder->get_es_size(); + input += runtime->decoder->get_es_size(); + state->current_byte_position_in_buffer += runtime->decoder->get_es_size(); } const int32_t sample_rate = data.parent->rack->system->sample_rate; - if (params->playback_scalar != 1 || static_cast(round(params->playback_frequency)) != sample_rate) { + if (params->playback_scalar != 1 || static_cast(std::round(params->playback_frequency)) != sample_rate) { LOG_INFO_ONCE("The currently running game requests playback rate scaling when decoding audio. Audio might crackle."); - - // resample the audio - int src_sample_rate = static_cast(params->playback_frequency); - if (params->playback_scalar != 1.0f) + double src_sample_rate = params->playback_frequency; + if (params->playback_scalar != 1.0f) { src_sample_rate *= params->playback_scalar; - - if (!state->swr) { - AVChannelLayout layout_stereo = AV_CHANNEL_LAYOUT_STEREO; - int ret = swr_alloc_set_opts2(&state->swr, - &layout_stereo, AV_SAMPLE_FMT_FLT, sample_rate, - &layout_stereo, AV_SAMPLE_FMT_FLT, src_sample_rate, - 0, nullptr); - assert(ret == 0); - - ret = swr_init(state->swr); - assert(ret == 0); } - // assume the skipped samples happen before the scaling - int scaled_samples_amount = swr_get_out_samples(state->swr, decoded_size); - std::vector scaled_data(scaled_samples_amount * sizeof(float) * 2, 0); - uint8_t *scaled_dest_data = scaled_data.data(); - const uint8_t *scaled_src_data = decoded_superframe_samples.data() + decoded_start_offset * sizeof(float) * 2; - scaled_samples_amount = swr_convert(state->swr, &scaled_dest_data, scaled_samples_amount, &scaled_src_data, decoded_size); - assert(scaled_samples_amount > 0); - - // Allocate memory to accommodate the result of the scaling process into the queue for the final audio buffer - data.extra_storage.resize(curr_pos + scaled_samples_amount * sizeof(float) * 2); - - // Pass scaled audio data into the queue for the final audio buffer - memcpy(data.extra_storage.data() + curr_pos, scaled_data.data(), scaled_samples_amount * sizeof(float) * 2); - decoded_size = scaled_samples_amount; + ensure_stereo_rate_resampler(runtime->rate_resampler, logical->rate_resampler, + static_cast(src_sample_rate), sample_rate); + // sssume skipped samples happen before playback-rate scaling + decoded_size = process_stereo_rate_resampler(runtime->rate_resampler, logical->rate_resampler, + runtime->decoded_superframe_samples.data() + decoded_start_offset * sizeof(float) * 2, decoded_size, + logical->decoded_pcm); } else { - data.extra_storage.resize(curr_pos + decoded_size * sizeof(float) * 2); - - memcpy(data.extra_storage.data() + curr_pos, decoded_superframe_samples.data() + decoded_start_offset * sizeof(float) * 2, decoded_size * sizeof(float) * 2); + logical->decoded_pcm.append_bytes(runtime->decoded_superframe_samples.data() + decoded_start_offset * sizeof(float) * 2, decoded_size); } if (got_decode_error) { @@ -294,24 +278,26 @@ bool Atrac9Module::decode_more_data(KernelState &kern, const MemState &mem, cons scheduler_lock.lock(); voice_lock.lock(); - // flush or we'll get en error next time we cant to decode - decoder->flush(); + // flush or we'll get an error next time we want to decode + runtime->decoder->flush(); } - temp_buffer.clear(); + logical->superframe_staging.clear(); state->samples_generated_since_key_on += decoded_size * params->channels; state->samples_generated_total += decoded_size * params->channels; state->bytes_consumed_since_key_on += superframe_size; state->total_bytes_consumed += superframe_size; + runtime->decoder->export_state(&logical->saved_state); - state->decoded_samples_pending += decoded_size; return true; } bool Atrac9Module::process(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, std::unique_lock &scheduler_lock, std::unique_lock &voice_lock) { const SceNgsAT9Params *params = data.get_parameters(mem); SceNgsAT9States *state = data.get_state(); + Atrac9LogicalState *logical = data.get_logical_state(); + Atrac9RuntimeState *runtime = data.get_runtime_state(); assert(state); if (state->current_buffer == -1 @@ -319,25 +305,33 @@ bool Atrac9Module::process(KernelState &kern, const MemState &mem, const SceUID return true; } + logical->decoded_pcm.compact(); + bool is_finished = false; // call decode more data until we either have an error or reached end of data - while (static_cast(state->decoded_samples_pending) < data.parent->rack->system->granularity) { - if (!decode_more_data(kern, mem, thread_id, data, params, state, scheduler_lock, voice_lock)) { + while (static_cast(logical->decoded_pcm.available_frames()) < data.parent->rack->system->granularity) { + if (!decode_more_data(kern, mem, thread_id, data, params, state, logical, runtime, scheduler_lock, voice_lock)) { is_finished = true; break; } } - // make sure the buffer is big enough - data.fill_to_fit_granularity(); + const uint32_t granularity = static_cast(data.parent->rack->system->granularity); + const uint32_t available_samples = logical->decoded_pcm.available_frames(); + const uint32_t samples_to_be_passed = std::min(available_samples, granularity); - uint8_t *data_ptr = data.extra_storage.data() + 2 * sizeof(float) * state->decoded_passed; - uint32_t samples_to_be_passed = data.parent->rack->system->granularity; + if (available_samples >= granularity) { + data.parent->products[0].data = logical->decoded_pcm.read_bytes(); + } else { + data.ensure_scratch_size(static_cast(granularity) * sizeof(float) * 2); + std::fill(data.scratch_data.begin(), data.scratch_data.end(), 0); + if (available_samples > 0) { + std::memcpy(data.scratch_data.data(), logical->decoded_pcm.read_bytes(), static_cast(available_samples) * sizeof(float) * 2); + } + data.parent->products[0].data = data.scratch_data.data(); + } - data.parent->products[0].data = data_ptr; - - state->decoded_samples_pending = (state->decoded_samples_pending < samples_to_be_passed) ? 0 : (state->decoded_samples_pending - samples_to_be_passed); - state->decoded_passed += samples_to_be_passed; + logical->decoded_pcm.consume_frames(samples_to_be_passed); return is_finished; } @@ -354,10 +348,10 @@ void Atrac9Module::free_swr_contexts() { } void Atrac9Module::cleanup_voice_state(ModuleData &data) { - SceNgsAT9States *state = data.get_state(); - if (state->swr) { - swr_free(&state->swr); + if (auto *runtime = static_cast(data.runtime_state.get())) { + destroy_stereo_rate_resampler(runtime->rate_resampler); } + data.runtime_state.reset(); } } // namespace ngs diff --git a/vita3k/ngs/src/modules/output.cpp b/vita3k/ngs/src/modules/output.cpp index 4bfd9107b..2b23356dd 100644 --- a/vita3k/ngs/src/modules/output.cpp +++ b/vita3k/ngs/src/modules/output.cpp @@ -20,19 +20,20 @@ #include namespace ngs { +void OutputModule::initialize_voice_data(ModuleData &data) const { + Module::initialize_voice_data(data); + data.guest_state_data.resize(static_cast(data.parent->rack->system->granularity) * sizeof(std::uint16_t) * 2); +} + bool OutputModule::process(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, std::unique_lock &scheduler_lock, std::unique_lock &voice_lock) { // Merge all voices. This buss manually outputs 2 channels - if (data.voice_state_data.empty()) { - data.voice_state_data.resize(data.parent->rack->system->granularity * sizeof(std::uint16_t) * 2); - } - - std::fill(data.voice_state_data.begin(), data.voice_state_data.end(), 0); + std::fill(data.guest_state_data.begin(), data.guest_state_data.end(), 0); if (data.parent->inputs.inputs.empty()) { return false; } - int16_t *dest_data = reinterpret_cast(data.voice_state_data.data()); + int16_t *dest_data = reinterpret_cast(data.guest_state_data.data()); float *source_data = reinterpret_cast(data.parent->inputs.inputs[0].data()); // Convert FLTP to S16 diff --git a/vita3k/ngs/src/modules/player.cpp b/vita3k/ngs/src/modules/player.cpp index 009ec4db1..6403d07e5 100644 --- a/vita3k/ngs/src/modules/player.cpp +++ b/vita3k/ngs/src/modules/player.cpp @@ -18,42 +18,52 @@ #include #include -extern "C" { -#include -} - -#include +#include +#include #include namespace ngs { +std::unique_ptr PlayerModule::create_logical_state() const { + return std::make_unique(); +} + +std::unique_ptr PlayerModule::create_runtime_state() const { + return std::make_unique(); +} + void PlayerModule::on_state_change(const MemState &mem, ModuleData &data, const VoiceState previous) { SceNgsPlayerStates *state = data.get_state(); + PlayerLogicalState *logical = data.get_logical_state(); SceNgsPlayerParams *params = data.get_parameters(mem); + if (data.parent->state == VOICE_STATE_ACTIVE && previous == VOICE_STATE_AVAILABLE) { state->samples_generated_since_key_on = 0; state->bytes_consumed_since_key_on = 0; state->current_buffer = params->start_buffer; state->current_byte_position_in_buffer = params->start_bytes; - state->current_loop_count = 0; + logical->current_loop_count = 0; + logical->decoded_pcm.clear(); + logical->rate_resampler.reset(); + logical->adpcm_buffer.clear(); - memset(&state->adpcm_history, 0, sizeof(state->adpcm_history)); + std::memset(&logical->adpcm_history, 0, sizeof(logical->adpcm_history)); } else if (data.parent->is_keyed_off) { state->current_buffer = params->start_buffer; state->current_byte_position_in_buffer = params->start_bytes; - state->current_loop_count = 0; - state->reset_swr = true; + logical->current_loop_count = 0; + logical->rate_resampler.reset(); } } void PlayerModule::on_param_change(const MemState &mem, ModuleData &data) { - SceNgsPlayerStates *state = data.get_state(); + PlayerLogicalState *logical = data.get_logical_state(); const SceNgsPlayerParams *old_params = reinterpret_cast(data.last_info.data()); SceNgsPlayerParams *new_params = static_cast(data.info.data.get(mem)); // check for invalid playback values const auto is_invalid_playback_value = [](const float playback_value, const float max_value) { - return isnan(playback_value) || (playback_value < 0.f) || (playback_value > max_value); + return std::isnan(playback_value) || (playback_value < 0.f) || (playback_value > max_value); }; if (is_invalid_playback_value(new_params->playback_scalar, 10.f)) { @@ -72,283 +82,225 @@ void PlayerModule::on_param_change(const MemState &mem, ModuleData &data) { } } - // if playback scaling changed, reset the resampler + // if playback scaling changed, reset the rate resampler if (old_params->playback_frequency != new_params->playback_frequency || old_params->playback_scalar != new_params->playback_scalar) { ADPCMHistory hist_empty{}; - std::fill_n(state->adpcm_history, SCE_NGS_PLAYER_MAX_PCM_CHANNELS, hist_empty); + std::fill_n(logical->adpcm_history, SCE_NGS_PLAYER_MAX_PCM_CHANNELS, hist_empty); - state->reset_swr = true; + logical->rate_resampler.reset(); } } void PlayerModule::set_default_preset(const MemState &mem, ModuleData &data) { SceNgsPlayerStates *state = data.get_state(); + PlayerLogicalState *logical = data.get_logical_state(); + LOG_WARN_ONCE("Player reset state"); *state = {}; + *logical = {}; } bool PlayerModule::process(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, std::unique_lock &scheduler_lock, std::unique_lock &voice_lock) { SceNgsPlayerParams *params = data.get_parameters(mem); SceNgsPlayerStates *state = data.get_state(); + PlayerLogicalState *logical = data.get_logical_state(); + PlayerRuntimeState *runtime = data.get_runtime_state(); bool finished = false; const int32_t sample_rate = data.parent->rack->system->sample_rate; const int32_t granularity = data.parent->rack->system->granularity; // fix right now because it might be set by default in SceNgsVoiceInit, which we do not support - if (params->channels == 0) + if (params->channels == 0) { params->channels = 2; + } // If decoder hasn't been initialized - if (!decoder) { + if (!runtime->decoder) { // Create decoder specifying the desired destination sample rate - decoder = std::make_unique(static_cast(sample_rate)); + runtime->decoder = std::make_unique(static_cast(sample_rate)); } - // If the amount of samples already processed and pending to be passed is smaller than the amount of samples of the audio buffer - if (static_cast(state->decoded_samples_pending) < granularity) { - // Memory cleaning check - if (!data.extra_storage.empty()) { - // Delete data from previous processing if memory isn't empty - data.extra_storage.erase(data.extra_storage.begin(), data.extra_storage.begin() + state->decoded_samples_passed * 2 * sizeof(float)); - } + logical->decoded_pcm.compact(); - // Reset the passed samples count to 0 - state->decoded_samples_passed = 0; + while (static_cast(logical->decoded_pcm.available_frames()) < granularity) { + if ((state->current_buffer == -1) + || !params->buffer_params[state->current_buffer].buffer + || (params->buffer_params[state->current_buffer].bytes_count == 0)) { + // Stop processing if no valid buffer is available or if the buffer is empty + finished = true; + break; + } else if (state->current_byte_position_in_buffer >= params->buffer_params[state->current_buffer].bytes_count) { + const int32_t prev_index = state->current_buffer; + state->current_byte_position_in_buffer = 0; + logical->current_loop_count++; - while (static_cast(state->decoded_samples_pending) < granularity) { - // Ran out of data, supply new - // Decode new data and deliver them - // Let's open our context - if ((state->current_buffer == -1) - || !params->buffer_params[state->current_buffer].buffer - || (params->buffer_params[state->current_buffer].bytes_count == 0)) { - // Stop processing if no valid buffer is available or if the buffer is empty - finished = true; - break; - } - // If the current byte position in the buffer exceeds the total amount of bytes in the buffer - else if (state->current_byte_position_in_buffer >= params->buffer_params[state->current_buffer].bytes_count) { - const int32_t prev_index = state->current_buffer; - state->current_byte_position_in_buffer = 0; - state->current_loop_count++; + voice_lock.unlock(); + scheduler_lock.unlock(); - voice_lock.unlock(); - scheduler_lock.unlock(); + // Enable looping over the buffer if needed + if (params->buffer_params[state->current_buffer].loop_count != -1 + && logical->current_loop_count > params->buffer_params[state->current_buffer].loop_count) { + state->current_buffer = params->buffer_params[state->current_buffer].next_buffer_index; + logical->current_loop_count = 0; - // Enable looping over the buffer if needed - if (params->buffer_params[state->current_buffer].loop_count != -1 - && state->current_loop_count > params->buffer_params[state->current_buffer].loop_count) { - state->current_buffer = params->buffer_params[state->current_buffer].next_buffer_index; - state->current_loop_count = 0; + if ((state->current_buffer == -1) + || !params->buffer_params[state->current_buffer].buffer + || (params->buffer_params[state->current_buffer].bytes_count == 0)) { + data.invoke_callback(kern, mem, thread_id, SCE_NGS_PLAYER_END_OF_DATA, 0, 0); - if ((state->current_buffer == -1) - || !params->buffer_params[state->current_buffer].buffer - || (params->buffer_params[state->current_buffer].bytes_count == 0)) { - data.invoke_callback(kern, mem, thread_id, SCE_NGS_PLAYER_END_OF_DATA, 0, 0); - - // we are done - finished = true; - scheduler_lock.lock(); - voice_lock.lock(); - break; - } else { - data.invoke_callback(kern, mem, thread_id, SCE_NGS_PLAYER_SWAPPED_BUFFER, prev_index, - params->buffer_params[state->current_buffer].buffer.address()); - } + // we are done + finished = true; + scheduler_lock.lock(); + voice_lock.lock(); + break; } else { - data.invoke_callback(kern, mem, thread_id, SCE_NGS_PLAYER_LOOPED_BUFFER, state->current_loop_count, + data.invoke_callback(kern, mem, thread_id, SCE_NGS_PLAYER_SWAPPED_BUFFER, prev_index, params->buffer_params[state->current_buffer].buffer.address()); } - - scheduler_lock.lock(); - voice_lock.lock(); + } else { + data.invoke_callback(kern, mem, thread_id, SCE_NGS_PLAYER_LOOPED_BUFFER, logical->current_loop_count, + params->buffer_params[state->current_buffer].buffer.address()); } - if (data.extra_storage.size() < sizeof(float) * 2 * granularity - && state->current_buffer != -1 - && params->buffer_params[state->current_buffer].bytes_count != 0) { - // Set up decoder - decoder->source_channels = params->channels; - decoder->source_frequency = params->playback_frequency; - // Enable ADPCM mode on the decoder if needed, and restore state - decoder->he_adpcm = static_cast(params->type); - if (decoder->he_adpcm) { - std::copy_n(state->adpcm_history, decoder->source_channels, decoder->adpcm_history); + scheduler_lock.lock(); + voice_lock.lock(); + } + + runtime->decoder->source_channels = params->channels; + runtime->decoder->source_frequency = params->playback_frequency; + // Enable ADPCM mode on the decoder if needed, and restore state + runtime->decoder->he_adpcm = static_cast(params->type); + if (runtime->decoder->he_adpcm) { + std::copy_n(logical->adpcm_history, runtime->decoder->source_channels, runtime->decoder->adpcm_history); + } + + auto *input = params->buffer_params[state->current_buffer].buffer.cast().get(mem); + + DecoderSize samples_count; + // we need to know how many samples (not bytes!) we need to send (just enough for the system granularity) + uint32_t samples_needed = granularity - logical->decoded_pcm.available_frames(); + + if (params->playback_scalar != 1.0f) { + samples_needed = static_cast(samples_needed * params->playback_scalar) + 0x10; + } + if (static_cast(params->playback_frequency) != sample_rate) { + samples_needed = static_cast((samples_needed * params->playback_frequency) / sample_rate) + 0x10; + } + + // Convert samples count to actual bytes count that we need + uint32_t bytes_to_send; + if (runtime->decoder->he_adpcm) { + bytes_to_send = (samples_needed + 27) / 28 * params->channels * 16; + } else { + bytes_to_send = samples_needed * params->channels * sizeof(int16_t); + } + + // makes the value 4 bits aligned so we have no issue with decoding, adpcm or not and whether the sound is mono or stereo + bytes_to_send = std::min(bytes_to_send, params->buffer_params[state->current_buffer].bytes_count - state->current_byte_position_in_buffer); + + const uint8_t *chunk = input + state->current_byte_position_in_buffer; + bool decoded_new_audio = true; + uint32_t consumed_bytes = bytes_to_send; + + if (runtime->decoder->he_adpcm) { + const uint32_t carried_bytes = static_cast(logical->adpcm_buffer.size()); + const auto init_bytes_to_send = bytes_to_send; + const uint32_t frame_size = 0x10 * params->channels; + // We may have a partial HE-ADPCM frame carried over from the previous chunk, + // so combine staged bytes with the current input before deciding how much is decodable. + const auto combined_bytes = carried_bytes + bytes_to_send; + const uint32_t full_frames = combined_bytes / frame_size; + const uint32_t bytes_to_send_adpcm = full_frames * frame_size; + const uint32_t chunk_bytes_needed = (bytes_to_send_adpcm > carried_bytes) ? (bytes_to_send_adpcm - carried_bytes) : 0; + + // HE-ADPCM requires full frames (0x10 bytes per channel). + // We accumulate leftover bytes from previous chunks until we have enough to form one or more complete frames, then send them in a single block. + decoded_new_audio = (bytes_to_send_adpcm != 0); + if (carried_bytes == 0) { + // Case 1: No leftover -> frames can be sent directly from the input chunk + if (decoded_new_audio) { + runtime->decoder->send(chunk, bytes_to_send_adpcm); } + } else { + // Case 2: We have leftover -> complete the partial frame first + const size_t old_size = logical->adpcm_buffer.size(); + logical->adpcm_buffer.resize(old_size + init_bytes_to_send); + std::memcpy(logical->adpcm_buffer.data() + old_size, chunk, init_bytes_to_send); - // Get audio buffer - auto *input = params->buffer_params[state->current_buffer].buffer.cast().get(mem); - - DecoderSize samples_count; - // we need to know how many samples (not bytes!) we need to send (just enough for the system granularity) - uint32_t samples_needed = granularity - state->decoded_samples_pending; - - if (params->playback_scalar != 1.0f) { - samples_needed = static_cast(samples_needed * params->playback_scalar) + 0x10; - } - if (static_cast(params->playback_frequency) != sample_rate) { - samples_needed = static_cast((samples_needed * params->playback_frequency) / sample_rate) + 0x10; - } - - // Convert samples count to actual bytes count that we need - uint32_t bytes_to_send; - if (decoder->he_adpcm) { - bytes_to_send = (samples_needed + 27) / 28 * params->channels * 16; - } else { - bytes_to_send = samples_needed * params->channels * sizeof(int16_t); - } - - // makes the value 4 bits aligned so we have no issue with decoding, adpcm or not and whether the sound is mono or stereo - bytes_to_send = std::min(bytes_to_send, params->buffer_params[state->current_buffer].bytes_count - state->current_byte_position_in_buffer); - - const uint8_t *chunk = input + state->current_byte_position_in_buffer; - - // HE-ADPCM requires full frames (0x10 bytes per channel). - // We accumulate leftover bytes from previous chunks until we have enough to form one or more complete frames, then send them in a single block. - if (decoder->he_adpcm) { - // Number of bytes carried over from the previous iteration (partial frame) - uint32_t remaining_bytes = state->adpcm_buffer.size(); - - // Preserve the original chunk size (needed later to compute leftover correctly) - const auto init_bytes_to_send = bytes_to_send; - - // One HE-ADPCM frame = 0x10 bytes per channel - const uint32_t frame_size = 0x10 * params->channels; - - // Total bytes available for this iteration (leftover + current chunk) - const auto combined_bytes = remaining_bytes + bytes_to_send; - - // Number of complete frames we can output from combined_bytes - const uint32_t full_frames = combined_bytes / frame_size; - const uint32_t bytes_to_send_adpcm = full_frames * frame_size; - - // Number of bytes required from the current chunk to complete the frames - const auto chunk_bytes_needed = bytes_to_send_adpcm - remaining_bytes; - - // Case 1: No leftover -> frames can be sent directly from the input chunk - if (remaining_bytes == 0) { - decoder->send(chunk, bytes_to_send_adpcm); - } else { // Case 2: We have leftover -> complete the partial frame first - - // Resize buffer to hold exactly the full frames we will output - state->adpcm_buffer.resize(bytes_to_send_adpcm); - - // Copy only the bytes needed to complete the pending frame(s) - memcpy(state->adpcm_buffer.data() + remaining_bytes, chunk, chunk_bytes_needed); - - // Send the completed frames from the temporary buffer - if (full_frames > 0) - decoder->send(state->adpcm_buffer.data(), bytes_to_send_adpcm); - - // Update bytes_to_send to reflect only what was consumed from the current chunk - bytes_to_send = chunk_bytes_needed; - - // Clear temporary buffer for next iteration - state->adpcm_buffer.clear(); - } - - // Compute leftover bytes that do not form a complete frame (typically end-of-buffer) - remaining_bytes = init_bytes_to_send - bytes_to_send_adpcm; - state->adpcm_buffer.resize(remaining_bytes); - - // Store leftover bytes for the next iteration - if (remaining_bytes > 0) - memcpy(state->adpcm_buffer.data(), chunk + bytes_to_send_adpcm, remaining_bytes); - } else { - // PCM case, send directly - decoder->send(chunk, bytes_to_send); - } - - state->current_byte_position_in_buffer += bytes_to_send; - state->bytes_consumed_since_key_on += bytes_to_send; - state->total_bytes_consumed += bytes_to_send; - // save he_adpcm state - if (decoder->he_adpcm) - std::copy_n(decoder->adpcm_history, decoder->source_channels, state->adpcm_history); - - // Get the amount of samples about to be received from the decoder and dump the value in samples_count - decoder->receive(nullptr, &samples_count); - - // Playback rate scaling - float src_sample_rate = params->playback_frequency; - if ((src_sample_rate >= 1.f) && ((params->playback_scalar != 1.f) || (static_cast(src_sample_rate) != sample_rate))) { - LOG_INFO_ONCE("The currently running game requests playback rate scaling when decoding audio. Audio might crackle."); - - // Received decoded samples from decoder - std::vector decoded_data(samples_count.samples * sizeof(float) * 2, 0); - - // Receive the samples processed by the decoder - decoder->receive(decoded_data.data(), nullptr); - - // resample the audio - if (params->playback_scalar != 1.0f) - src_sample_rate *= params->playback_scalar; - - if (!state->swr || state->reset_swr) { - if (state->swr) - swr_free(&state->swr); - - AVChannelLayout layout_stereo = AV_CHANNEL_LAYOUT_STEREO; - int ret = swr_alloc_set_opts2(&state->swr, - &layout_stereo, AV_SAMPLE_FMT_FLT, sample_rate, - &layout_stereo, AV_SAMPLE_FMT_FLT, static_cast(src_sample_rate), - 0, nullptr); - assert(ret == 0); - - ret = swr_init(state->swr); - assert(ret == 0); - state->reset_swr = false; - } - int scaled_samples_amount = swr_get_out_samples(state->swr, samples_count.samples); - std::vector scaled_data(scaled_samples_amount * sizeof(float) * 2, 0); - - uint8_t *scaled_dest_data = scaled_data.data(); - const uint8_t *scaled_src_data = decoded_data.data(); - scaled_samples_amount = swr_convert(state->swr, &scaled_dest_data, scaled_samples_amount, &scaled_src_data, samples_count.samples); - assert(scaled_samples_amount > 0); - - // Get current size of audio queue for processed samples in memory - const uint32_t current_count = state->decoded_samples_pending * sizeof(float) * 2; - - // Allocate memory to accommodate the result of the scaling process into the queue for the final audio buffer - data.extra_storage.resize(current_count + scaled_samples_amount * sizeof(float) * 2); - - // Pass scaled audio data into the queue for the final audio buffer - memcpy(data.extra_storage.data() + current_count, scaled_data.data(), scaled_samples_amount * sizeof(float) * 2); - - } else { - // Get current size of audio buffer for processed samples in memory - const uint32_t current_count = state->decoded_samples_pending * sizeof(float) * 2; - - // Increase the size the audio buffer for processed samples to accommodate the new about-to-be-received samples - data.extra_storage.resize(current_count + samples_count.samples * sizeof(float) * 2); - - // Receive the samples processed by the decoder and append them to the buffer of already processed samples - decoder->receive(data.extra_storage.data() + current_count, nullptr); + if (decoded_new_audio) { + runtime->decoder->send(logical->adpcm_buffer.data(), bytes_to_send_adpcm); } } - uint32_t bytes_left_in_buffer = data.extra_storage.size(); - uint32_t samples_to_take_per_channel = bytes_left_in_buffer / sizeof(float) / 2; + // Compute leftover bytes that do not form a complete frame (typically end-of-buffer) + const uint32_t leftover_bytes = combined_bytes - bytes_to_send_adpcm; + logical->adpcm_buffer.resize(combined_bytes); + if (leftover_bytes > 0) { + if (carried_bytes == 0) { + std::memcpy(logical->adpcm_buffer.data(), chunk + chunk_bytes_needed, leftover_bytes); + } else if (bytes_to_send_adpcm > 0) { + // Keep only the undecoded tail so the next iteration can complete the frame + std::memmove(logical->adpcm_buffer.data(), logical->adpcm_buffer.data() + bytes_to_send_adpcm, leftover_bytes); + } + } + logical->adpcm_buffer.resize(leftover_bytes); - state->decoded_samples_pending = samples_to_take_per_channel; + consumed_bytes = init_bytes_to_send; + } else { + runtime->decoder->send(chunk, bytes_to_send); + } + + state->current_byte_position_in_buffer += consumed_bytes; + state->bytes_consumed_since_key_on += consumed_bytes; + state->total_bytes_consumed += consumed_bytes; + // save he_adpcm state + if (runtime->decoder->he_adpcm && decoded_new_audio) { + std::copy_n(runtime->decoder->adpcm_history, runtime->decoder->source_channels, logical->adpcm_history); + } + + if (!decoded_new_audio) { + continue; + } + + runtime->decoder->receive(nullptr, &samples_count); + + // Playback rate scaling + float src_sample_rate = params->playback_frequency; + if ((src_sample_rate >= 1.f) && ((params->playback_scalar != 1.f) || (static_cast(src_sample_rate) != sample_rate))) { + runtime->decoded_chunk.resize(static_cast(samples_count.samples) * sizeof(float) * 2); + runtime->decoder->receive(runtime->decoded_chunk.data(), nullptr); + + if (params->playback_scalar != 1.0f) { + src_sample_rate *= params->playback_scalar; + } + + ensure_stereo_rate_resampler(runtime->rate_resampler, logical->rate_resampler, + static_cast(src_sample_rate), sample_rate); + process_stereo_rate_resampler(runtime->rate_resampler, logical->rate_resampler, + runtime->decoded_chunk.data(), samples_count.samples, logical->decoded_pcm); + + } else { + uint8_t *decoded_dest = logical->decoded_pcm.append_uninitialized_bytes(samples_count.samples); + runtime->decoder->receive(decoded_dest, nullptr); } } - uint32_t samples_to_be_passed = std::min(state->decoded_samples_pending, granularity); + const uint32_t available_samples = logical->decoded_pcm.available_frames(); + const uint32_t samples_to_be_passed = std::min(available_samples, granularity); - auto const new_size = 2 * sizeof(float) * (state->decoded_samples_passed + granularity); - if (data.extra_storage.size() < new_size) { - data.extra_storage.resize(new_size); + if (available_samples >= static_cast(granularity)) { + data.parent->products[0].data = logical->decoded_pcm.read_bytes(); + } else { + data.ensure_scratch_size(static_cast(granularity) * sizeof(float) * 2); + std::fill(data.scratch_data.begin(), data.scratch_data.end(), 0); + if (available_samples > 0) { + std::memcpy(data.scratch_data.data(), logical->decoded_pcm.read_bytes(), static_cast(available_samples) * sizeof(float) * 2); + } + data.parent->products[0].data = data.scratch_data.data(); } - uint8_t *data_ptr = data.extra_storage.data(); - data_ptr += 2 * sizeof(float) * state->decoded_samples_passed; - data.parent->products[0].data = data_ptr; - - state->decoded_samples_pending -= samples_to_be_passed; - state->decoded_samples_passed += samples_to_be_passed; + logical->decoded_pcm.consume_frames(samples_to_be_passed); state->samples_generated_since_key_on += samples_to_be_passed * params->channels; state->samples_generated_total += samples_to_be_passed * params->channels; @@ -356,10 +308,10 @@ bool PlayerModule::process(KernelState &kern, const MemState &mem, const SceUID } void PlayerModule::cleanup_voice_state(ModuleData &data) { - SceNgsPlayerStates *state = data.get_state(); - if (state->swr) { - swr_free(&state->swr); + if (auto *runtime = static_cast(data.runtime_state.get())) { + destroy_stereo_rate_resampler(runtime->rate_resampler); } + data.runtime_state.reset(); } } // namespace ngs diff --git a/vita3k/ngs/src/ngs.cpp b/vita3k/ngs/src/ngs.cpp index 8191368f9..e257b3e04 100644 --- a/vita3k/ngs/src/ngs.cpp +++ b/vita3k/ngs/src/ngs.cpp @@ -30,12 +30,116 @@ Rack::Rack(System *mama, const Ptr memspace, const uint32_t memspace_size) : MempoolObject(memspace, memspace_size) , system(mama) {} +int32_t Rack::index_of_voice(const MemState &mem, const Voice *voice) const { + for (size_t i = 0; i < voices.size(); i++) { + if (voices[i].get(mem) == voice) { + return static_cast(i); + } + } + + return -1; +} + System::System(const Ptr memspace, const uint32_t memspace_size) : MempoolObject(memspace, memspace_size) , max_voices(0) , granularity(0) , sample_rate(0) {} +int32_t System::index_of_rack(const Rack *rack) const { + for (size_t i = 0; i < racks.size(); i++) { + if (racks[i] == rack) { + return static_cast(i); + } + } + + return -1; +} + +Voice *System::find_voice(const MemState &mem, const VoiceAddress &address) const { + if (!address) { + return nullptr; + } + + if (address.rack_index < 0 || address.rack_index >= static_cast(racks.size())) { + return nullptr; + } + + const Rack *rack = racks[address.rack_index]; + if (!rack) { + return nullptr; + } + + if (address.voice_index < 0 || address.voice_index >= static_cast(rack->voices.size())) { + return nullptr; + } + + return rack->voices[address.voice_index].get(mem); +} + +VoiceAddress System::address_of(const MemState &mem, const Voice *voice) const { + if (!voice || !voice->rack) { + return {}; + } + + VoiceAddress result; + result.rack_index = index_of_rack(voice->rack); + if (result.rack_index < 0) { + return {}; + } + + result.voice_index = voice->rack->index_of_voice(mem, voice); + if (result.voice_index < 0) { + return {}; + } + + return result; +} + +bool Patch::is_active() const { + return output_sub_index != -1; +} + +Voice *Patch::resolve_source(const MemState &mem) const { + if (system && source_address) { + return system->find_voice(mem, source_address); + } + + return source; +} + +Voice *Patch::resolve_dest(const MemState &mem) const { + if (system && dest_address) { + return system->find_voice(mem, dest_address); + } + + return dest; +} + +void Patch::refresh_endpoints(const MemState &mem) { + if (!system) { + if (source && source->rack) { + system = source->rack->system; + } else if (dest && dest->rack) { + system = dest->rack->system; + } + } + + if (source) { + source_address = source->address(mem); + } + if (system && source_address) { + source = system->find_voice(mem, source_address); + } + + if (dest) { + dest_address = dest->address(mem); + } + if (system && dest_address) { + dest = system->find_voice(mem, dest_address); + } +} + void VoiceInputManager::init(const uint32_t granularity, const uint16_t total_input) { inputs.resize(total_input); @@ -61,7 +165,14 @@ VoiceInputManager::PCMInput *VoiceInputManager::get_input_buffer_queue(const int return &inputs[index]; } -int32_t VoiceInputManager::receive(ngs::Patch *patch, const VoiceProduct &product) { +int32_t VoiceInputManager::receive(const MemState &mem, ngs::Patch *patch, const VoiceProduct &product) { + patch->refresh_endpoints(mem); + Voice *source = patch->resolve_source(mem); + Voice *dest = patch->resolve_dest(mem); + if (!source || !dest) { + return -1; + } + PCMInput *input = get_input_buffer_queue(patch->dest_index); if (!input) { @@ -75,19 +186,19 @@ int32_t VoiceInputManager::receive(ngs::Patch *patch, const VoiceProduct &produc memcpy(volume_matrix, patch->volume_matrix, sizeof(volume_matrix)); // we always use stereo internally, so make sure not to add too many channels - if (patch->source->rack->channels_per_voice == 1) { + if (source->rack->channels_per_voice == 1) { volume_matrix[1][0] = 0.0f; volume_matrix[1][1] = 0.0f; } - if (patch->dest->rack->channels_per_voice == 1) { + if (dest->rack->channels_per_voice == 1) { volume_matrix[0][1] = 0.0f; volume_matrix[1][1] = 0.0f; } // Try mixing, also with the use of this volume matrix // Dest is our voice to receive this data. - for (int32_t k = 0; k < patch->dest->rack->system->granularity; k++) { + for (int32_t k = 0; k < dest->rack->system->granularity; k++) { dest_buffer[k * 2] = std::clamp(dest_buffer[k * 2] + data_to_mix_in[k * 2] * volume_matrix[0][0] + data_to_mix_in[k * 2 + 1] * volume_matrix[1][0], -1.0f, 1.0f); @@ -140,16 +251,6 @@ void ModuleData::invoke_callback(KernelState &kernel, const MemState &mem, const reason1, reason2, reason_ptr); } -void ModuleData::fill_to_fit_granularity() { - const int start_fill = extra_storage.size(); - const int to_fill = parent->rack->system->granularity * 2 * sizeof(float) - start_fill; - - if (to_fill > 0) { - extra_storage.resize(start_fill + to_fill); - std::fill(extra_storage.begin() + start_fill, extra_storage.end(), 0); - } -} - void Voice::init(Rack *mama) { rack = mama; state = VoiceState::VOICE_STATE_AVAILABLE; @@ -203,8 +304,11 @@ Ptr Voice::patch(const MemState &mem, const int32_t index, int32_t subind patch->output_sub_index = subindex; patch->output_index = index; patch->dest_index = dest_index; + patch->system = rack->system; patch->dest = dest; patch->source = this; + patch->dest_address = dest ? dest->address(mem) : VoiceAddress(); + patch->source_address = address(mem); // Initialize the matrix memset(patch->volume_matrix, 0, sizeof(patch->volume_matrix)); @@ -338,6 +442,10 @@ void Voice::invoke_callback(KernelState &kernel, const MemState &mem, const SceU stack_free(*thread->cpu, sizeof(SceNgsCallbackInfo)); } +VoiceAddress Voice::address(const MemState &mem) const { + return rack->system->address_of(mem, this); +} + uint32_t System::get_required_memspace_size(SceNgsSystemInitParams *parameters) { return sizeof(System); } @@ -449,6 +557,7 @@ bool init_rack(State &ngs, const MemState &mem, System *system, SceNgsBufferInfo v->datas[i].parent = v; v->datas[i].index = static_cast(i); + rack->modules[i]->initialize_voice_data(v->datas[i]); } } diff --git a/vita3k/ngs/src/rate_resampler.cpp b/vita3k/ngs/src/rate_resampler.cpp new file mode 100644 index 000000000..fed99e855 --- /dev/null +++ b/vita3k/ngs/src/rate_resampler.cpp @@ -0,0 +1,195 @@ +// Vita3K emulator project +// Copyright (C) 2026 Vita3K team +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include +#include + +extern "C" { +#include +#include +#include +} + +#include +#include + +namespace ngs { +namespace { +constexpr uint32_t stereo_channels = 2; +constexpr uint32_t history_safety_margin_frames = 128; +constexpr uint32_t history_compact_threshold_frames = 1024; + +bool create_stereo_rate_resampler(StereoRateResamplerRuntimeState &runtime, const int source_rate, + const int dest_rate) { + AVChannelLayout stereo = AV_CHANNEL_LAYOUT_STEREO; + const int alloc_result = swr_alloc_set_opts2(&runtime.context, + &stereo, AV_SAMPLE_FMT_FLT, dest_rate, + &stereo, AV_SAMPLE_FMT_FLT, source_rate, + 0, nullptr); + if (alloc_result < 0) { + LOG_ERROR("Failed to allocate stereo rate SwrContext for {} -> {} Hz (error {}).", + source_rate, dest_rate, alloc_result); + runtime.context = nullptr; + return false; + } + + const int init_result = swr_init(runtime.context); + if (init_result < 0) { + LOG_ERROR("Failed to initialize stereo rate SwrContext for {} -> {} Hz (error {}).", + source_rate, dest_rate, init_result); + swr_free(&runtime.context); + return false; + } + + runtime.source_rate = source_rate; + runtime.dest_rate = dest_rate; + return true; +} + +void compact_history_if_needed(StereoRateResamplerLogicalState &logical) { + if (logical.input_history.read_offset_frames == 0) { + return; + } + + const uint32_t total_frames = logical.input_history.total_frames(); + if (logical.input_history.read_offset_frames >= history_compact_threshold_frames + || logical.input_history.read_offset_frames >= (total_frames / 2)) { + logical.input_history.compact(); + } +} + +void trim_history(StereoRateResamplerRuntimeState &runtime, StereoRateResamplerLogicalState &logical) { + if (!runtime.context) { + logical.input_history.clear(); + return; + } + + const int64_t delay = swr_get_delay(runtime.context, runtime.source_rate); + const uint32_t keep_frames = static_cast(std::max(delay, 0)) + history_safety_margin_frames; + const uint32_t available_frames = logical.input_history.available_frames(); + + if (available_frames > keep_frames) { + logical.input_history.consume_frames(available_frames - keep_frames); + compact_history_if_needed(logical); + } +} + +bool replay_history(StereoRateResamplerRuntimeState &runtime, StereoRateResamplerLogicalState &logical) { + logical.input_history.compact(); + + const uint32_t history_frames = logical.input_history.available_frames(); + if (history_frames == 0) { + return true; + } + + const int out_samples = swr_get_out_samples(runtime.context, static_cast(history_frames)); + if (out_samples < 0) { + LOG_ERROR("Failed to query stereo rate resampler replay output size for {} history frames (error {}).", + history_frames, out_samples); + return false; + } + + runtime.scratch_buffer.resize(static_cast(out_samples) * sizeof(float) * stereo_channels); + + uint8_t *discard_output = runtime.scratch_buffer.empty() ? nullptr : runtime.scratch_buffer.data(); + const uint8_t *history_input = logical.input_history.read_bytes(); + const int result = swr_convert(runtime.context, &discard_output, out_samples, &history_input, + static_cast(history_frames)); + if (result < 0) { + LOG_ERROR("Failed to replay {} history frames into stereo rate resampler (error {}).", + history_frames, result); + return false; + } + return true; +} +} // namespace + +void destroy_stereo_rate_resampler(StereoRateResamplerRuntimeState &runtime) { + if (runtime.context) { + swr_free(&runtime.context); + } + + runtime.source_rate = 0; + runtime.dest_rate = 0; + runtime.scratch_buffer.clear(); +} + +bool ensure_stereo_rate_resampler(StereoRateResamplerRuntimeState &runtime, StereoRateResamplerLogicalState &logical, + const int source_rate, const int dest_rate) { + if (source_rate <= 0 || dest_rate <= 0) { + LOG_ERROR("Invalid stereo rate resampler configuration {} -> {} Hz.", source_rate, dest_rate); + return false; + } + + const bool needs_recreate = logical.needs_reset || !runtime.context || runtime.source_rate != source_rate + || runtime.dest_rate != dest_rate; + + if (!needs_recreate) { + return true; + } + + destroy_stereo_rate_resampler(runtime); + if (!create_stereo_rate_resampler(runtime, source_rate, dest_rate)) { + return false; + } + + if (!replay_history(runtime, logical)) { + destroy_stereo_rate_resampler(runtime); + return false; + } + + logical.needs_reset = false; + return true; +} + +uint32_t process_stereo_rate_resampler(StereoRateResamplerRuntimeState &runtime, StereoRateResamplerLogicalState &logical, + const uint8_t *input, const uint32_t input_frames, PCMFrameQueue &output) { + if (!input || input_frames == 0 || !runtime.context) { + return 0; + } + + const int out_samples = swr_get_out_samples(runtime.context, static_cast(input_frames)); + if (out_samples < 0) { + LOG_ERROR("Failed to query stereo rate resampler output size for {} input frames (error {}).", + input_frames, out_samples); + return 0; + } + + const size_t old_samples = output.samples.size(); + output.samples.resize(old_samples + static_cast(out_samples) * stereo_channels); + + uint8_t *output_bytes = reinterpret_cast(output.samples.data() + old_samples); + const uint8_t *input_bytes = input; + const int produced_samples = swr_convert(runtime.context, &output_bytes, out_samples, &input_bytes, + static_cast(input_frames)); + + if (produced_samples < 0) { + LOG_ERROR("Stereo rate resampler failed while converting {} input frames (error {}).", + input_frames, produced_samples); + output.samples.resize(old_samples); + return 0; + } + + output.samples.resize(old_samples + static_cast(produced_samples) * stereo_channels); + + logical.input_history.append_bytes(input, input_frames); + trim_history(runtime, logical); + + return static_cast(produced_samples); +} + +} // namespace ngs diff --git a/vita3k/ngs/src/route.cpp b/vita3k/ngs/src/route.cpp index c3c5694c7..dfb7ce75a 100644 --- a/vita3k/ngs/src/route.cpp +++ b/vita3k/ngs/src/route.cpp @@ -29,14 +29,16 @@ bool deliver_data(const MemState &mem, const std::vector &voice_queue, for (auto &patch_ptr : source->patches[output_port]) { Patch *patch = patch_ptr.get(mem); - if (!patch || patch->output_sub_index == -1) + if (!patch || !patch->is_active()) continue; - if (!vector_utils::contains(voice_queue, patch->dest)) + patch->refresh_endpoints(mem); + Voice *dest = patch->resolve_dest(mem); + if (!dest || !vector_utils::contains(voice_queue, dest)) continue; - const std::lock_guard guard(*patch->dest->voice_mutex); - patch->dest->inputs.receive(patch, data_to_deliver); + const std::lock_guard guard(*dest->voice_mutex); + dest->inputs.receive(mem, patch, data_to_deliver); } return true; diff --git a/vita3k/ngs/src/scheduler.cpp b/vita3k/ngs/src/scheduler.cpp index cff201aa8..5f0f40019 100644 --- a/vita3k/ngs/src/scheduler.cpp +++ b/vita3k/ngs/src/scheduler.cpp @@ -43,7 +43,11 @@ void VoiceScheduler::deque_insert(const MemState &mem, Voice *voice) { continue; } - Voice *dest = patch.get(mem)->dest; + patch.get(mem)->refresh_endpoints(mem); + Voice *dest = patch.get(mem)->resolve_dest(mem); + if (!dest) { + continue; + } const int32_t pos = get_position(dest); if (pos == -1) { @@ -204,11 +208,15 @@ bool VoiceScheduler::resort_to_respect_dependencies(const MemState &mem, Voice * // Check all dependencies, could be optimized- @sunho suggested dfs topological sort for (size_t i = 0; i < source->patches.size(); i++) { for (const auto &patch : source->patches[i]) { - if (!patch || patch.get(mem)->output_sub_index == -1) { + if (!patch || !patch.get(mem)->is_active()) { continue; } - Voice *dest = patch.get(mem)->dest; + patch.get(mem)->refresh_endpoints(mem); + Voice *dest = patch.get(mem)->resolve_dest(mem); + if (!dest) { + continue; + } const int32_t dest_pos = get_position(dest); if (dest_pos == -1) {