diff --git a/src/libs/ngs2.cpp b/src/libs/ngs2.cpp index 196f5ef51..2947052da 100644 --- a/src/libs/ngs2.cpp +++ b/src/libs/ngs2.cpp @@ -1435,6 +1435,11 @@ static void Ngs2FinishBlock(Ngs2VoiceInternal& voice) { } ++block.repeated_count; block.cursor = 0; + if (voice.decoder != nullptr) { + block.data_cursor = 0; + voice.compressed_input.clear(); + voice.decoder->Reset(); + } } const Ngs2VoiceCallbackInfo info {voice.callback_data, reinterpret_cast(&voice), @@ -2396,8 +2401,7 @@ int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamH block.num_samples = 0; } EXIT_NOT_IMPLEMENTED(block.num_repeats != 0 && - (voice->rack->type != Ngs2RackType::Sampler || - voice->decoder != nullptr)); + voice->rack->type != Ngs2RackType::Sampler); EXIT_NOT_IMPLEMENTED( voice->decoder == nullptr && (uint64_t(block.num_skip_samples) + block.num_samples) * diff --git a/tests/Ngs2SamplerTests.cpp b/tests/Ngs2SamplerTests.cpp index 3641c7e75..e9d5e802e 100644 --- a/tests/Ngs2SamplerTests.cpp +++ b/tests/Ngs2SamplerTests.cpp @@ -712,6 +712,74 @@ void TestLoopCallbacksAndExit() { Ngs2VoiceControl(reinterpret_cast(&pending.voice), &exit_loop); } +void TestAtrac9Looping() { + // Four encoded mono frames with one nonzero spectral coefficient, then superframe padding. + // The block skips one frame plus one sample and ends inside the third frame. + constexpr std::array frame {0, 0, 0x04, 0x20, 0x04, 0xc0, 0, 0, 0x1f, 0xc0}; + std::array encoded {}; + for (size_t i = 0; i < 4; ++i) { + std::copy(std::begin(frame), std::end(frame), encoded.begin() + i * sizeof(frame)); + if (i != 0) encoded[i * sizeof(frame)] |= 0x80; + } + const auto make_voice = [&](uint32_t repeats, LoopCallbacks& log) { + auto f = std::make_unique(24000); + f->voice.SetupSampler({NGS2_WAVEFORM_TYPE_ATRAC9, 1, 24000, 0xfe4005f0}); + f->voice.state = Ngs2VoicePlayState::Playing; + f->voice.callback = reinterpret_cast(&RecordLoopCallback); + f->voice.callback_data = reinterpret_cast(&log); + f->voice.callback_flags = 3; + const Ngs2WaveformBlock block {0, encoded.size(), repeats, 129, 137, 0, 123}; + struct Blocks { + Ngs2VoiceParamHeader header {32, 0, 0x10000001}; + const uint8_t* data; + uint32_t flags, count; + const Ngs2WaveformBlock* blocks; + } param {{32, 0, 0x10000001}, encoded.data(), 0, 1, &block}; + Check(Ngs2VoiceControl(reinterpret_cast(&f->voice), ¶m.header) == OK, + "compressed loop queue failed"); + return f; + }; + LoopCallbacks baseline_log, finite_log, split_log, infinite_log; + auto baseline = make_voice(0, baseline_log); + baseline->Render(274); + const auto reference = baseline->voice.samples; + Check(std::ranges::any_of(reference, [](float sample) { return std::abs(sample) > 0.00001f; }), + "synthetic compressed fixture produced no decoded signal"); + auto finite = make_voice(2, finite_log); + finite->Render(822); + Check(finite->voice.state == Ngs2VoicePlayState::Empty && + finite->voice.decoded_samples == 411 && finite->voice.decoded_bytes == 576 && + finite_log.events == std::vector> {{2, 1}, {2, 2}, {1, 2}}, + "compressed finite loop lost samples, input progress, or completion callbacks"); + for (size_t i = 0; i < 3; ++i) { + Check(std::equal(reference.begin(), reference.end() - 1, + finite->voice.samples.begin() + i * 274), + "compressed loop did not restore decoder history and skip samples"); + const float tail = i == 2 ? reference.back() : std::lerp(reference[272], reference[0], 0.5f); + Check(finite->voice.samples[i * 274 + 273] == tail, + "compressed loop dropped the final frame or broke boundary interpolation"); + } + auto split = make_voice(2, split_log); + split->Render(273); + auto joined = split->voice.samples; + split->Render(549); + joined.insert(joined.end(), split->voice.samples.begin(), split->voice.samples.end()); + Check(joined == finite->voice.samples && split_log.events == finite_log.events, + "compressed loop changed across render grains"); + auto infinite = make_voice(UINT32_MAX, infinite_log); + infinite->Render(300); + Check(infinite->voice.state == Ngs2VoicePlayState::Playing && + infinite->voice.blocks.front().info.num_repeats == UINT32_MAX, + "compressed infinite loop did not rewind"); + Ngs2VoiceParamHeader exit_loop {sizeof(Ngs2VoiceParamHeader), 0, 0x10000004}; + Ngs2VoiceControl(reinterpret_cast(&infinite->voice), &exit_loop); + infinite->Render(248); + Check(infinite->voice.state == Ngs2VoicePlayState::Empty && + infinite->voice.decoded_samples == 274 && + infinite_log.events == std::vector> {{2, 1}, {1, 1}}, + "compressed loop exit failed to finish the active iteration"); +} + void TestWaveformReadAddress() { Fixture f(48000, 2); const int16_t pcm[] = {-100, -100, 8192, 4096, 16384, 8192}; @@ -921,6 +989,7 @@ int main() { TestSamplerReuseResetsRouting(); TestDeferredSamplerSetup(); TestLoopCallbacksAndExit(); + TestAtrac9Looping(); TestWaveformReadAddress(); TestFilterTailDuringStarvation(); TestFiniteFilterTail();