feat(libSceNgs2.native): add custom voice filter support and related tests for 0x13 issue case
This commit is contained in:
parent
855823e85e
commit
ef52b16920
|
|
@ -63,10 +63,12 @@ static constexpr std::uint32_t SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_
|
|||
static constexpr std::uint32_t SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_EXIT_LOOP = 0x40010004;
|
||||
static constexpr std::uint32_t SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_PITCH = 0x40010005;
|
||||
static constexpr std::uint32_t SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP = 0x40020000;
|
||||
static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_FILTER = 0x40001300;
|
||||
static constexpr std::uint32_t SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX2 = 0x40001f00;
|
||||
|
||||
static constexpr std::uint32_t SCE_NGS2_CUSTOM_MAX_MODULES = 24;
|
||||
static constexpr std::uint32_t SCE_NGS2_CUSTOM_MAX_PORTS = 16;
|
||||
static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_FILTER = 0x13;
|
||||
static constexpr std::uint32_t SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2 = 0x1f;
|
||||
|
||||
static constexpr std::uint32_t SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE = 1;
|
||||
|
|
@ -507,6 +509,33 @@ struct Ngs2CustomVoiceUserFx2Param {
|
|||
};
|
||||
static_assert(sizeof(Ngs2CustomVoiceUserFx2Param) == 24);
|
||||
|
||||
struct Ngs2CustomVoiceFilterParam {
|
||||
Ngs2VoiceParamHeader header;
|
||||
std::uint32_t type;
|
||||
std::uint32_t channel_mask;
|
||||
union {
|
||||
struct {
|
||||
float i0;
|
||||
float i1;
|
||||
float i2;
|
||||
float o1;
|
||||
float o2;
|
||||
} direct;
|
||||
struct {
|
||||
float frequency;
|
||||
float q;
|
||||
float level;
|
||||
std::uint32_t reserved;
|
||||
std::uint32_t reserved2;
|
||||
} fcq;
|
||||
} param;
|
||||
std::uint32_t reserved3;
|
||||
};
|
||||
static_assert(sizeof(Ngs2CustomVoiceFilterParam) == 40);
|
||||
static_assert(offsetof(Ngs2CustomVoiceFilterParam, type) == 8);
|
||||
static_assert(offsetof(Ngs2CustomVoiceFilterParam, channel_mask) == 12);
|
||||
static_assert(offsetof(Ngs2CustomVoiceFilterParam, param) == 16);
|
||||
|
||||
struct Ngs2MasteringVoiceSetupParam {
|
||||
Ngs2VoiceParamHeader header;
|
||||
std::uint32_t num_io_channels;
|
||||
|
|
|
|||
|
|
@ -17,10 +17,16 @@ void Ngs2CheckCustomRack(const Ngs2CustomRackOption& option) {
|
|||
if (option.num_modules > SCE_NGS2_CUSTOM_MAX_MODULES || option.rack_option.max_ports > SCE_NGS2_CUSTOM_MAX_PORTS) APS5_INVALID_ARG_EX;
|
||||
for (std::uint32_t m = 0; m < option.num_modules; m++) {
|
||||
const auto& info = option.module[m];
|
||||
if (info.module_id != SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2) throw std::runtime_error("NGS2: custom module id " + Ngs2Hex(info.module_id) + " is not implemented");
|
||||
if (info.option == nullptr || info.option->size != sizeof(Ngs2CustomUserFx2ModuleOption)) APS5_INVALID_ARG_EX;
|
||||
if (info.source_buffer_id != 0 || info.dest_buffer_id != 0) APS5_INVALID_ARG_EX;
|
||||
if (reinterpret_cast<const Ngs2CustomUserFx2ModuleOption*>(info.option)->process_handler == nullptr) APS5_INVALID_ARG_EX;
|
||||
if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2) {
|
||||
if (info.option == nullptr || info.option->size != sizeof(Ngs2CustomUserFx2ModuleOption)) APS5_INVALID_ARG_EX;
|
||||
if (info.source_buffer_id != 0 || info.dest_buffer_id != 0) APS5_INVALID_ARG_EX;
|
||||
if (reinterpret_cast<const Ngs2CustomUserFx2ModuleOption*>(info.option)->process_handler == nullptr) APS5_INVALID_ARG_EX;
|
||||
} else if (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_FILTER) {
|
||||
if (info.option != nullptr || info.state_size != 0 || info.state_offset != 0) APS5_INVALID_ARG_EX;
|
||||
if (info.source_buffer_id != 0 || info.extra_buffer_id != 0 || info.dest_buffer_id != 0) APS5_INVALID_ARG_EX;
|
||||
} else {
|
||||
throw std::runtime_error("NGS2: custom module id " + Ngs2Hex(info.module_id) + " is not implemented");
|
||||
}
|
||||
}
|
||||
for (std::uint32_t p = 0; p < option.rack_option.max_ports; p++) {
|
||||
if (option.port[p].source_buffer_id != 0) APS5_INVALID_ARG_EX;
|
||||
|
|
@ -34,10 +40,13 @@ static Ngs2UserFx2SetupContext UserFxContext(Ngs2Rack& rack, std::size_t module,
|
|||
}
|
||||
|
||||
void Ngs2SetupUserFx(Ngs2Rack& rack, const Ngs2CustomRackOption& option) {
|
||||
rack.customModuleIds.resize(option.num_modules);
|
||||
rack.userFx.resize(option.num_modules);
|
||||
rack.userFxCommon.resize(option.num_modules);
|
||||
for (auto& voice : rack.voices) voice.userFx.resize(option.num_modules);
|
||||
for (std::uint32_t m = 0; m < option.num_modules; m++) {
|
||||
rack.customModuleIds[m] = option.module[m].module_id;
|
||||
if (option.module[m].module_id == SCE_NGS2_CUSTOM_MODULE_ID_FILTER) continue;
|
||||
const auto& module = *reinterpret_cast<const Ngs2CustomUserFx2ModuleOption*>(option.module[m].option);
|
||||
rack.userFx[m] = module;
|
||||
rack.userFxCommon[m].resize(module.common_size);
|
||||
|
|
@ -58,6 +67,7 @@ void Ngs2SetupUserFx(Ngs2Rack& rack, const Ngs2CustomRackOption& option) {
|
|||
void Ngs2CleanupUserFx(Ngs2Rack& rack) {
|
||||
for (std::uint32_t i = 0; i < rack.voices.size(); i++) {
|
||||
for (std::size_t m = 0; m < rack.userFx.size(); m++) {
|
||||
if (rack.customModuleIds[m] != SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2) continue;
|
||||
if (rack.userFx[m].cleanup_handler == nullptr) continue;
|
||||
auto context = UserFxContext(rack, m, i);
|
||||
const int result = rack.userFx[m].cleanup_handler(&context);
|
||||
|
|
@ -90,6 +100,27 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) {
|
|||
SetupCustomSubmixer(voice, ParamAs<Ngs2CustomSubmixerVoiceSetupParam>(param));
|
||||
return;
|
||||
}
|
||||
if ((param.id & ~0x1fu) == SCE_NGS2_CUSTOM_VOICE_PARAM_FILTER) {
|
||||
if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SAMPLER && rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) {
|
||||
throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack");
|
||||
}
|
||||
const auto index = param.id & 0x1f;
|
||||
if (index >= voice.rack->customModuleIds.size() || voice.rack->customModuleIds[index] != SCE_NGS2_CUSTOM_MODULE_ID_FILTER) APS5_INVALID_ARG_EX;
|
||||
const auto& custom = ParamAs<Ngs2CustomVoiceFilterParam>(param);
|
||||
Ngs2SamplerVoiceFilterParam filter{};
|
||||
filter.header = custom.header;
|
||||
filter.index = index;
|
||||
filter.location = 1;
|
||||
filter.type = custom.type;
|
||||
filter.channel_mask = custom.channel_mask;
|
||||
if (custom.type == 1) {
|
||||
filter.frequency = custom.param.fcq.frequency;
|
||||
filter.q = custom.param.fcq.q;
|
||||
filter.level = custom.param.fcq.level;
|
||||
}
|
||||
Ngs2SetFilter(voice, filter);
|
||||
return;
|
||||
}
|
||||
if ((param.id & ~0x1fu) == SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX2) {
|
||||
if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SAMPLER && rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) {
|
||||
throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack");
|
||||
|
|
@ -102,10 +133,12 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) {
|
|||
}
|
||||
|
||||
void Ngs2ProcessUserFx(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sampleRate) {
|
||||
if (voice.userFx.empty()) return;
|
||||
if (voice.userFx.empty() || std::none_of(voice.rack->customModuleIds.begin(), voice.rack->customModuleIds.end(),
|
||||
[](std::uint32_t id) { return id == SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2; })) return;
|
||||
std::vector<float*> channels(voice.channels);
|
||||
for (std::uint32_t c = 0; c < voice.channels; c++) channels[c] = voice.samples.data() + static_cast<std::size_t>(c) * grain;
|
||||
for (std::size_t m = 0; m < voice.userFx.size(); m++) {
|
||||
if (voice.rack->customModuleIds[m] != SCE_NGS2_CUSTOM_MODULE_ID_USER_FX2) continue;
|
||||
auto& fx = voice.userFx[m];
|
||||
const auto& module = voice.rack->userFx[m];
|
||||
Ngs2UserFx2ProcessContext context{channels.data(), voice.rack->userFxCommon[m].data(), fx.param.data(), fx.work.data(), fx.state.data(),
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ struct Ngs2Rack {
|
|||
std::vector<Ngs2Voice> voices;
|
||||
std::vector<Ngs2CustomUserFx2ModuleOption> userFx;
|
||||
std::vector<std::vector<std::uint8_t>> userFxCommon;
|
||||
std::vector<std::uint32_t> customModuleIds;
|
||||
};
|
||||
|
||||
struct Ngs2System {
|
||||
|
|
@ -185,6 +186,7 @@ void* Ngs2Place(const Ngs2ContextBufferInfo* bufferInfo, std::size_t size, std::
|
|||
int Ngs2DestroyRack(Ngs2Rack& rack, Ngs2ContextBufferInfo* outBufferInfo);
|
||||
int Ngs2ReleaseBuffer(const Ngs2BufferAllocator& allocator, Ngs2ContextBufferInfo bufferInfo, Ngs2ContextBufferInfo* outBufferInfo);
|
||||
void Ngs2SetupAtrac9(Ngs2Voice& voice, const Ngs2WaveformFormat& format);
|
||||
void Ngs2SetFilter(Ngs2Voice& voice, const Ngs2SamplerVoiceFilterParam& param);
|
||||
std::size_t Ngs2Atrac9BlockBytes(const Ngs2Voice& voice, const Ngs2WaveformBlock& block);
|
||||
void Ngs2RestartAtrac9(Ngs2Voice& voice);
|
||||
void Ngs2ConsumeAtrac9(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate);
|
||||
|
|
|
|||
|
|
@ -114,7 +114,9 @@ static int CreateRack(Ngs2Handle systemHandle, std::uint32_t rackId, const RackO
|
|||
rack->system = system;
|
||||
rack->rackId = rackId;
|
||||
rack->maxChannels = RackMaxChannels(rackId, options);
|
||||
rack->maxFilters = rackId == SCE_NGS2_RACK_ID_SAMPLER ? options.sampler.max_filters : 0;
|
||||
rack->maxFilters = rackId == SCE_NGS2_RACK_ID_SAMPLER ? options.sampler.max_filters
|
||||
: rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER ? options.customSampler.custom_rack_option.num_modules
|
||||
: rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER ? options.customSubmixer.custom_rack_option.num_modules : 0;
|
||||
rack->uid = nextUid++;
|
||||
std::memcpy(rack->name, options.common.name, sizeof(rack->name));
|
||||
rack->name[sizeof(rack->name) - 1] = '\0';
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ static void AddWaveformBlocks(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBl
|
|||
}
|
||||
}
|
||||
|
||||
static void SetFilter(Ngs2Voice& voice, const Ngs2SamplerVoiceFilterParam& param) {
|
||||
void Ngs2SetFilter(Ngs2Voice& voice, const Ngs2SamplerVoiceFilterParam& param) {
|
||||
if (param.index >= voice.rack->maxFilters) APS5_INVALID_ARG_EX;
|
||||
if (voice.filters.size() <= param.index) voice.filters.resize(param.index + 1);
|
||||
auto& filter = voice.filters[param.index];
|
||||
|
|
@ -305,7 +305,7 @@ static void ApplySamplerParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& para
|
|||
voice.pitch = ratio;
|
||||
break;
|
||||
}
|
||||
case SCE_NGS2_SAMPLER_VOICE_PARAM_FILTER: SetFilter(voice, ParamAs<Ngs2SamplerVoiceFilterParam>(param)); break;
|
||||
case SCE_NGS2_SAMPLER_VOICE_PARAM_FILTER: Ngs2SetFilter(voice, ParamAs<Ngs2SamplerVoiceFilterParam>(param)); break;
|
||||
default: throw std::runtime_error("NGS2: sampler voice param " + Ngs2Hex(param.id) + " is not implemented");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,17 @@ static bool CreateThrows(const Ngs2CustomSubmixerRackOption& option) {
|
|||
return false;
|
||||
}
|
||||
|
||||
template <typename TException>
|
||||
static bool CreateSamplerThrows(const Ngs2CustomSamplerRackOption& option) {
|
||||
Ngs2ContextBufferInfo query{};
|
||||
try {
|
||||
sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &query);
|
||||
} catch (const TException&) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename TException, typename TParam>
|
||||
static bool ControlThrows(uintptr_t voice, std::uint32_t id, TParam param) {
|
||||
param.header = {static_cast<std::uint16_t>(sizeof(TParam)), 0, id};
|
||||
|
|
@ -274,9 +285,62 @@ static void TestCustomSampler() {
|
|||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
static void TestCustomFilter() {
|
||||
const auto system = CreateSystem();
|
||||
const auto master = Mastering(system, 1);
|
||||
|
||||
Ngs2CustomSamplerRackOption option{};
|
||||
option.custom_rack_option.rack_option.size = sizeof(option);
|
||||
option.custom_rack_option.rack_option.max_grain_samples = 512;
|
||||
option.custom_rack_option.rack_option.max_voices = 1;
|
||||
option.custom_rack_option.num_buffers = 1;
|
||||
option.custom_rack_option.num_modules = 1;
|
||||
option.custom_rack_option.module[0].module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER;
|
||||
option.max_channel_works = 1;
|
||||
|
||||
auto invalid = option;
|
||||
invalid.custom_rack_option.module[0].state_size = 4;
|
||||
Require(CreateSamplerThrows<std::invalid_argument>(invalid));
|
||||
invalid = option;
|
||||
invalid.custom_rack_option.module[0].option = reinterpret_cast<const Ngs2CustomModuleOption*>(1);
|
||||
Require(CreateSamplerThrows<std::invalid_argument>(invalid));
|
||||
|
||||
Ngs2ContextBufferInfo query{};
|
||||
Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &query) == SCE_NGS2_OK);
|
||||
const auto info = Buffer(query);
|
||||
uintptr_t rack = 0;
|
||||
Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_OK);
|
||||
|
||||
const auto voice = Voice(rack);
|
||||
Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_SETUP,
|
||||
Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1, 48000, 0, 0, 0}});
|
||||
const std::vector<std::int16_t> pcm(Grain, 16384);
|
||||
const Ngs2WaveformBlock block{0, pcm.size() * sizeof(std::int16_t), 0, 0, static_cast<std::uint32_t>(pcm.size()), 0, 0};
|
||||
Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS,
|
||||
Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block});
|
||||
|
||||
Ngs2CustomVoiceFilterParam filter{};
|
||||
filter.type = 1;
|
||||
filter.param.fcq.frequency = 10000.0f;
|
||||
filter.param.fcq.q = 0.70710678f;
|
||||
filter.param.fcq.level = 1.0f;
|
||||
Control(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_FILTER, filter);
|
||||
Require(ControlThrows<std::invalid_argument>(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_FILTER | 1, filter));
|
||||
Patch(voice, master);
|
||||
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
|
||||
std::vector<float> out(Grain, -1.0f);
|
||||
const Ngs2RenderBufferInfo renderInfo{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 1};
|
||||
Require(sceNgs2SystemRender(system, &renderInfo, 1) == SCE_NGS2_OK);
|
||||
Require(out[0] > 0.0f && out[0] < 0.1f && out.back() > out[0]);
|
||||
Require(sceNgs2RackDestroy(rack, nullptr) == SCE_NGS2_OK);
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
int main() {
|
||||
TestUserFxChain();
|
||||
TestRackOptions();
|
||||
TestCustomSampler();
|
||||
TestCustomFilter();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue