Cleanup
publish / publish (push) Successful in 13m11s

This commit is contained in:
khodowany
2026-10-08 02:13:33 -05:00
parent 3aa3a67e5d
commit dc46d83207
3 changed files with 50 additions and 1 deletions
+1
View File
@@ -16,6 +16,7 @@ struct transport_state {
std::uint32_t channels{}; std::uint32_t channels{};
std::uint64_t duration{}; std::uint64_t duration{};
mutable std::uint64_t cursor{}; mutable std::uint64_t cursor{};
mutable std::size_t first_live_click{};
sonus::transport_config config{}; sonus::transport_config config{};
std::array<std::vector<std::float32_t>, 2> samples{}; std::array<std::vector<std::float32_t>, 2> samples{};
mutable std::vector<std::float32_t> scratch{}; mutable std::vector<std::float32_t> scratch{};
+12 -1
View File
@@ -155,7 +155,18 @@ auto sonus::detail::transport_player::render(std::float32_t *output, std::uint64
return; return;
} }
} }
for (auto const &click : state->config.clicks) { // Retain overlapping tails, but never revisit an expired schedule prefix.
// The cursor is owned by the render thread and needs no allocation or lock.
while (state->first_live_click < state->config.clicks.size()) {
auto const &click = state->config.clicks[state->first_live_click];
auto length = state->samples[click.downbeat ? 0 : 1].size() / channels;
if (click.frame > state->cursor || length > state->cursor - click.frame) {
break;
}
++state->first_live_click;
}
for (auto index = state->first_live_click; index < state->config.clicks.size(); ++index) {
auto const &click = state->config.clicks[index];
if (click.frame >= state->cursor + count) { if (click.frame >= state->cursor + count) {
break; break;
} }
+37
View File
@@ -109,3 +109,40 @@ BOOST_AUTO_TEST_CASE(recording_transport_replacement_cancels_old_session_and_val
config.clicks.push_back({10, true}); config.clicks.push_back({10, true});
BOOST_CHECK(!sonus::prepared_transport::create(config, 48000, 2)); BOOST_CHECK(!sonus::prepared_transport::create(config, 48000, 2));
} }
BOOST_AUTO_TEST_CASE(recording_transport_long_schedule_preserves_overlaps_across_buffer_sizes) {
auto files = recordings{};
files.write("beat1.wav", 1537, 1, -1);
files.write("beat2.wav", 17, 2, -2);
auto config = files.config();
config.backing.clear();
config.duration_frames = 40000;
config.clicks.clear();
for (std::uint64_t i = 0; i < 2000; ++i) {
config.clicks.push_back({i * 20, i % 7 == 0});
}
auto expected = std::vector<float>(config.duration_frames * 2);
for (auto const &click : config.clicks) {
auto length = click.downbeat ? 1537U : 17U;
auto value = click.downbeat ? 1.F / 32768 : 2.F / 32768;
for (auto frame = click.frame; frame < std::min(click.frame + length, config.duration_frames); ++frame) {
expected[frame * 2] += value;
expected[frame * 2 + 1] -= value;
}
}
for (auto chunk : {317U, 1024U, 1701U}) {
auto prepared = sonus::prepared_transport::create(config, 48000, 2);
BOOST_REQUIRE(prepared);
auto engine = sonus::engine::create({.sample_rate = 48000, .channels = 2, .master_volume = 1.F, .no_device = true});
BOOST_REQUIRE(engine);
auto session = engine->play_transport(std::move(*prepared));
BOOST_REQUIRE(session);
auto actual = std::vector<float>(expected.size());
for (std::uint64_t frame = 0; frame < config.duration_frames; frame += chunk) {
BOOST_REQUIRE(engine->render(actual.data() + frame * 2, std::min<std::uint64_t>(chunk, config.duration_frames - frame)));
}
BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end());
BOOST_CHECK(session->status() == sonus::playback_status::COMPLETED);
BOOST_TEST(session->frames() == config.duration_frames);
}
}