Files
Renderive/Core/plottable/Frequency_Trace_p.h
T
2026-08-02 18:58:35 +08:00

176 lines
6.9 KiB
C++

#pragma once
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory_resource>
#include <vector>
#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 <typename Handler>
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<Frequency_Trace, Frequency_Trace_Render_State, Frequency_Trace_Input_Data> {
Psc::StreamRingBuffer_ST buffer{memory_resource(Memory_Domain::Audio)};
std::pmr::vector<Frequency_Sample> data_snapshot{memory_resource(Memory_Domain::Audio)};
std::pmr::vector<PointF> prepared_points{memory_resource(Memory_Domain::Audio)};
std::pmr::vector<std::size_t> 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<int>(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<std::size_t>(data_count));
segment_offsets.reserve(static_cast<std::size_t>(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<std::size_t>(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<double>(coord), item.power);
previous_tick = item.tick;
has_previous_tick = true;
}
segment_count = static_cast<int>(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<PointF> 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();
}
};
}