#include #include #include #include #include #include #include "detail/sequence_player.hpp" sonus::playback_session::playback_session(std::shared_ptr> status) : status_{std::move(status)} {} auto sonus::playback_session::status() const -> sonus::playback_status { return status_ ? status_->load(std::memory_order_acquire) : sonus::playback_status::STOPPED; } auto sonus::playback_session::stop() const -> void { if (status_) { auto expected = sonus::playback_status::PLAYING; status_->compare_exchange_strong(expected, sonus::playback_status::STOPPED, std::memory_order_acq_rel); } } sonus::prepared_sequence::prepared_sequence(std::unique_ptr state) : state_{std::move(state)} {} sonus::prepared_sequence::prepared_sequence(prepared_sequence &&) noexcept = default; auto sonus::prepared_sequence::operator=(prepared_sequence &&) noexcept -> prepared_sequence & = default; sonus::prepared_sequence::~prepared_sequence() = default; auto sonus::prepared_sequence::create(sonus::sample_sequence sequence, std::uint32_t sample_rate) -> std::expected { auto fail = [](std::string message) -> std::expected { return sonus::detail::fail(sonus::detail::error_invalid_argument, std::move(message)); }; if (sample_rate < 8000U || sample_rate > 384000U || sequence.max_voices == 0U || sequence.max_voices > 256U || sequence.events.size() > 1000000U || sequence.samples.size() > 4096U || sequence.duration_frames == 0U || sequence.duration_frames > static_cast(sample_rate) * 86400U || !std::isfinite(sequence.gain) || sequence.gain < 0.0F || sequence.gain > 1.0F) { return fail("invalid sequence limits or gain"); } if (sequence.release_envelope.empty() || sequence.release_envelope.size() > sample_rate / 2U || sequence.release_envelope.front() != 1.0F || sequence.release_envelope.back() != 0.0F || std::any_of(sequence.release_envelope.begin(), sequence.release_envelope.end(), [](auto value) { return !std::isfinite(value) || value < 0.0F || value > 1.0F; })) { return fail("release envelope must run from one to zero within 500 milliseconds"); } auto sample_count = std::uint64_t{}; for (auto const &sample : sequence.samples) { sample_count += sample.frames.size(); if (sample_count > 16U * 1024U * 1024U || sample.sample_rate != sample_rate || sample.loop_start >= sample.loop_end || sample.loop_end > sample.frames.size() || std::any_of(sample.frames.begin(), sample.frames.end(), [](auto value) { return !std::isfinite(value) || std::abs(value) > 1.0F; })) { return fail("invalid sample rate, loop, PCM data or sample memory limit"); } } auto endpoints = std::vector>{}; endpoints.reserve(sequence.events.size() * 2U); auto end = sequence.duration_frames; auto previous_start = std::uint64_t{}; for (auto const &event : sequence.events) { if (event.sample >= sequence.samples.size() || event.duration_frames == 0U || event.start_frame < previous_start || event.start_frame >= sequence.duration_frames || event.duration_frames > sequence.duration_frames - event.start_frame || !std::isfinite(event.gain) || event.gain < 0.0F || event.gain > 1.0F) { return fail("invalid or unsorted sample event"); } auto release_end = event.start_frame + event.duration_frames + sequence.release_envelope.size(); endpoints.emplace_back(event.start_frame, 1); endpoints.emplace_back(release_end, -1); end = std::max(end, release_end); previous_start = event.start_frame; } std::sort(endpoints.begin(), endpoints.end()); auto voices = std::int32_t{}; auto peak = std::int32_t{}; for (auto const &[frame, change] : endpoints) { voices += change; peak = std::max(peak, voices); if (voices > static_cast(sequence.max_voices)) { return fail("sequence exceeds simultaneous voice capacity including releases"); } } auto next = std::make_unique(); next->voices.resize(static_cast(peak)); next->sequence = std::move(sequence); next->status = std::make_shared>(sonus::playback_status::PLAYING); next->end_frame = end; next->sample_rate = sample_rate; return sonus::prepared_sequence{std::move(next)}; } auto sonus::detail::sequence_player::play(sonus::prepared_sequence sequence, std::uint32_t sample_rate) -> std::expected { if (!sequence.state_ || sequence.state_->sample_rate != sample_rate) { return sonus::detail::fail(sonus::detail::error_invalid_argument, "empty sequence or incompatible sample rate"); } auto next = std::move(sequence.state_); auto result = sonus::playback_session{next->status}; auto lock = std::scoped_lock{mutex_}; if (auto current = snapshots_.current()) { sonus::playback_session{current->status}.stop(); } snapshots_.publish(std::move(next)); return result; } auto sonus::detail::sequence_player::stop() -> void { auto lock = std::scoped_lock{mutex_}; if (auto current = snapshots_.current()) { sonus::playback_session{current->status}.stop(); } snapshots_.publish(nullptr); } auto sonus::detail::sequence_player::collect() -> void { auto lock = std::scoped_lock{mutex_}; if (auto current = snapshots_.current(); current && current->status->load(std::memory_order_acquire) != sonus::playback_status::PLAYING) { snapshots_.publish(nullptr); } snapshots_.collect(); } auto sonus::detail::sequence_player::fail() -> void { auto guard = snapshots_.read(); if (auto current = guard.get()) { auto expected = sonus::playback_status::PLAYING; current->status->compare_exchange_strong(expected, sonus::playback_status::FAILED, std::memory_order_release); } } auto sonus::detail::sequence_player::render(std::float32_t *output, std::uint64_t frames, std::uint32_t channels, std::float32_t gain) -> void { auto guard = snapshots_.read(); auto current = guard.get(); if (!current || current->status->load(std::memory_order_acquire) != sonus::playback_status::PLAYING) { return; } auto const &sequence = current->sequence; for (auto frame = std::uint64_t{}; frame < frames && current->frame < current->end_frame; ++frame, ++current->frame) { for (auto &voice : current->voices) { if (voice.active) { auto const &event = sequence.events[voice.event]; if (current->frame >= event.start_frame + event.duration_frames + sequence.release_envelope.size()) { voice.active = false; } } } while (current->next_event < sequence.events.size() && sequence.events[current->next_event].start_frame == current->frame) { auto slot = std::find_if(current->voices.begin(), current->voices.end(), [](auto const &voice) { return !voice.active; }); if (slot == current->voices.end()) { auto expected = sonus::playback_status::PLAYING; current->status->compare_exchange_strong(expected, sonus::playback_status::FAILED, std::memory_order_release); return; } *slot = {.event = current->next_event++, .active = true}; } auto mixed = std::float32_t{}; for (auto const &voice : current->voices) { if (!voice.active) { continue; } auto const &event = sequence.events[voice.event]; auto const &sample = sequence.samples[event.sample]; auto age = current->frame - event.start_frame; auto index = age < sample.loop_end ? age : sample.loop_start + (age - sample.loop_start) % (sample.loop_end - sample.loop_start); auto release = age < event.duration_frames ? 1.0F : sequence.release_envelope[age - event.duration_frames]; mixed += sample.frames[index] * event.gain * release; } for (auto channel = std::uint32_t{}; channel < channels; ++channel) { auto &destination = output[frame * channels + channel]; destination = std::clamp(destination + mixed * sequence.gain * gain, -1.0F, 1.0F); } } if (current->frame == current->end_frame) { auto expected = sonus::playback_status::PLAYING; current->status->compare_exchange_strong(expected, sonus::playback_status::COMPLETED, std::memory_order_release); } }