恢复成自定义协议了
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
#include "Web_Plot_Session.h"
|
||||
#include "Pixel_Frame.h"
|
||||
#include "render_2D/export.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#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 {
|
||||
Plot_Core plot;
|
||||
std::shared_ptr<Frequency_Axis> spectrum_frequency_axis;
|
||||
std::shared_ptr<Axis> spectrum_power_axis;
|
||||
std::shared_ptr<Frequency_Axis> waterfall_frequency_axis;
|
||||
std::shared_ptr<Time_Axis> waterfall_time_axis;
|
||||
std::shared_ptr<Spectrum> spectrum;
|
||||
std::shared_ptr<Waterfall> waterfall;
|
||||
std::shared_ptr<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});
|
||||
plot.set_max_render_fps(30.0);
|
||||
plot.set_viewport_size({960, 600});
|
||||
const auto root = plot.root_renderable();
|
||||
const auto data = plot.create_renderable_node(root, "Web_Data");
|
||||
const auto axes = plot.create_renderable_node(root, "Web_Axes");
|
||||
const auto overlay = plot.create_renderable_node(root, "Web_Overlay");
|
||||
axes->set_cache_mode(Renderable_Cache_Mode::Local_Pixel);
|
||||
constexpr Color axis_color{93, 116, 151, 255};
|
||||
const Range initial_frequency{101'300'000.0, 103'700'000.0};
|
||||
spectrum_frequency_axis =
|
||||
Frequency_Axis::Builder(axes, Orientation::Horizontal)
|
||||
.set_coord_range(initial_frequency)
|
||||
.set_label_precision(2)
|
||||
.set_tick_length(8)
|
||||
.set_sub_tick_length(4)
|
||||
.set_color(axis_color)
|
||||
.set_use_wheel(true)
|
||||
.set_use_drag(true)
|
||||
.build();
|
||||
spectrum_power_axis =
|
||||
Axis::Builder(axes, Orientation::Vertical)
|
||||
.set_coord_range({-20.0, -120.0})
|
||||
.set_label_precision(0)
|
||||
.set_tick_length(-8)
|
||||
.set_sub_tick_length(-4)
|
||||
.set_color(axis_color)
|
||||
.set_unit_text("dBm")
|
||||
.build();
|
||||
waterfall_frequency_axis =
|
||||
Frequency_Axis::Builder(axes, Orientation::Horizontal)
|
||||
.set_coord_range(initial_frequency)
|
||||
.set_label_precision(2)
|
||||
.set_tick_length(8)
|
||||
.set_sub_tick_length(4)
|
||||
.set_color(axis_color)
|
||||
.set_use_wheel(true)
|
||||
.set_use_drag(true)
|
||||
.build();
|
||||
waterfall_time_axis =
|
||||
Time_Axis::Builder(axes, Orientation::Vertical)
|
||||
.set_visible_time_point_count(72)
|
||||
.set_tick_label_spacing_px(34)
|
||||
.set_time_format("mm:ss")
|
||||
.set_tick_length(-8)
|
||||
.set_sub_tick_length(-4)
|
||||
.set_color(axis_color)
|
||||
.build();
|
||||
spectrum = Spectrum::Builder(data, spectrum_frequency_axis, spectrum_power_axis)
|
||||
.set_frequency_range(initial_frequency)
|
||||
.set_frequency_point_size(768)
|
||||
.set_center_frequency(102'500'000.0)
|
||||
.set_sweep_frequency_range({102'200'000.0, 102'800'000.0})
|
||||
.set_max_marker_visible(true)
|
||||
.set_sweep_region_visible(true)
|
||||
.set_interpolation_mode(Line_Interpolation_Mode::Cubic_Value)
|
||||
.build();
|
||||
spectrum->set_current_pen({
|
||||
Color{57, 224, 177, 255}, 2.0, Line_Style::Solid,
|
||||
Line_Cap::Round, Line_Join::Round
|
||||
});
|
||||
spectrum->set_current_brush({Color{31, 174, 145, 32}, Brush_Style::Solid});
|
||||
spectrum->set_max_pen({Color{250, 204, 21, 210}, 1.0});
|
||||
spectrum->set_middle_frequency_pen({
|
||||
Color{56, 189, 248, 220}, 1.0,
|
||||
Line_Style::Dash
|
||||
});
|
||||
waterfall = Waterfall::Builder(data, waterfall_frequency_axis, waterfall_time_axis)
|
||||
.set_frequency_range(initial_frequency)
|
||||
.set_power_range({-120.0, -20.0})
|
||||
.set_frequency_bin_count(768)
|
||||
.set_interpolation_mode(Image_Interpolation_Mode::Bilinear)
|
||||
.set_color_map(radio_color_map())
|
||||
.build();
|
||||
selection = Selection_Rectangle_Overlay::Builder(
|
||||
overlay, spectrum_frequency_axis, spectrum_power_axis)
|
||||
.set_rect_brush({Color{56, 189, 248, 36}, Brush_Style::Solid})
|
||||
.set_border_pen({Color{125, 211, 252, 230}, 1.0, Line_Style::Dash})
|
||||
.build();
|
||||
apply_layout(plot.viewport_size());
|
||||
plot.activate_view();
|
||||
update_model();
|
||||
(void)plot.render_frame(true);
|
||||
}
|
||||
~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->set_x(left);
|
||||
spectrum_frequency_axis->set_y(top + spectrum_height);
|
||||
spectrum_frequency_axis->set_pixel_length(static_cast<std::size_t>(content_width));
|
||||
spectrum_power_axis->set_x(left);
|
||||
spectrum_power_axis->set_y(top);
|
||||
spectrum_power_axis->set_pixel_length(static_cast<std::size_t>(spectrum_height));
|
||||
waterfall_frequency_axis->set_x(left);
|
||||
waterfall_frequency_axis->set_y(waterfall_y + waterfall_height);
|
||||
waterfall_frequency_axis->set_pixel_length(static_cast<std::size_t>(content_width));
|
||||
waterfall_time_axis->set_x(left);
|
||||
waterfall_time_axis->set_y(waterfall_y);
|
||||
waterfall_time_axis->set_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->coord_range().size();
|
||||
const Range range{center - bandwidth * 0.5, center + bandwidth * 0.5};
|
||||
spectrum_frequency_axis->set_coord_range(range);
|
||||
waterfall_frequency_axis->set_coord_range(range);
|
||||
spectrum->set_frequency_range(range);
|
||||
spectrum->set_center_frequency(center);
|
||||
spectrum->set_sweep_frequency_range({
|
||||
center - bandwidth * 0.125,
|
||||
center + bandwidth * 0.125
|
||||
});
|
||||
waterfall->set_frequency_range(range);
|
||||
}
|
||||
void set_bandwidth(double kilohertz) {
|
||||
const double center = spectrum->center_frequency();
|
||||
const double bandwidth = kilohertz * 1000.0;
|
||||
const Range range{center - bandwidth * 0.5, center + bandwidth * 0.5};
|
||||
spectrum_frequency_axis->set_coord_range(range);
|
||||
waterfall_frequency_axis->set_coord_range(range);
|
||||
spectrum->set_frequency_range(range);
|
||||
spectrum->set_sweep_frequency_range({
|
||||
center - bandwidth * 0.125,
|
||||
center + bandwidth * 0.125
|
||||
});
|
||||
waterfall->set_frequency_range(range);
|
||||
}
|
||||
std::optional<std::string> render_pixels() {
|
||||
if (!plot.view_active())
|
||||
return std::nullopt;
|
||||
update_model();
|
||||
if (!plot.render_frame(true))
|
||||
return std::nullopt;
|
||||
std::string pixels;
|
||||
const Color background = plot.background_color();
|
||||
plot.with_frame([&pixels, background](Image_View image) {
|
||||
pixels = encode_pixel_frame(image, background);
|
||||
});
|
||||
return pixels.empty() ? std::nullopt : std::optional<std::string>(std::move(pixels));
|
||||
}
|
||||
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 (std::is_base_of_v<Event, T>) {
|
||||
plot.dispatch_event(value);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Set_Demo_Mode>) {
|
||||
mode = value.mode;
|
||||
plot.notify_model_dirty();
|
||||
}
|
||||
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.notify_model_dirty();
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Set_Max_Hold>) {
|
||||
spectrum->set_max_hold_visible(value.enabled);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Set_Smoothing>) {
|
||||
spectrum->set_interpolation_mode(
|
||||
value.enabled
|
||||
? Line_Interpolation_Mode::Cubic_Value
|
||||
: Line_Interpolation_Mode::Nearest_Sample);
|
||||
waterfall->set_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);
|
||||
}
|
||||
};
|
||||
Web_Plot_Session::Web_Plot_Session() : impl_(std::make_unique<Impl>()) {}
|
||||
Web_Plot_Session::~Web_Plot_Session() = default;
|
||||
std::optional<Web_Response> Web_Plot_Session::handle(const Web_Event& event) {
|
||||
return impl_->handle(event);
|
||||
}
|
||||
} // namespace renderive::web
|
||||
Reference in New Issue
Block a user