仍有bug
This commit is contained in:
@@ -1,350 +1,45 @@
|
||||
#include "Web_Plot_Session.h"
|
||||
#include "Pixel_Frame.h"
|
||||
#include "render_2D/export.h"
|
||||
#include <renderive/error/Error_Policy.hpp>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include "render_2D/scene/Render_Scene_2D.h"
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace renderive::web {
|
||||
namespace {
|
||||
Color blend(Color first, Color second, double amount) {
|
||||
const auto channel = [amount](std::uint8_t a, std::uint8_t b) {
|
||||
return static_cast<std::uint8_t>(std::clamp(
|
||||
std::lround(a + (b - a) * amount), 0L, 255L));
|
||||
};
|
||||
return {
|
||||
channel(first.r, second.r), channel(first.g, second.g),
|
||||
channel(first.b, second.b), channel(first.a, second.a)
|
||||
};
|
||||
}
|
||||
Color_Map radio_color_map() {
|
||||
constexpr Color stops[] = {
|
||||
{3, 7, 18, 255}, {16, 52, 105, 255}, {23, 163, 184, 255},
|
||||
{238, 210, 91, 255}, {239, 68, 68, 255}
|
||||
};
|
||||
std::vector<Pixel> colors;
|
||||
colors.reserve(256);
|
||||
for (int index = 0; index < 256; ++index) {
|
||||
const double position = index / 255.0 * (std::size(stops) - 1);
|
||||
const auto stop = static_cast<std::size_t>(std::floor(position));
|
||||
const auto next = std::min(stop + 1, std::size(stops) - 1);
|
||||
colors.push_back(premultiply(blend(stops[stop], stops[next], position - stop)));
|
||||
}
|
||||
return Color_Map(std::move(colors));
|
||||
}
|
||||
Time_Of_Day current_time_of_day() {
|
||||
constexpr std::int64_t day_ms = 24LL * 60LL * 60LL * 1000LL;
|
||||
const auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
return {now % day_ms};
|
||||
}
|
||||
double gaussian(double x, double center, double width) {
|
||||
const double normalized = (x - center) / width;
|
||||
return std::exp(-0.5 * normalized * normalized);
|
||||
}
|
||||
} // namespace
|
||||
struct Web_Plot_Session::Impl {
|
||||
Render_Scene_2D plot;
|
||||
renderive_Owner<Frequency_Axis> spectrum_frequency_axis;
|
||||
renderive_Owner<Axis> spectrum_power_axis;
|
||||
renderive_Owner<Frequency_Axis> waterfall_frequency_axis;
|
||||
renderive_Owner<Time_Axis> waterfall_time_axis;
|
||||
renderive_Owner<Spectrum> spectrum;
|
||||
renderive_Owner<Waterfall> waterfall;
|
||||
renderive_Owner<Selection_Rectangle_Overlay> selection;
|
||||
Demo_Mode mode = Demo_Mode::Fm;
|
||||
double gain_db = 8.0;
|
||||
std::uint64_t frame_index{};
|
||||
std::mutex mutex;
|
||||
Impl() {
|
||||
plot.init();
|
||||
plot.set_background_color({3, 7, 18, 255});
|
||||
if (!plot.set_max_render_fps(30.0))
|
||||
::renderive::error::unexpected<std::logic_error>(
|
||||
"validated web plot frame rate was rejected");
|
||||
plot.set_viewport_size({960, 600});
|
||||
const auto root = plot.root_renderable();
|
||||
constexpr Color axis_color{93, 116, 151, 255};
|
||||
const Range initial_frequency{101'300'000.0, 103'700'000.0};
|
||||
{
|
||||
auto attach = plot.attach_builder();
|
||||
const auto make_group = [&](std::string name) {
|
||||
auto group = detail::make_renderable_group(true);
|
||||
group->set_object_name(std::move(name));
|
||||
attach.attach(group);
|
||||
attach.add_display_parent(group, root);
|
||||
attach.add_dependency_parent(group, root);
|
||||
return group;
|
||||
};
|
||||
const auto data = make_group("Web_Data");
|
||||
const auto axes = make_group("Web_Axes");
|
||||
const auto overlay = make_group("Web_Overlay");
|
||||
axes->set_cache_mode(Renderable_Cache_Mode::Local_Pixel);
|
||||
spectrum_frequency_axis =
|
||||
Frequency_Axis::Builder{&attach}
|
||||
.set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal)
|
||||
.set<&Frequency_Axis::Properties::coordinates>(initial_frequency)
|
||||
.set<&Frequency_Axis::Properties::precision>(2)
|
||||
.set<&Frequency_Axis::Properties::tick_length>(8)
|
||||
.set<&Frequency_Axis::Properties::sub_tick_length>(4)
|
||||
.set<&Frequency_Axis::Properties::color>(axis_color)
|
||||
.set<&Frequency_Axis::Properties::wheel>(true)
|
||||
.set<&Frequency_Axis::Properties::drag>(true)
|
||||
.build(axes);
|
||||
spectrum_power_axis =
|
||||
Axis::Builder{&attach}
|
||||
.set<&Axis::Properties::orientation>(Orientation::Vertical)
|
||||
.set<&Axis::Properties::coordinates>(Range{-20.0, -120.0})
|
||||
.set<&Axis::Properties::precision>(0)
|
||||
.set<&Axis::Properties::tick_length>(-8)
|
||||
.set<&Axis::Properties::sub_tick_length>(-4)
|
||||
.set<&Axis::Properties::color>(axis_color)
|
||||
.set<&Axis::Properties::unit_text>("dBm")
|
||||
.build(axes);
|
||||
waterfall_frequency_axis =
|
||||
Frequency_Axis::Builder{&attach}
|
||||
.set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal)
|
||||
.set<&Frequency_Axis::Properties::coordinates>(initial_frequency)
|
||||
.set<&Frequency_Axis::Properties::precision>(2)
|
||||
.set<&Frequency_Axis::Properties::tick_length>(8)
|
||||
.set<&Frequency_Axis::Properties::sub_tick_length>(4)
|
||||
.set<&Frequency_Axis::Properties::color>(axis_color)
|
||||
.set<&Frequency_Axis::Properties::wheel>(true)
|
||||
.set<&Frequency_Axis::Properties::drag>(true)
|
||||
.build(axes);
|
||||
waterfall_time_axis =
|
||||
Time_Axis::Builder{&attach}
|
||||
.set<&Time_Axis::Properties::orientation>(Orientation::Vertical)
|
||||
.set<&Time_Axis::Properties::visible_count>(72)
|
||||
.set<&Time_Axis::Properties::tick_label_spacing_px>(34)
|
||||
.set<&Time_Axis::Properties::format>("mm:ss")
|
||||
.set<&Time_Axis::Properties::tick_length>(-8)
|
||||
.set<&Time_Axis::Properties::sub_tick_length>(-4)
|
||||
.set<&Time_Axis::Properties::color>(axis_color)
|
||||
.build(axes);
|
||||
spectrum = Spectrum::Builder{&attach}
|
||||
.set<&Spectrum::Properties::frequency_range>(initial_frequency)
|
||||
.set<&Spectrum::Properties::frequency_point_size>(768)
|
||||
.set<&Spectrum::Properties::center_frequency>(102'500'000.0)
|
||||
.set<&Spectrum::Properties::sweep_frequency_range>(Range{102'200'000.0, 102'800'000.0})
|
||||
.set<&Spectrum::Properties::max_marker_visible>(true)
|
||||
.set<&Spectrum::Properties::sweep_region_visible>(true)
|
||||
.set<&Spectrum::Properties::interpolation_mode>(Line_Interpolation_Mode::Cubic_Value)
|
||||
.build(data, spectrum_frequency_axis, spectrum_power_axis);
|
||||
spectrum->set<&Spectrum::Properties::current_pen>(Pen{
|
||||
Color{57, 224, 177, 255}, 2.0, Line_Style::Solid,
|
||||
Line_Cap::Round, Line_Join::Round
|
||||
});
|
||||
spectrum->set<&Spectrum::Properties::current_brush>(Brush{Color{31, 174, 145, 32}, Brush_Style::Solid});
|
||||
spectrum->set<&Spectrum::Properties::max_pen>(Pen{Color{250, 204, 21, 210}, 1.0});
|
||||
spectrum->set<&Spectrum::Properties::middle_frequency_pen>(Pen{
|
||||
Color{56, 189, 248, 220}, 1.0,
|
||||
Line_Style::Dash
|
||||
});
|
||||
waterfall = Waterfall::Builder{&attach}
|
||||
.set<&Waterfall::Properties::frequency_range>(initial_frequency)
|
||||
.set<&Waterfall::Properties::power_range>(Range{-120.0, -20.0})
|
||||
.set<&Waterfall::Properties::frequency_bin_count>(768)
|
||||
.set<&Waterfall::Properties::interpolation_mode>(Image_Interpolation_Mode::Bilinear)
|
||||
.set<&Waterfall::Properties::color_map>(radio_color_map())
|
||||
.build(data, waterfall_frequency_axis, waterfall_time_axis);
|
||||
selection = Selection_Rectangle_Overlay::Builder{&attach}
|
||||
.set<&Selection_Rectangle_Overlay::Properties::selection_brush>(Brush{Color{56, 189, 248, 36}, Brush_Style::Solid})
|
||||
.set<&Selection_Rectangle_Overlay::Properties::selection_border_pen>(Pen{Color{125, 211, 252, 230}, 1.0, Line_Style::Dash})
|
||||
.build(overlay, spectrum_frequency_axis, spectrum_power_axis);
|
||||
}
|
||||
apply_layout(plot.viewport_size());
|
||||
plot.activate_view();
|
||||
update_model();
|
||||
const auto initial_render = plot.render_frame(true);
|
||||
if (!initial_render || *initial_render != Plot_Render_Status::rendered)
|
||||
plot.request_redraw();
|
||||
}
|
||||
~Impl() {
|
||||
plot.deactivate_view();
|
||||
}
|
||||
void apply_layout(Size viewport) {
|
||||
const int left = viewport.width < 620 ? 54 : 72;
|
||||
const int right = viewport.width < 620 ? 44 : 70;
|
||||
const int top = 16;
|
||||
const int bottom = viewport.height < 480 ? 24 : 32;
|
||||
const int gap = viewport.height < 480 ? 36 : 48;
|
||||
const int content_width = std::max(1, viewport.width - left - right);
|
||||
const int available_height = std::max(2, viewport.height - top - bottom - gap);
|
||||
const int spectrum_height = std::max(1, available_height * 43 / 100);
|
||||
const int waterfall_y = top + spectrum_height + gap;
|
||||
const int waterfall_height = std::max(1, viewport.height - waterfall_y - bottom);
|
||||
spectrum_frequency_axis->update([&](Axis_Properties& state) {
|
||||
state.x = left;
|
||||
state.y = top + spectrum_height;
|
||||
state.pixel_length = static_cast<std::size_t>(content_width);
|
||||
});
|
||||
spectrum_power_axis->update([&](Axis_Properties& state) {
|
||||
state.x = left;
|
||||
state.y = top;
|
||||
state.pixel_length = static_cast<std::size_t>(spectrum_height);
|
||||
});
|
||||
waterfall_frequency_axis->update([&](Axis_Properties& state) {
|
||||
state.x = left;
|
||||
state.y = waterfall_y + waterfall_height;
|
||||
state.pixel_length = static_cast<std::size_t>(content_width);
|
||||
});
|
||||
waterfall_time_axis->update([&](auto& state) {
|
||||
state.x = left;
|
||||
state.y = waterfall_y;
|
||||
state.pixel_length = static_cast<std::size_t>(waterfall_height);
|
||||
});
|
||||
}
|
||||
void update_model() {
|
||||
constexpr int sample_count = 768;
|
||||
const double time = static_cast<double>(frame_index) * 0.075;
|
||||
std::vector<double> samples(sample_count);
|
||||
for (int index = 0; index < sample_count; ++index) {
|
||||
const double x = static_cast<double>(index) / (sample_count - 1);
|
||||
const double noise = std::sin(index * 12.9898 + frame_index * 0.371) *
|
||||
std::sin(index * 0.137 + frame_index * 0.071);
|
||||
double signal{};
|
||||
switch (mode) {
|
||||
case Demo_Mode::Am:
|
||||
signal = 66.0 * gaussian(x, 0.5, 0.008) +
|
||||
39.0 * gaussian(x, 0.42, 0.018) +
|
||||
39.0 * gaussian(x, 0.58, 0.018);
|
||||
break;
|
||||
case Demo_Mode::Fm: {
|
||||
const double moving = 0.5 + std::sin(time) * 0.035;
|
||||
signal = 58.0 * gaussian(x, moving, 0.055) +
|
||||
25.0 * gaussian(x, moving - 0.11, 0.023) +
|
||||
25.0 * gaussian(x, moving + 0.11, 0.023);
|
||||
break;
|
||||
}
|
||||
case Demo_Mode::Usb:
|
||||
signal = 61.0 * gaussian(x, 0.57, 0.045) +
|
||||
28.0 * gaussian(x, 0.68, 0.025);
|
||||
break;
|
||||
case Demo_Mode::Lsb:
|
||||
signal = 61.0 * gaussian(x, 0.43, 0.045) +
|
||||
28.0 * gaussian(x, 0.32, 0.025);
|
||||
break;
|
||||
}
|
||||
samples[index] = std::clamp(-110.0 + noise * 4.5 + signal + gain_db,
|
||||
-120.0, -20.0);
|
||||
}
|
||||
spectrum->update_samples(samples);
|
||||
if ((frame_index & 1U) == 0U)
|
||||
waterfall->append_row(current_time_of_day(), samples);
|
||||
++frame_index;
|
||||
}
|
||||
void set_center_frequency(double megahertz) {
|
||||
const double center = megahertz * 1'000'000.0;
|
||||
const double bandwidth = spectrum_frequency_axis->get<&Axis_Properties::coordinates>().size();
|
||||
const Range range{center - bandwidth * 0.5, center + bandwidth * 0.5};
|
||||
spectrum_frequency_axis->set<&Axis_Properties::coordinates>(range);
|
||||
waterfall_frequency_axis->set<&Axis_Properties::coordinates>(range);
|
||||
spectrum->set<&Spectrum::Properties::frequency_range>(range);
|
||||
spectrum->set<&Spectrum::Properties::center_frequency>(center);
|
||||
spectrum->set<&Spectrum::Properties::sweep_frequency_range>(Range{
|
||||
center - bandwidth * 0.125,
|
||||
center + bandwidth * 0.125
|
||||
});
|
||||
waterfall->set<&Waterfall::Properties::frequency_range>(range);
|
||||
}
|
||||
void set_bandwidth(double kilohertz) {
|
||||
const double center = spectrum->get<&Spectrum::Properties::center_frequency>();
|
||||
const double bandwidth = kilohertz * 1000.0;
|
||||
const Range range{center - bandwidth * 0.5, center + bandwidth * 0.5};
|
||||
spectrum_frequency_axis->set<&Axis_Properties::coordinates>(range);
|
||||
waterfall_frequency_axis->set<&Axis_Properties::coordinates>(range);
|
||||
spectrum->set<&Spectrum::Properties::frequency_range>(range);
|
||||
spectrum->set<&Spectrum::Properties::sweep_frequency_range>(Range{
|
||||
center - bandwidth * 0.125,
|
||||
center + bandwidth * 0.125
|
||||
});
|
||||
waterfall->set<&Waterfall::Properties::frequency_range>(range);
|
||||
}
|
||||
std::optional<std::string> render_pixels() {
|
||||
if (!plot.view_active())
|
||||
return std::nullopt;
|
||||
update_model();
|
||||
const auto render = plot.render_frame(true);
|
||||
if (!render || *render != Plot_Render_Status::rendered)
|
||||
return std::nullopt;
|
||||
std::string pixels;
|
||||
const Color background = plot.background_color();
|
||||
const std::uint64_t sequence = plot.frame_status().latest_sequence;
|
||||
plot.with_frame([&pixels, background, sequence](Image_View image) {
|
||||
pixels = encode_pixel_frame(image, sequence, background);
|
||||
});
|
||||
return pixels.empty() ? std::nullopt : std::optional<std::string>(std::move(pixels));
|
||||
Size viewport{960, 600};
|
||||
std::uint64_t sequence{};
|
||||
std::unique_ptr<Render_Scene_2D> scene;
|
||||
|
||||
Impl() { rebuild(); }
|
||||
void rebuild() {
|
||||
Render_Scene_2D::State state;
|
||||
state.viewport = viewport;
|
||||
scene = Render_Scene_2D::Builder{state}.build(
|
||||
std::make_shared<Low_Latency_Render_Scene_2D_Strategy>());
|
||||
scene->activate_view();
|
||||
}
|
||||
std::optional<Web_Response> handle(const Web_Event& event) {
|
||||
std::lock_guard lock(mutex);
|
||||
return std::visit(
|
||||
[this](const auto& value) -> std::optional<Web_Response> {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_same_v<T, Frame_Request>) {
|
||||
auto pixels = render_pixels();
|
||||
if (pixels)
|
||||
return Web_Response{Web_Response_Type::Pixels, std::move(*pixels)};
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Viewport_Resize>) {
|
||||
const Size previous = plot.viewport_size();
|
||||
plot.set_viewport_size(value.size);
|
||||
apply_layout(value.size);
|
||||
Resize_Event resized;
|
||||
resized.old_size = previous;
|
||||
resized.new_size = value.size;
|
||||
plot.dispatch_event(resized);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Event>) {
|
||||
if (value.type == Event_Type::Show)
|
||||
plot.activate_view();
|
||||
else if (value.type == Event_Type::Hide)
|
||||
plot.deactivate_view();
|
||||
plot.dispatch_event(value);
|
||||
}
|
||||
else if constexpr (Event_Object<T>) {
|
||||
plot.dispatch_event(value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Set_Demo_Mode>) {
|
||||
mode = value.mode;
|
||||
plot.request_redraw();
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Set_Center_Frequency>) {
|
||||
set_center_frequency(value.megahertz);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Set_Bandwidth>) {
|
||||
set_bandwidth(value.kilohertz);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Set_Gain>) {
|
||||
gain_db = value.decibels;
|
||||
plot.request_redraw();
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Set_Max_Hold>) {
|
||||
spectrum->set<&Spectrum::Properties::max_hold_visible>(value.enabled);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Set_Smoothing>) {
|
||||
spectrum->set<&Spectrum::Properties::interpolation_mode>(
|
||||
value.enabled
|
||||
? Line_Interpolation_Mode::Cubic_Value
|
||||
: Line_Interpolation_Mode::Nearest_Sample);
|
||||
waterfall->set<&Waterfall::Properties::interpolation_mode>(
|
||||
value.enabled
|
||||
? Image_Interpolation_Mode::Bilinear
|
||||
: Image_Interpolation_Mode::Nearest);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Clear_Selection>) {
|
||||
selection->clear_selected_regions();
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
event);
|
||||
return std::visit([this](const auto& value) -> std::optional<Web_Response> {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::same_as<T, Frame_Request>) {
|
||||
const auto result = scene->render_frame(true);
|
||||
if (!result || scene->wait_for_render() != Scene_Render_Result::none)
|
||||
return std::nullopt;
|
||||
std::string pixels;
|
||||
scene->with_frame([&](Image_View image) {
|
||||
pixels = encode_pixel_frame(image, ++sequence);
|
||||
});
|
||||
if (pixels.empty()) return std::nullopt;
|
||||
return Web_Response{Web_Response_Type::Pixels,
|
||||
std::move(pixels)};
|
||||
} else if constexpr (std::same_as<T, Viewport_Resize>) {
|
||||
viewport = value.size;
|
||||
rebuild();
|
||||
} else if constexpr (std::derived_from<T, Event>) {
|
||||
scene->dispatch_event(value);
|
||||
}
|
||||
return std::nullopt;
|
||||
}, event);
|
||||
}
|
||||
};
|
||||
Web_Plot_Session::Web_Plot_Session() : impl_(std::make_unique<Impl>()) {}
|
||||
|
||||
Reference in New Issue
Block a user