mirror of
https://github.com/Vita3K/Vita3K.git
synced 2026-07-11 01:34:23 +02:00
ngs: refactor ngs/audio state ownership & some cleanup
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+120
-168
@@ -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,75 +82,67 @@ 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>(state->decoded_samples_pending) < granularity) {
|
||||
// Ran out of data, supply new
|
||||
// Decode new data and deliver them
|
||||
// Let's open our context
|
||||
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;
|
||||
}
|
||||
// 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) {
|
||||
} 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++;
|
||||
logical->current_loop_count++;
|
||||
|
||||
voice_lock.unlock();
|
||||
scheduler_lock.unlock();
|
||||
|
||||
// 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) {
|
||||
&& logical->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;
|
||||
logical->current_loop_count = 0;
|
||||
|
||||
if ((state->current_buffer == -1)
|
||||
|| !params->buffer_params[state->current_buffer].buffer
|
||||
@@ -157,7 +159,7 @@ bool PlayerModule::process(KernelState &kern, const MemState &mem, const SceUID
|
||||
params->buffer_params[state->current_buffer].buffer.address());
|
||||
}
|
||||
} 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_LOOPED_BUFFER, logical->current_loop_count,
|
||||
params->buffer_params[state->current_buffer].buffer.address());
|
||||
}
|
||||
|
||||
@@ -165,24 +167,19 @@ bool PlayerModule::process(KernelState &kern, const MemState &mem, const SceUID
|
||||
voice_lock.lock();
|
||||
}
|
||||
|
||||
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;
|
||||
runtime->decoder->source_channels = params->channels;
|
||||
runtime->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);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
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;
|
||||
@@ -193,7 +190,7 @@ bool PlayerModule::process(KernelState &kern, const MemState &mem, const SceUID
|
||||
|
||||
// Convert samples count to actual bytes count that we need
|
||||
uint32_t bytes_to_send;
|
||||
if (decoder->he_adpcm) {
|
||||
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);
|
||||
@@ -203,152 +200,107 @@ bool PlayerModule::process(KernelState &kern, const MemState &mem, const SceUID
|
||||
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.
|
||||
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;
|
||||
|
||||
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 (remaining_bytes == 0) {
|
||||
decoder->send(chunk, bytes_to_send_adpcm);
|
||||
} else { // Case 2: We have leftover -> complete the partial frame first
|
||||
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);
|
||||
|
||||
// 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();
|
||||
if (decoded_new_audio) {
|
||||
runtime->decoder->send(logical->adpcm_buffer.data(), bytes_to_send_adpcm);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
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);
|
||||
|
||||
// Store leftover bytes for the next iteration
|
||||
if (remaining_bytes > 0)
|
||||
memcpy(state->adpcm_buffer.data(), chunk + bytes_to_send_adpcm, remaining_bytes);
|
||||
consumed_bytes = init_bytes_to_send;
|
||||
} else {
|
||||
// PCM case, send directly
|
||||
decoder->send(chunk, bytes_to_send);
|
||||
runtime->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;
|
||||
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 (decoder->he_adpcm)
|
||||
std::copy_n(decoder->adpcm_history, decoder->source_channels, state->adpcm_history);
|
||||
if (runtime->decoder->he_adpcm && decoded_new_audio) {
|
||||
std::copy_n(runtime->decoder->adpcm_history, runtime->decoder->source_channels, logical->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);
|
||||
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))) {
|
||||
LOG_INFO_ONCE("The currently running game requests playback rate scaling when decoding audio. Audio might crackle.");
|
||||
runtime->decoded_chunk.resize(static_cast<size_t>(samples_count.samples) * sizeof(float) * 2);
|
||||
runtime->decoder->receive(runtime->decoded_chunk.data(), nullptr);
|
||||
|
||||
// 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)
|
||||
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);
|
||||
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 {
|
||||
// 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);
|
||||
uint8_t *decoded_dest = logical->decoded_pcm.append_uninitialized_bytes(samples_count.samples);
|
||||
runtime->decoder->receive(decoded_dest, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t bytes_left_in_buffer = data.extra_storage.size();
|
||||
uint32_t samples_to_take_per_channel = bytes_left_in_buffer / sizeof(float) / 2;
|
||||
const uint32_t available_samples = logical->decoded_pcm.available_frames();
|
||||
const uint32_t samples_to_be_passed = std::min<uint32_t>(available_samples, granularity);
|
||||
|
||||
state->decoded_samples_pending = samples_to_take_per_channel;
|
||||
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();
|
||||
}
|
||||
|
||||
uint32_t samples_to_be_passed = std::min<uint32_t>(state->decoded_samples_pending, 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);
|
||||
}
|
||||
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
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user