mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-30 01:01:21 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b65dd18224 | ||
|
|
2f6dccb10b | ||
|
|
08a7d7fbed | ||
|
|
2aa0d58ffb | ||
|
|
9bf311c6c7 | ||
|
|
e66a7f52b5 | ||
|
|
67f8276751 |
No files matched your search
@@ -1162,6 +1162,8 @@ LIB_DEFINE(InitAudio_1_Ngs2) {
|
|||||||
LIB_FUNC("AbYvTOZ8Pts", Ngs2::Ngs2VoiceRunCommands);
|
LIB_FUNC("AbYvTOZ8Pts", Ngs2::Ngs2VoiceRunCommands);
|
||||||
LIB_FUNC("-TOuuAQ-buE", Ngs2::Ngs2VoiceGetState);
|
LIB_FUNC("-TOuuAQ-buE", Ngs2::Ngs2VoiceGetState);
|
||||||
LIB_FUNC("rEh728kXk3w", Ngs2::Ngs2VoiceGetStateFlags);
|
LIB_FUNC("rEh728kXk3w", Ngs2::Ngs2VoiceGetStateFlags);
|
||||||
|
LIB_FUNC("9eic4AmjGVI", Ngs2::Ngs2VoiceQueryInfo);
|
||||||
|
LIB_FUNC("WCayTgob7-o", Ngs2::Ngs2VoiceGetPortInfo);
|
||||||
LIB_FUNC("i0VnXM-C9fc", Ngs2::Ngs2SystemRender);
|
LIB_FUNC("i0VnXM-C9fc", Ngs2::Ngs2SystemRender);
|
||||||
LIB_FUNC("l4Q2dWEH6UM", Ngs2::Ngs2SystemSetGrainSamples);
|
LIB_FUNC("l4Q2dWEH6UM", Ngs2::Ngs2SystemSetGrainSamples);
|
||||||
LIB_FUNC("hyVLT2VlOYk", Ngs2::Ngs2ParseWaveformData);
|
LIB_FUNC("hyVLT2VlOYk", Ngs2::Ngs2ParseWaveformData);
|
||||||
|
|||||||
+121
-7
@@ -568,6 +568,9 @@ struct Ngs2VoiceInternal {
|
|||||||
std::vector<float> output_matrix;
|
std::vector<float> output_matrix;
|
||||||
uint32_t channels = 0;
|
uint32_t channels = 0;
|
||||||
uint32_t output_id = 0;
|
uint32_t output_id = 0;
|
||||||
|
float fbw_level = 1.0f;
|
||||||
|
float lfe_level = 1.0f;
|
||||||
|
float dry_level = 0.0f;
|
||||||
bool rendering = false;
|
bool rendering = false;
|
||||||
bool rendered = false;
|
bool rendered = false;
|
||||||
bool has_samples = false;
|
bool has_samples = false;
|
||||||
@@ -710,10 +713,11 @@ struct Ngs2VoiceInternal {
|
|||||||
if (count > capacity) {
|
if (count > capacity) {
|
||||||
return NGS2_ERROR_INVALID_NUM_WAVEFORM_BLOCKS;
|
return NGS2_ERROR_INVALID_NUM_WAVEFORM_BLOCKS;
|
||||||
}
|
}
|
||||||
EXIT_NOT_IMPLEMENTED(!accepts_blocks || (flags & ~0x17u) != 0);
|
|
||||||
const bool clear = (flags & 4u) != 0;
|
const bool clear = (flags & 4u) != 0;
|
||||||
const bool only_data = (flags & 2u) != 0;
|
const bool only_data = (flags & 2u) != 0;
|
||||||
const bool join = only_data && (flags & 0x10u) == 0;
|
const bool join = only_data && (flags & 0x10u) == 0;
|
||||||
|
// CLEAR reopens a list that the previous addition closed.
|
||||||
|
EXIT_NOT_IMPLEMENTED((!accepts_blocks && !clear) || (flags & ~0x17u) != 0);
|
||||||
const auto retained = clear ? 0 : blocks.size();
|
const auto retained = clear ? 0 : blocks.size();
|
||||||
Block tail = retained == 0 ? Block {} : blocks.back();
|
Block tail = retained == 0 ? Block {} : blocks.back();
|
||||||
std::vector<Block> pending;
|
std::vector<Block> pending;
|
||||||
@@ -1800,6 +1804,18 @@ static void Ngs2ConsumeSamples(Ngs2VoiceInternal& voice, uint32_t grain) {
|
|||||||
voice.has_samples = output != 0;
|
voice.has_samples = output != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mono and stereo convert at -3 dB, mono feeds the surround front center, others map 1:1.
|
||||||
|
static float Ngs2DefaultMatrixLevel(uint32_t src_channels, uint32_t src, uint32_t dst_channels,
|
||||||
|
size_t dst) {
|
||||||
|
if ((src_channels == 1 && dst_channels == 2) || (src_channels == 2 && dst_channels == 1)) {
|
||||||
|
return std::numbers::sqrt2_v<float> / 2;
|
||||||
|
}
|
||||||
|
if (src_channels == 1 && dst_channels > 2) {
|
||||||
|
return dst == 2 ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
return src == dst ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2VoiceInternal*>& voices,
|
static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2VoiceInternal*>& voices,
|
||||||
uint32_t grain) {
|
uint32_t grain) {
|
||||||
if (voice.rendered) {
|
if (voice.rendered) {
|
||||||
@@ -1848,7 +1864,11 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
|
|||||||
EXIT_NOT_IMPLEMENTED(port.input >= voice.rack->NumBuffers());
|
EXIT_NOT_IMPLEMENTED(port.input >= voice.rack->NumBuffers());
|
||||||
const auto* input = source->Buffer(source->rack->PortBufferId(p), grain);
|
const auto* input = source->Buffer(source->rack->PortBufferId(p), grain);
|
||||||
auto* output = voice.Buffer(port.input, grain);
|
auto* output = voice.Buffer(port.input, grain);
|
||||||
|
// A port without a matrix, or whose matrix has no levels, uses the default mix.
|
||||||
const auto* matrix = port.matrix < 0 ? nullptr : &source->matrices.at(port.matrix);
|
const auto* matrix = port.matrix < 0 ? nullptr : &source->matrices.at(port.matrix);
|
||||||
|
if (matrix != nullptr && matrix->empty()) {
|
||||||
|
matrix = nullptr;
|
||||||
|
}
|
||||||
const size_t output_channels =
|
const size_t output_channels =
|
||||||
matrix == nullptr
|
matrix == nullptr
|
||||||
? voice.channels
|
? voice.channels
|
||||||
@@ -1857,7 +1877,8 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
|
|||||||
for (uint32_t src = 0; src < source->channels; ++src) {
|
for (uint32_t src = 0; src < source->channels; ++src) {
|
||||||
const float level =
|
const float level =
|
||||||
port.volume * (matrix == nullptr
|
port.volume * (matrix == nullptr
|
||||||
? (src == dst ? 1.0f : 0.0f)
|
? Ngs2DefaultMatrixLevel(source->channels, src,
|
||||||
|
voice.channels, dst)
|
||||||
: (*matrix)[dst * source->channels + src]);
|
: (*matrix)[dst * source->channels + src]);
|
||||||
for (uint32_t i = 0; i < grain; ++i) {
|
for (uint32_t i = 0; i < grain; ++i) {
|
||||||
output[dst * grain + i] += input[src * grain + i] * level;
|
output[dst * grain + i] += input[src * grain + i] * level;
|
||||||
@@ -1867,6 +1888,11 @@ static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector<Ngs2Voic
|
|||||||
voice.has_samples = true;
|
voice.has_samples = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (voice.has_samples && voice.rack->type == Ngs2RackType::Reverb) {
|
||||||
|
for (auto& sample: voice.samples) {
|
||||||
|
sample *= voice.dry_level;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (voice.has_samples) {
|
if (voice.has_samples) {
|
||||||
const auto& custom = voice.rack->option.custom_sampler.custom_rack_option;
|
const auto& custom = voice.rack->option.custom_sampler.custom_rack_option;
|
||||||
const auto rate = voice.rack->ngs->option.sample_rate;
|
const auto rate = voice.rack->ngs->option.sample_rate;
|
||||||
@@ -1978,10 +2004,13 @@ int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuff
|
|||||||
pcm = static_cast<float*>(output.buffer);
|
pcm = static_cast<float*>(output.buffer);
|
||||||
}
|
}
|
||||||
for (uint32_t dst = 0; dst < output.num_channels; ++dst) {
|
for (uint32_t dst = 0; dst < output.num_channels; ++dst) {
|
||||||
|
// Output channel 3 is LFE; every other channel is full band.
|
||||||
|
const float gain = dst == 3 ? voice->lfe_level : voice->fbw_level;
|
||||||
for (uint32_t src = 0; src < voice->channels; ++src) {
|
for (uint32_t src = 0; src < voice->channels; ++src) {
|
||||||
const float level = voice->output_matrix.empty()
|
const float level =
|
||||||
? (src == dst ? 1.0f : 0.0f)
|
gain * (voice->output_matrix.empty()
|
||||||
: voice->output_matrix.at(src * output.num_channels + dst);
|
? static_cast<float>(src == dst)
|
||||||
|
: voice->output_matrix.at(src * output.num_channels + dst));
|
||||||
if (level == 0.0f) {
|
if (level == 0.0f) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -2339,6 +2368,17 @@ int KYTY_SYSV_ABI Ngs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_i
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A patch to a rack handle connects to voice #0 of that rack.
|
||||||
|
static Ngs2VoiceInternal* Ngs2FindPatchDestination(uintptr_t handle) {
|
||||||
|
Common::LockGuard racks_lock(g_racks_mutex);
|
||||||
|
for (auto* rack = g_racks_list; rack != nullptr; rack = rack->next) {
|
||||||
|
if (reinterpret_cast<uintptr_t>(rack) == handle) {
|
||||||
|
return reinterpret_cast<Ngs2VoiceInternal*>(rack + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reinterpret_cast<Ngs2VoiceInternal*>(handle);
|
||||||
|
}
|
||||||
|
|
||||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||||
int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list) {
|
int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
@@ -2408,7 +2448,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
|||||||
const auto* patch = reinterpret_cast<const Ngs2VoicePatchParam*>(param);
|
const auto* patch = reinterpret_cast<const Ngs2VoicePatchParam*>(param);
|
||||||
EXIT_NOT_IMPLEMENTED(patch->port >= voice->ports.size());
|
EXIT_NOT_IMPLEMENTED(patch->port >= voice->ports.size());
|
||||||
voice->ports[patch->port].dest =
|
voice->ports[patch->port].dest =
|
||||||
reinterpret_cast<Ngs2VoiceInternal*>(patch->dest_handle);
|
Ngs2FindPatchDestination(patch->dest_handle);
|
||||||
voice->ports[patch->port].input = patch->dest_input_id;
|
voice->ports[patch->port].input = patch->dest_input_id;
|
||||||
LOGF("\t connect->port = %u\n"
|
LOGF("\t connect->port = %u\n"
|
||||||
"\t connect->dest_input_id = %u\n"
|
"\t connect->dest_input_id = %u\n"
|
||||||
@@ -2456,7 +2496,31 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 0x2001: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb); break;
|
case 0x2001: {
|
||||||
|
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb);
|
||||||
|
const auto* values = reinterpret_cast<const uint32_t*>(param + 1);
|
||||||
|
switch (param->id & 0xffffu) {
|
||||||
|
case 0:
|
||||||
|
// Input and output channels, flags; channel conversion stays silent.
|
||||||
|
EXIT_NOT_IMPLEMENTED(values[0] == 0 || values[2] != 0);
|
||||||
|
voice->ResetSetupState();
|
||||||
|
voice->channels = values[1] == values[0] ? values[0] : 0;
|
||||||
|
break;
|
||||||
|
case 1: {
|
||||||
|
// The reverb parameters start with the wet and dry levels.
|
||||||
|
const auto* levels = reinterpret_cast<const float*>(values);
|
||||||
|
voice->dry_level = std::clamp(levels[1], 0.0f, 1.0f);
|
||||||
|
|
||||||
|
static std::atomic_bool wet_warned = false;
|
||||||
|
if (levels[0] != 0.0f && !wet_warned.exchange(true)) {
|
||||||
|
Log::WriteToConsoleAndLog("warning: NGS2 reverb wet signal is muted\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: EXIT("unsupported reverb control: 0x%08" PRIx32 "\n", param->id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 0x3000: {
|
case 0x3000: {
|
||||||
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering);
|
EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering);
|
||||||
const auto* values = reinterpret_cast<const uint32_t*>(param + 1);
|
const auto* values = reinterpret_cast<const uint32_t*>(param + 1);
|
||||||
@@ -2470,6 +2534,12 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH
|
|||||||
matrix->levels + matrix->num_levels);
|
matrix->levels + matrix->num_levels);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 4: {
|
||||||
|
const auto* levels = reinterpret_cast<const float*>(values);
|
||||||
|
voice->fbw_level = std::clamp(levels[0], 0.0f, 4.0f);
|
||||||
|
voice->lfe_level = std::clamp(levels[1], 0.0f, 4.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 5: voice->output_id = values[0]; break;
|
case 5: voice->output_id = values[0]; break;
|
||||||
default: EXIT("unsupported mastering control: 0x%08" PRIx32 "\n", param->id);
|
default: EXIT("unsupported mastering control: 0x%08" PRIx32 "\n", param->id);
|
||||||
}
|
}
|
||||||
@@ -2761,6 +2831,50 @@ int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state
|
|||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int KYTY_SYSV_ABI Ngs2VoiceQueryInfo(uintptr_t voice_handle, uint32_t query_id, void* info,
|
||||||
|
size_t info_size) {
|
||||||
|
PRINT_NAME();
|
||||||
|
|
||||||
|
struct SetupInfo {
|
||||||
|
uint32_t channels, flags;
|
||||||
|
};
|
||||||
|
EXIT_NOT_IMPLEMENTED(voice_handle == 0 || info == nullptr);
|
||||||
|
EXIT_NOT_IMPLEMENTED(query_id != 0x4001 || info_size != sizeof(SetupInfo));
|
||||||
|
|
||||||
|
auto* voice = reinterpret_cast<Ngs2VoiceInternal*>(voice_handle);
|
||||||
|
|
||||||
|
Common::LockGuard lock(voice->rack->ngs->mutex);
|
||||||
|
|
||||||
|
*static_cast<SetupInfo*>(info) = {voice->channels, 0};
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int KYTY_SYSV_ABI Ngs2VoiceGetPortInfo(uintptr_t voice_handle, uint32_t port, void* info,
|
||||||
|
size_t info_size) {
|
||||||
|
PRINT_NAME();
|
||||||
|
|
||||||
|
struct PortInfo {
|
||||||
|
int32_t matrix;
|
||||||
|
float volume;
|
||||||
|
uint32_t delay_samples, input;
|
||||||
|
uintptr_t dest;
|
||||||
|
};
|
||||||
|
EXIT_NOT_IMPLEMENTED(voice_handle == 0 || info == nullptr || info_size != sizeof(PortInfo));
|
||||||
|
|
||||||
|
auto* voice = reinterpret_cast<Ngs2VoiceInternal*>(voice_handle);
|
||||||
|
|
||||||
|
Common::LockGuard lock(voice->rack->ngs->mutex);
|
||||||
|
|
||||||
|
EXIT_NOT_IMPLEMENTED(port >= voice->ports.size());
|
||||||
|
const auto& p = voice->ports[port];
|
||||||
|
|
||||||
|
*static_cast<PortInfo*>(info) = {p.matrix, p.volume, 0, p.input,
|
||||||
|
reinterpret_cast<uintptr_t>(p.dest)};
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
int KYTY_SYSV_ABI Ngs2SystemLock(uintptr_t system_handle) {
|
int KYTY_SYSV_ABI Ngs2SystemLock(uintptr_t system_handle) {
|
||||||
PRINT_NAME();
|
PRINT_NAME();
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* comma
|
|||||||
int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state,
|
int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state,
|
||||||
size_t state_size);
|
size_t state_size);
|
||||||
int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags);
|
int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags);
|
||||||
|
int KYTY_SYSV_ABI Ngs2VoiceQueryInfo(uintptr_t voice_handle, uint32_t query_id, void* info,
|
||||||
|
size_t info_size);
|
||||||
|
int KYTY_SYSV_ABI Ngs2VoiceGetPortInfo(uintptr_t voice_handle, uint32_t port, void* info,
|
||||||
|
size_t info_size);
|
||||||
int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info,
|
int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info,
|
||||||
uint32_t num_buffer_info);
|
uint32_t num_buffer_info);
|
||||||
int KYTY_SYSV_ABI Ngs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info);
|
int KYTY_SYSV_ABI Ngs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info);
|
||||||
|
|||||||
@@ -807,6 +807,76 @@ void TestPcm16RenderOutput() {
|
|||||||
"PCM16 output retained samples from the previous render");
|
"PCM16 output retained samples from the previous render");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestPatchToRackHandle() {
|
||||||
|
Ngs2Internal system;
|
||||||
|
Ngs2RackOptionUnion sampler_option {}, mastering_option {};
|
||||||
|
Ngs2FillDefaultRackOption(0x1000, &sampler_option);
|
||||||
|
Ngs2FillDefaultRackOption(0x3000, &mastering_option);
|
||||||
|
sampler_option.common.max_voices = 1;
|
||||||
|
RenderRack sampler(system, 0x1000, sampler_option.common);
|
||||||
|
RenderRack master(system, 0x3000, mastering_option.common);
|
||||||
|
auto& source = sampler.Voice();
|
||||||
|
Ngs2VoicePatchParam patch {{24, 0, 5}, 0, 0, master.handle};
|
||||||
|
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&source), &patch.header) == OK &&
|
||||||
|
source.ports[0].dest == &master.Voice(),
|
||||||
|
"patch to a rack handle did not connect to its voice #0");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestMasteringGain() {
|
||||||
|
Ngs2Internal system;
|
||||||
|
system.option = Ngs2DefaultSystemOption();
|
||||||
|
system.option.num_grain_samples = 1;
|
||||||
|
Ngs2RackOptionUnion sampler_option {}, mastering_option {};
|
||||||
|
Ngs2FillDefaultRackOption(0x1000, &sampler_option);
|
||||||
|
Ngs2FillDefaultRackOption(0x3000, &mastering_option);
|
||||||
|
sampler_option.common.max_voices = 1;
|
||||||
|
RenderRack sampler(system, 0x1000, sampler_option.common);
|
||||||
|
RenderRack master(system, 0x3000, mastering_option.common);
|
||||||
|
auto& source = sampler.Voice();
|
||||||
|
auto& mastering = master.Voice();
|
||||||
|
mastering.channels = 8;
|
||||||
|
mastering.state = Ngs2VoicePlayState::Playing;
|
||||||
|
const int16_t frame[8] = {8192, 8192, 8192, 8192, 8192, 8192, 8192, 8192};
|
||||||
|
source.SetupSampler({0x12, 8, 48000});
|
||||||
|
source.blocks.push_back({reinterpret_cast<const uint8_t*>(frame), {0, sizeof(frame), 0, 0, 1}});
|
||||||
|
source.ports[0].dest = &mastering;
|
||||||
|
source.SetEvent(1);
|
||||||
|
struct {
|
||||||
|
Ngs2VoiceParamHeader header {16, 0, 0x30000004};
|
||||||
|
float fbw = 5.0f, lfe = 0.5f;
|
||||||
|
} gain;
|
||||||
|
float pcm[8] = {};
|
||||||
|
Ngs2RenderBufferInfo output {pcm, sizeof(pcm), 0x18, 8};
|
||||||
|
const float expected[8] = {1.0f, 1.0f, 1.0f, 0.125f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
Check(Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&mastering), &gain.header) == OK &&
|
||||||
|
Ngs2SystemRender(reinterpret_cast<uintptr_t>(&system), &output, 1) == OK &&
|
||||||
|
std::ranges::equal(pcm, expected),
|
||||||
|
"mastering gain was not clamped or applied per full band and LFE channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestReverbDryLevel() {
|
||||||
|
Fixture f(48000);
|
||||||
|
Ngs2RackInternal reverb_rack {};
|
||||||
|
reverb_rack.ngs = &f.system;
|
||||||
|
reverb_rack.type = Ngs2RackType::Reverb;
|
||||||
|
Ngs2VoiceInternal reverb;
|
||||||
|
reverb.rack = &reverb_rack;
|
||||||
|
f.voice.ports.push_back({&reverb, 0, 1.0f, -1});
|
||||||
|
const int16_t pcm = 8192;
|
||||||
|
f.Queue(&pcm, 1, 0);
|
||||||
|
struct {
|
||||||
|
Ngs2VoiceParamHeader header {24, 24, 0x20010000};
|
||||||
|
uint32_t channels[4] = {1, 1, 0, 0};
|
||||||
|
Ngs2VoiceParamHeader levels_header {96, 0, 0x20010001};
|
||||||
|
float levels[22] = {0.6f, 0.4f};
|
||||||
|
} setup;
|
||||||
|
Ngs2VoiceControl(reinterpret_cast<uintptr_t>(&reverb), &setup.header);
|
||||||
|
reverb.SetEvent(1);
|
||||||
|
Ngs2RenderVoice(reverb, {&f.voice, &reverb}, 1);
|
||||||
|
Check(reverb.has_samples && reverb.samples[0] == 0.25f * 0.4f,
|
||||||
|
"reverb did not pass its dry signal");
|
||||||
|
}
|
||||||
|
|
||||||
void TestCustomMasteringChannelMixer() {
|
void TestCustomMasteringChannelMixer() {
|
||||||
Ngs2Internal system;
|
Ngs2Internal system;
|
||||||
system.option = Ngs2DefaultSystemOption();
|
system.option = Ngs2DefaultSystemOption();
|
||||||
@@ -1026,6 +1096,35 @@ void TestMonoRateAndRouting() {
|
|||||||
"22.05 kHz effect duration was incorrect");
|
"22.05 kHz effect duration was incorrect");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestDefaultMatrix() {
|
||||||
|
struct Case {
|
||||||
|
uint32_t src_channels;
|
||||||
|
int32_t matrix;
|
||||||
|
uint32_t dst_channels;
|
||||||
|
std::vector<float> expected;
|
||||||
|
};
|
||||||
|
constexpr float minus_3db = std::numbers::sqrt2_v<float> / 2;
|
||||||
|
const int16_t pcm[] = {8192, -16384};
|
||||||
|
for (const auto& c: {Case {1, -1, 2, {0.25f * minus_3db, 0.25f * minus_3db}},
|
||||||
|
Case {1, 0, 2, {0.25f * minus_3db, 0.25f * minus_3db}},
|
||||||
|
Case {2, -1, 1, {0.25f * minus_3db + -0.5f * minus_3db}},
|
||||||
|
Case {1, -1, 6, {0, 0, 0.25f, 0, 0, 0}}}) {
|
||||||
|
Fixture f(48000, c.src_channels);
|
||||||
|
Ngs2RackInternal rack {};
|
||||||
|
rack.ngs = &f.system;
|
||||||
|
rack.type = Ngs2RackType::Mastering;
|
||||||
|
Ngs2VoiceInternal dest;
|
||||||
|
dest.rack = &rack;
|
||||||
|
dest.channels = c.dst_channels;
|
||||||
|
dest.state = Ngs2VoicePlayState::Playing;
|
||||||
|
f.voice.matrices.resize(1);
|
||||||
|
f.voice.ports.push_back({&dest, 0, 1.0f, c.matrix});
|
||||||
|
f.Queue(pcm, 1, 0);
|
||||||
|
Ngs2RenderVoice(dest, {&f.voice, &dest}, 1);
|
||||||
|
Check(dest.samples == c.expected, "default port mix produced the wrong channel levels");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TestMatrixOutputChannels() {
|
void TestMatrixOutputChannels() {
|
||||||
Fixture f(48000, 2);
|
Fixture f(48000, 2);
|
||||||
Ngs2RackInternal mastering_rack {};
|
Ngs2RackInternal mastering_rack {};
|
||||||
@@ -1111,6 +1210,38 @@ void TestDeferredSamplerSetup() {
|
|||||||
"sampler did not play after deferred stream setup");
|
"sampler did not play after deferred stream setup");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestClearReopensClosedList() {
|
||||||
|
Fixture f(48000);
|
||||||
|
const int16_t first = 8192, second = -16384;
|
||||||
|
f.Queue(&first, 1, 0);
|
||||||
|
f.Queue(&second, 1, 4);
|
||||||
|
f.Render(1);
|
||||||
|
Check(f.voice.samples[0] == -0.5f, "CLEAR did not reopen a closed waveform list");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestSetupQuery() {
|
||||||
|
Fixture f(48000, 2);
|
||||||
|
uint32_t info[2] = {99, 99};
|
||||||
|
Ngs2VoiceQueryInfo(reinterpret_cast<uintptr_t>(&f.voice), 0x4001, info, sizeof(info));
|
||||||
|
Check(info[0] == 2 && info[1] == 0, "setup query did not report the voice channels");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestPortInfo() {
|
||||||
|
Fixture f(48000);
|
||||||
|
Ngs2VoiceInternal dest;
|
||||||
|
f.voice.ports.push_back({&dest, 1, 0.5f, 0});
|
||||||
|
struct {
|
||||||
|
int32_t matrix;
|
||||||
|
float volume;
|
||||||
|
uint32_t delay_samples, input;
|
||||||
|
uintptr_t dest;
|
||||||
|
} info {99, 99, 99, 99, 99};
|
||||||
|
Ngs2VoiceGetPortInfo(reinterpret_cast<uintptr_t>(&f.voice), 0, &info, sizeof(info));
|
||||||
|
Check(info.matrix == 0 && info.volume == 0.5f && info.delay_samples == 0 && info.input == 1 &&
|
||||||
|
info.dest == reinterpret_cast<uintptr_t>(&dest),
|
||||||
|
"port info did not report the port routing and volume");
|
||||||
|
}
|
||||||
|
|
||||||
void TestPlayStateBeforeRender() {
|
void TestPlayStateBeforeRender() {
|
||||||
Fixture f(48000);
|
Fixture f(48000);
|
||||||
const int16_t pcm[] = {8192, -16384};
|
const int16_t pcm[] = {8192, -16384};
|
||||||
@@ -1803,6 +1934,9 @@ int main() {
|
|||||||
TestAtrac9Vibration();
|
TestAtrac9Vibration();
|
||||||
TestAtrac9Discrete();
|
TestAtrac9Discrete();
|
||||||
TestPcm16RenderOutput();
|
TestPcm16RenderOutput();
|
||||||
|
TestPatchToRackHandle();
|
||||||
|
TestMasteringGain();
|
||||||
|
TestReverbDryLevel();
|
||||||
TestCustomMasteringChannelMixer();
|
TestCustomMasteringChannelMixer();
|
||||||
TestCustomVoiceStateCapacity();
|
TestCustomVoiceStateCapacity();
|
||||||
TestSubmixerSetupAndRouting();
|
TestSubmixerSetupAndRouting();
|
||||||
@@ -1837,8 +1971,12 @@ int main() {
|
|||||||
TestPcmSkipAcrossBlocks();
|
TestPcmSkipAcrossBlocks();
|
||||||
TestPcmBlocksEndWithTheirData();
|
TestPcmBlocksEndWithTheirData();
|
||||||
TestMonoRateAndRouting();
|
TestMonoRateAndRouting();
|
||||||
|
TestDefaultMatrix();
|
||||||
TestMatrixOutputChannels();
|
TestMatrixOutputChannels();
|
||||||
TestCustomPcmStillPlays();
|
TestCustomPcmStillPlays();
|
||||||
|
TestClearReopensClosedList();
|
||||||
|
TestSetupQuery();
|
||||||
|
TestPortInfo();
|
||||||
TestPlayStateBeforeRender();
|
TestPlayStateBeforeRender();
|
||||||
TestStatePublication();
|
TestStatePublication();
|
||||||
TestStopThenPlayBeforeRender();
|
TestStopThenPlayBeforeRender();
|
||||||
|
|||||||
Reference in new issue
Block a user