176 lines
6.9 KiB
C++
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();
|
|
}
|
|
};
|
|
}
|