ngs: refactor ngs/audio state ownership & some cleanup

This commit is contained in:
KorewaWatchful
2026-05-25 21:34:06 -04:00
committed by Gamid
parent 8b363cd37a
commit b718baa002
14 changed files with 923 additions and 427 deletions
+15 -6
View File
@@ -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<ngs::Voice>(patch->source, emuenv.mem);
deli_info->dest_voice_handle = Ptr<ngs::Voice>(patch->dest, emuenv.mem);
deli_info->source_voice_handle = Ptr<ngs::Voice>(source, emuenv.mem);
deli_info->dest_voice_handle = Ptr<ngs::Voice>(dest, emuenv.mem);
}
return SCE_NGS_OK;
@@ -245,7 +252,9 @@ EXPORT(int, sceNgsPatchRemoveRouting, Ptr<ngs::Patch> 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<std::size_t>(mem_size, storage->voice_state_data.size()));
memcpy(mem, storage->guest_state_data.data(), std::min<std::size_t>(mem_size, storage->guest_state_data.size()));
}
return SCE_NGS_OK;
+1
View File
@@ -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)
+26 -16
View File
@@ -17,6 +17,7 @@
#pragma once
#include <ngs/rate_resampler.h>
#include <ngs/system.h>
#include <ngs/types.h>
@@ -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<uint8_t> 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<Atrac9DecoderState> decoder;
StereoRateResamplerRuntimeState rate_resampler;
std::vector<uint8_t> decoded_superframe_samples;
std::vector<uint8_t> temporary_bytes;
};
class Atrac9Module : public Module {
private:
std::unique_ptr<Atrac9DecoderState> decoder;
uint32_t last_config = 0;
std::vector<uint8_t> 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<std::recursive_mutex> &scheduler_lock, std::unique_lock<std::mutex> &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<std::recursive_mutex> &scheduler_lock, std::unique_lock<std::mutex> &voice_lock);
public:
bool process(KernelState &kern, const MemState &mem, const SceUID thread_id, ModuleData &data, std::unique_lock<std::recursive_mutex> &scheduler_lock, std::unique_lock<std::mutex> &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<ModuleLogicalState> create_logical_state() const override;
std::unique_ptr<ModuleRuntimeState> 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;
+1
View File
@@ -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<std::recursive_mutex> &scheduler_lock, std::unique_lock<std::mutex> &voice_lock) override;
static constexpr uint32_t get_max_parameter_size() {
+21 -16
View File
@@ -17,6 +17,7 @@
#pragma once
#include <codec/state.h>
#include <ngs/rate_resampler.h>
#include <ngs/system.h>
#include <ngs/types.h>
@@ -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<uint8_t> 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<PCMDecoderState> decoder;
struct PlayerLogicalState : public ModuleLogicalState {
PCMFrameQueue decoded_pcm;
// preserve enough recent resampler input to rebuild the swresample state later
StereoRateResamplerLogicalState rate_resampler;
std::vector<uint8_t> 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<PCMDecoderState> decoder;
StereoRateResamplerRuntimeState rate_resampler;
std::vector<uint8_t> 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<std::recursive_mutex> &scheduler_lock, std::unique_lock<std::mutex> &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<ModuleLogicalState> create_logical_state() const override;
std::unique_ptr<ModuleRuntimeState> 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;
+54
View File
@@ -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 <ngs/system.h>
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<uint8_t> 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
+164 -9
View File
@@ -24,8 +24,12 @@
#include <ngs/types.h>
#include <util/types.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <mutex>
#include <vector>
@@ -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<float> 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<uint32_t>(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<std::ptrdiff_t>(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<size_t>(frames) * 2);
return reinterpret_cast<uint8_t *>(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<size_t>(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<uint8_t *>(samples.data() + static_cast<size_t>(read_offset_frames) * 2);
}
void ensure_available_frames(const uint32_t frames) {
const size_t required_samples = static_cast<size_t>(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<uint8_t> voice_state_data; ///< Voice state.
std::vector<uint8_t> extra_storage; ///< Local data storage for module.
std::vector<uint8_t> guest_state_data; ///< guest-visible voice state
std::vector<uint8_t> scratch_data; ///< temp local data storage for module
std::unique_ptr<ModuleLogicalState> logical_state; ///< non-guest state needed to resume processing later
std::unique_ptr<ModuleRuntimeState> runtime_state; ///< host runtime objects rebuilt from logical state
SceNgsBufferInfo info;
std::vector<uint8_t> last_info;
@@ -82,12 +192,30 @@ struct ModuleData {
template <typename T>
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<T *>(&voice_state_data[0]);
return reinterpret_cast<T *>(&guest_state_data[0]);
}
template <typename T>
T *get_logical_state() {
if (!logical_state) {
logical_state = std::make_unique<T>();
}
return static_cast<T *>(logical_state.get());
}
template <typename T>
T *get_runtime_state() {
if (!runtime_state) {
runtime_state = std::make_unique<T>();
}
return static_cast<T *>(runtime_state.get());
}
template <typename T>
@@ -100,13 +228,17 @@ struct ModuleData {
return info.data.cast<T>().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<std::recursive_mutex> &scheduler_lock, std::unique_lock<std::mutex> &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<ModuleLogicalState> create_logical_state() const { return nullptr; }
virtual std::unique_ptr<ModuleRuntimeState> 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<void> callback, Ptr<void> 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<void> 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<void> 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);
};
+105 -111
View File
@@ -22,65 +22,72 @@ extern "C" {
#include <libswresample/swresample.h>
}
#include <cassert>
#include <cmath>
#include <cstring>
namespace ngs {
SwrContext *Atrac9Module::swr_mono_to_stereo = nullptr;
SwrContext *Atrac9Module::swr_stereo = nullptr;
std::unique_ptr<ModuleLogicalState> Atrac9Module::create_logical_state() const {
return std::make_unique<Atrac9LogicalState>();
}
std::unique_ptr<ModuleRuntimeState> Atrac9Module::create_runtime_state() const {
return std::make_unique<Atrac9RuntimeState>();
}
void Atrac9Module::on_state_change(const MemState &mem, ModuleData &data, const VoiceState previous) {
SceNgsAT9States *state = data.get_state<SceNgsAT9States>();
Atrac9LogicalState *logical = data.get_logical_state<Atrac9LogicalState>();
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<SceNgsAT9States>();
Atrac9LogicalState *logical = data.get_logical_state<Atrac9LogicalState>();
const SceNgsAT9Params *old_params = reinterpret_cast<SceNgsAT9Params *>(data.last_info.data());
const SceNgsAT9Params *new_params = static_cast<SceNgsAT9Params *>(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<std::recursive_mutex> &scheduler_lock, std::unique_lock<std::mutex> &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<std::recursive_mutex> &scheduler_lock, std::unique_lock<std::mutex> &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<Atrac9DecoderState>(params->config_data);
last_config = params->config_data;
if (!runtime->decoder || config_changed) {
runtime->decoder = std::make_unique<Atrac9DecoderState>(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<uint8_t>().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<uint32_t>(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<uint32_t>(frame_bytes_gotten, superframe_size - static_cast<uint32_t>(logical->superframe_staging.size()));
uint32_t old_size = static_cast<uint32_t>(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<int32_t *>(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<uint32_t>(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<uint32_t>(bufparam.samples_discard_end_off - samples_left_after));
}
}
std::vector<uint8_t> decoded_superframe_samples(decoder->get(DecoderQuery::AT9_SAMPLE_PER_SUPERFRAME) * sizeof(float) * 2);
runtime->decoded_superframe_samples.resize(static_cast<size_t>(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<uint8_t> 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<size_t>(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<int>(round(params->playback_frequency)) != sample_rate) {
if (params->playback_scalar != 1 || static_cast<int>(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<int>(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<uint8_t> 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<int>(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<std::recursive_mutex> &scheduler_lock, std::unique_lock<std::mutex> &voice_lock) {
const SceNgsAT9Params *params = data.get_parameters<SceNgsAT9Params>(mem);
SceNgsAT9States *state = data.get_state<SceNgsAT9States>();
Atrac9LogicalState *logical = data.get_logical_state<Atrac9LogicalState>();
Atrac9RuntimeState *runtime = data.get_runtime_state<Atrac9RuntimeState>();
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<int32_t>(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<int32_t>(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<uint32_t>(data.parent->rack->system->granularity);
const uint32_t available_samples = logical->decoded_pcm.available_frames();
const uint32_t samples_to_be_passed = std::min<uint32_t>(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<size_t>(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<size_t>(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<SceNgsAT9States>();
if (state->swr) {
swr_free(&state->swr);
if (auto *runtime = static_cast<Atrac9RuntimeState *>(data.runtime_state.get())) {
destroy_stereo_rate_resampler(runtime->rate_resampler);
}
data.runtime_state.reset();
}
} // namespace ngs
+7 -6
View File
@@ -20,19 +20,20 @@
#include <algorithm>
namespace ngs {
void OutputModule::initialize_voice_data(ModuleData &data) const {
Module::initialize_voice_data(data);
data.guest_state_data.resize(static_cast<size_t>(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<std::recursive_mutex> &scheduler_lock, std::unique_lock<std::mutex> &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<std::int16_t *>(data.voice_state_data.data());
int16_t *dest_data = reinterpret_cast<std::int16_t *>(data.guest_state_data.data());
float *source_data = reinterpret_cast<float *>(data.parent->inputs.inputs[0].data());
// Convert FLTP to S16
+194 -242
View File
@@ -18,42 +18,52 @@
#include <ngs/modules/player.h>
#include <util/log.h>
extern "C" {
#include <libswresample/swresample.h>
}
#include <cassert>
#include <algorithm>
#include <cmath>
#include <cstring>
namespace ngs {
std::unique_ptr<ModuleLogicalState> PlayerModule::create_logical_state() const {
return std::make_unique<PlayerLogicalState>();
}
std::unique_ptr<ModuleRuntimeState> PlayerModule::create_runtime_state() const {
return std::make_unique<PlayerRuntimeState>();
}
void PlayerModule::on_state_change(const MemState &mem, ModuleData &data, const VoiceState previous) {
SceNgsPlayerStates *state = data.get_state<SceNgsPlayerStates>();
PlayerLogicalState *logical = data.get_logical_state<PlayerLogicalState>();
SceNgsPlayerParams *params = data.get_parameters<SceNgsPlayerParams>(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<SceNgsPlayerStates>();
PlayerLogicalState *logical = data.get_logical_state<PlayerLogicalState>();
const SceNgsPlayerParams *old_params = reinterpret_cast<SceNgsPlayerParams *>(data.last_info.data());
SceNgsPlayerParams *new_params = static_cast<SceNgsPlayerParams *>(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<SceNgsPlayerStates>();
PlayerLogicalState *logical = data.get_logical_state<PlayerLogicalState>();
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<std::recursive_mutex> &scheduler_lock, std::unique_lock<std::mutex> &voice_lock) {
SceNgsPlayerParams *params = data.get_parameters<SceNgsPlayerParams>(mem);
SceNgsPlayerStates *state = data.get_state<SceNgsPlayerStates>();
PlayerLogicalState *logical = data.get_logical_state<PlayerLogicalState>();
PlayerRuntimeState *runtime = data.get_runtime_state<PlayerRuntimeState>();
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<PCMDecoderState>(static_cast<float>(sample_rate));
runtime->decoder = std::make_unique<PCMDecoderState>(static_cast<float>(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<int>(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<int>(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<int>(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<bool>(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<bool>(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<uint8_t>().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<uint32_t>(samples_needed * params->playback_scalar) + 0x10;
}
if (static_cast<int>(params->playback_frequency) != sample_rate) {
samples_needed = static_cast<uint32_t>((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<uint32_t>(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<uint32_t>(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<uint8_t>().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<uint32_t>(samples_needed * params->playback_scalar) + 0x10;
}
if (static_cast<int>(params->playback_frequency) != sample_rate) {
samples_needed = static_cast<uint32_t>((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<uint32_t>(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<int>(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<uint8_t> 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<int>(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<uint8_t> 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<int>(src_sample_rate) != sample_rate))) {
runtime->decoded_chunk.resize(static_cast<size_t>(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<int>(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<uint32_t>(state->decoded_samples_pending, granularity);
const uint32_t available_samples = logical->decoded_pcm.available_frames();
const uint32_t samples_to_be_passed = std::min<uint32_t>(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<uint32_t>(granularity)) {
data.parent->products[0].data = logical->decoded_pcm.read_bytes();
} else {
data.ensure_scratch_size(static_cast<size_t>(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<size_t>(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<SceNgsPlayerStates>();
if (state->swr) {
swr_free(&state->swr);
if (auto *runtime = static_cast<PlayerRuntimeState *>(data.runtime_state.get())) {
destroy_stereo_rate_resampler(runtime->rate_resampler);
}
data.runtime_state.reset();
}
} // namespace ngs
+123 -14
View File
@@ -30,12 +30,116 @@ Rack::Rack(System *mama, const Ptr<void> 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<int32_t>(i);
}
}
return -1;
}
System::System(const Ptr<void> 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<int32_t>(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<int32_t>(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<int32_t>(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<Patch> 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<uint32_t>(i);
rack->modules[i]->initialize_voice_data(v->datas[i]);
}
}
+195
View File
@@ -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 <ngs/rate_resampler.h>
#include <util/log.h>
extern "C" {
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>
}
#include <algorithm>
#include <cstdint>
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<uint32_t>(std::max<int64_t>(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<int>(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<size_t>(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<int>(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<int>(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<size_t>(out_samples) * stereo_channels);
uint8_t *output_bytes = reinterpret_cast<uint8_t *>(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<int>(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<size_t>(produced_samples) * stereo_channels);
logical.input_history.append_bytes(input, input_frames);
trim_history(runtime, logical);
return static_cast<uint32_t>(produced_samples);
}
} // namespace ngs
+6 -4
View File
@@ -29,14 +29,16 @@ bool deliver_data(const MemState &mem, const std::vector<Voice *> &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<std::mutex> guard(*patch->dest->voice_mutex);
patch->dest->inputs.receive(patch, data_to_deliver);
const std::lock_guard<std::mutex> guard(*dest->voice_mutex);
dest->inputs.receive(mem, patch, data_to_deliver);
}
return true;
+11 -3
View File
@@ -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) {