#pragma once #include #include #include #include #include #include "../Axis/Axis_p.h" #include "../Axis/Time_Axis_p.h" #include "../architecture/Bounded_Input_Buffer.h" #include "../architecture/Renderable.h" #include "../base/Memory.h" #include "../primitive/Curve_Utils_p.h" #include "../render/Render_Frame_Snapshot.h" #include "Psc_Cpp_Core/Base/RingBuffer.hpp" #include "Frequency_Trace.h" namespace renderive { struct Frequency_Sample { int tick{}; double power{}; }; struct Frequency_Trace_Input_Data : Input_Data { static constexpr std::size_t Max_Pending_Value_Count = 256; Bounded_Input_Buffer data_list; void push(int tick, double power) { Frequency_Sample data{tick, power}; data_list.push(&data, sizeof(data), Max_Pending_Value_Count); } template void read_all(Handler handler) { data_list.read_all([&handler](const void* data, std::size_t size) { (void)size; Frequency_Sample value; std::memcpy(&value, data, sizeof(value)); handler(value); }); } void clear() override { data_list.clear(); } }; struct Frequency_Trace_Render_State : Render_State, Frequency_Trace_Prop {}; struct Frequency_Trace_Private : Typed_Render_Data { Psc::StreamRingBuffer_ST buffer{memory_resource(Memory_Domain::Audio)}; std::pmr::vector data_snapshot{memory_resource(Memory_Domain::Audio)}; std::pmr::vector prepared_points{memory_resource(Memory_Domain::Audio)}; std::pmr::vector segment_offsets{memory_resource(Memory_Domain::Audio)}; int data_count{}; int point_count{}; int tick_gap_count{}; int non_monotonic_tick_count{}; int segment_count{}; static bool trace_debug_enabled() { static const bool enabled = []() { const char* value = std::getenv("RENDERIVE_FREQUENCY_TRACE_DEBUG"); return value && std::atoi(value) != 0; }(); return enabled; } void resize_power_buffer(int count) { point_count = count; data_count = 0; data_snapshot.resize(count); buffer.init(sizeof(Frequency_Sample) * count); } void push_power(const Frequency_Sample& data) { if (data_count == point_count) { Frequency_Sample ignored; std::size_t len = sizeof(Frequency_Sample); buffer.read(&ignored, len); --data_count; } buffer.write(&data, sizeof(data)); ++data_count; } void snapshot_power_buffer() { const std::size_t copied = buffer.peek_best_effort(data_snapshot.data(), sizeof(Frequency_Sample) * data_count); data_count = static_cast(copied / sizeof(Frequency_Sample)); } void rebuild_prepared_points(const Timeline_Stream_Snapshot& timeline) { prepared_points.clear(); segment_offsets.clear(); tick_gap_count = 0; non_monotonic_tick_count = 0; segment_count = 0; prepared_points.reserve(static_cast(data_count)); segment_offsets.reserve(static_cast(data_count)); bool in_segment = false; bool has_previous_tick = false; int previous_tick = 0; auto start_segment = [this, &in_segment]() { if (in_segment) return; segment_offsets.push_back(prepared_points.size()); in_segment = true; }; for (int i = 0; i < data_count; ++i) { const Frequency_Sample& item = data_snapshot[static_cast(i)]; int coord{}; if (!timeline.coord_by_stream_tick(item.tick, coord)) { in_segment = false; has_previous_tick = false; continue; } if (has_previous_tick) { if (item.tick <= previous_tick) { ++non_monotonic_tick_count; in_segment = false; } else if (item.tick != previous_tick + 1) { ++tick_gap_count; in_segment = false; } } start_segment(); prepared_points.emplace_back(static_cast(coord), item.power); previous_tick = item.tick; has_previous_tick = true; } segment_count = static_cast(segment_offsets.size()); if (trace_debug_enabled()) { std::fprintf(stderr, "frequency_tick_gap_count=%d frequency_non_monotonic_count=%d frequency_segment_count=%d\n", tick_gap_count, non_monotonic_tick_count, segment_count); } } void prepare_data(const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { Frequency_Trace_Render_State* s = render_state(frame_view); Frequency_Trace_Input_Data* d = render_input_data(frame_view); if (!s || !d) return; auto time_axis = s->time_axis.lock(); auto value_axis = s->value_axis.lock(); if (!time_axis || !value_axis) return; auto timeline = Time_Axis_Render_Access::capture_timeline(time_axis.get(), snapshot); if (!timeline) return; if (point_count != timeline->visible_time_point_count) resize_power_buffer(timeline->visible_time_point_count); d->read_all([this](const Frequency_Sample& data) { push_power(data); }); snapshot_power_buffer(); rebuild_prepared_points(*timeline); frame_view.consume_input(); } void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { Frequency_Trace_Render_State* s = render_state(frame_view); if (!s) return; auto time_axis = s->time_axis.lock(); auto value_axis = s->value_axis.lock(); if (!time_axis || !value_axis || prepared_points.empty()) return; Axis_Mapping_2D mapping = Axis_Render_Access::mapping(time_axis.get(), value_axis.get(), snapshot); canvas.save(); std::pmr::vector pixels(frame_memory_resource()); for (std::size_t i = 0; i < segment_offsets.size(); ++i) { const std::size_t begin = segment_offsets[i]; const std::size_t end = i + 1 < segment_offsets.size() ? segment_offsets[i + 1] : prepared_points.size(); if (end <= begin + 1) continue; pixels.clear(); pixels.reserve(end - begin); for (std::size_t point_index = begin; point_index < end; ++point_index) pixels.push_back(mapping.map(prepared_points[point_index])); Curve_Utils::draw_polyline(&canvas, pixels, s->pen); } canvas.restore(); } }; }