#include #include #include "../base/Frame_Memory.h" #include "../base/Memory.h" #include "../primitive/Curve.h" #include "../primitive/Curve_Utils_p.h" #include "../primitive/Label.h" #include "../primitive/Marker.h" #include "Spectrum_p.h" namespace renderive { RENDERIVE_DEFINE_RENDERABLE_BINDING(Spectrum, Spectrum_Private, Spectrum_Render_State, Spectrum_Input_Data, "Spectrum") std::shared_ptr Spectrum::frequency_axis() { return d()->edit_state_value(&Spectrum_Render_State::frequency_axis).lock(); } void Spectrum::set_frequency_axis(std::shared_ptr value) { d()->set_state_value(&Spectrum_Render_State::frequency_axis, std::weak_ptr(value)); } std::shared_ptr Spectrum::power_axis() { return d()->edit_state_value(&Spectrum_Render_State::power_axis).lock(); } void Spectrum::set_power_axis(std::shared_ptr value) { d()->set_state_value(&Spectrum_Render_State::power_axis, std::weak_ptr(value)); } int Spectrum::frequency_point_size() { return d()->edit_state_value(&Spectrum_Render_State::frequency_point_size); } Range Spectrum::frequency_range() { return d()->edit_state_value(&Spectrum_Render_State::frequency_range); } double Spectrum::center_frequency() { return d()->edit_state_value(&Spectrum_Render_State::center_frequency); } void Spectrum::set_center_frequency(double value) { d()->set_state_value(&Spectrum_Render_State::center_frequency, value); } Range Spectrum::sweep_frequency_range() { return d()->edit_state_value(&Spectrum_Render_State::sweep_frequency_range); } void Spectrum::set_sweep_frequency_range(Range value) { if (value.length() == 0.0) return; d()->set_state_value(&Spectrum_Render_State::sweep_frequency_range, std::move(value)); } bool Spectrum::max_hold_visible() { return d()->edit_state_value(&Spectrum_Render_State::max_hold_visible); } void Spectrum::set_max_hold_visible(bool value) { d()->set_state_value(&Spectrum_Render_State::max_hold_visible, value); } bool Spectrum::min_hold_visible() { return d()->edit_state_value(&Spectrum_Render_State::min_hold_visible); } void Spectrum::set_min_hold_visible(bool value) { d()->set_state_value(&Spectrum_Render_State::min_hold_visible, value); } bool Spectrum::max_marker_visible() { return d()->edit_state_value(&Spectrum_Render_State::max_marker_visible); } void Spectrum::set_max_marker_visible(bool value) { d()->set_state_value(&Spectrum_Render_State::max_marker_visible, value); } bool Spectrum::use_min_marker() { return d()->edit_state_value(&Spectrum_Render_State::use_min_marker); } void Spectrum::set_use_min_marker(bool value) { d()->set_state_value(&Spectrum_Render_State::use_min_marker, value); } bool Spectrum::sweep_region_visible() { return d()->edit_state_value(&Spectrum_Render_State::sweep_region_visible); } void Spectrum::set_sweep_region_visible(bool value) { d()->set_state_value(&Spectrum_Render_State::sweep_region_visible, value); } bool Spectrum::visible_range_only() { return d()->edit_state_value(&Spectrum_Render_State::visible_range_only); } void Spectrum::set_visible_range_only(bool value) { d()->set_state_value(&Spectrum_Render_State::visible_range_only, value); } Line_Interpolation_Mode Spectrum::interpolation_mode() { return d()->edit_state_value(&Spectrum_Render_State::interpolation_mode); } void Spectrum::set_interpolation_mode(Line_Interpolation_Mode value) { d()->set_state_value(&Spectrum_Render_State::interpolation_mode, value); } Line_Sampling_Strategy Spectrum::sample_mode() { return d()->edit_state_value(&Spectrum_Render_State::sampling_strategy); } void Spectrum::set_sample_mode(Line_Sampling_Strategy value) { d()->set_state_value(&Spectrum_Render_State::sampling_strategy, value); } Brush Spectrum::max_brush() { return d()->edit_state_value(&Spectrum_Render_State::max_brush); } void Spectrum::set_max_brush(Brush value) { d()->set_state_value(&Spectrum_Render_State::max_brush, std::move(value)); } Brush Spectrum::current_brush() { return d()->edit_state_value(&Spectrum_Render_State::current_brush); } void Spectrum::set_current_brush(Brush value) { d()->set_state_value(&Spectrum_Render_State::current_brush, std::move(value)); } Brush Spectrum::min_brush() { return d()->edit_state_value(&Spectrum_Render_State::min_brush); } void Spectrum::set_min_brush(Brush value) { d()->set_state_value(&Spectrum_Render_State::min_brush, std::move(value)); } Pen Spectrum::max_pen() { return d()->edit_state_value(&Spectrum_Render_State::max_pen); } void Spectrum::set_max_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::max_pen, std::move(value)); } Pen Spectrum::current_pen() { return d()->edit_state_value(&Spectrum_Render_State::cur_pen); } void Spectrum::set_current_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::cur_pen, std::move(value)); } Pen Spectrum::min_pen() { return d()->edit_state_value(&Spectrum_Render_State::min_pen); } void Spectrum::set_min_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::min_pen, std::move(value)); } Pen Spectrum::selected_marker_pen() { return d()->edit_state_value(&Spectrum_Render_State::selected_marker_pen); } void Spectrum::set_selected_marker_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::selected_marker_pen, std::move(value)); } Pen Spectrum::marker_pen() { return d()->edit_state_value(&Spectrum_Render_State::marker_pen); } void Spectrum::set_marker_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::marker_pen, std::move(value)); } Pen Spectrum::middle_frequency_pen() { return d()->edit_state_value(&Spectrum_Render_State::middle_frequency_pen); } void Spectrum::set_middle_frequency_pen(Pen value) { d()->set_state_value(&Spectrum_Render_State::middle_frequency_pen, std::move(value)); } Brush Spectrum::sweep_region_brush() { return d()->edit_state_value(&Spectrum_Render_State::sweep_region_brush); } void Spectrum::set_sweep_region_brush(Brush value) { d()->set_state_value(&Spectrum_Render_State::sweep_region_brush, std::move(value)); } void Spectrum::set_frequency_point_size(int frequency_point_size) { Render_Edit_Lease edit(d()); edit->frequency_point_size = std::max(1, frequency_point_size); } void Spectrum::set_frequency_range(Range frequency_range) { if (frequency_range.length() == 0.0) return; Render_Edit_Lease edit(d()); edit->frequency_range = frequency_range; } Spectrum::Builder::Builder(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr power_axis) { ASSERT(parent, "Spectrum build error! parent == nullptr"); ASSERT(frequency_axis && power_axis, "Spectrum build error! axis == nullptr"); ASSERT(frequency_axis->attached_to_plot(), "Spectrum build error! frequency_axis->plot == nullptr"); ASSERT(frequency_axis->shares_plot_with(*power_axis), "Spectrum build error! frequency_axis->plot != power_axis->plot"); ASSERT(parent->shares_plot_with(*frequency_axis), "Spectrum build error! parent->plot != axis->plot"); ASSERT(frequency_axis->orientation() != power_axis->orientation(), "Spectrum build error! axes must be orthogonal"); this->parent = parent; this->frequency_axis = frequency_axis; this->power_axis = power_axis; } std::shared_ptr Spectrum::Builder::build() { auto ret = renderive::make_shared(); ret->init(parent); Spectrum_Private* pd = ret->d(); Render_Edit_Lease edit(pd); Spectrum_Render_State* sc = edit.operator->(); PROP_R(std::weak_ptr, frequency_axis) PROP_R(std::weak_ptr, power_axis); PROP_R(int, frequency_point_size); PROP_R(Range, frequency_range); PROP_R(double, center_frequency); PROP_R(Range, sweep_frequency_range); PROP_R(bool, max_hold_visible); PROP_R(bool, min_hold_visible); PROP_R(bool, max_marker_visible); PROP_R(bool, use_min_marker); PROP_R(bool, sweep_region_visible); PROP_R(bool, visible_range_only); PROP_R(Line_Interpolation_Mode, interpolation_mode); PROP_R(Line_Sampling_Strategy, sampling_strategy); PROP_R(Brush, max_brush); PROP_R(Brush, current_brush); PROP_R(Brush, min_brush); PROP_R(Pen, max_pen); PROP_R(Pen, cur_pen); PROP_R(Pen, min_pen); PROP_R(Pen, selected_marker_pen); PROP_R(Pen, marker_pen); PROP_R(Pen, middle_frequency_pen); PROP_R(Brush, sweep_region_brush); sc->frequency_point_size = std::max(1, sc->frequency_point_size); if (sc->frequency_range.length() == 0.0) sc->frequency_range = {0.0, 1.0}; if (sc->sweep_frequency_range.length() == 0.0) sc->sweep_frequency_range = {0.0, 1.0}; return ret; } void Spectrum_Private::update_frequency_point_size(int frequency_point_size, Range frequency_range) { if (frequency_point_size <= 0) { cache_frequency_point_size = frequency_point_size; cache_frequency_range = frequency_range; has_power_data = false; max_powers.clear(); cur_powers.clear(); min_powers.clear(); frequency.clear(); return; } cache_frequency_point_size = frequency_point_size; cache_frequency_range = frequency_range; has_power_data = false; max_powers.resize(frequency_point_size); cur_powers.resize(frequency_point_size); min_powers.resize(frequency_point_size); frequency.resize(frequency_point_size); if (frequency_point_size == 1) { frequency[0] = frequency_range.origin; return; } double cur = frequency_range.origin; double step = frequency_range.length() / (frequency_point_size - 1); frequency[0] = frequency_range.origin; for (int i = 1; i < frequency_point_size; ++i) { cur += step; frequency[i] = cur; } } void Spectrum_Private::set_frequency_data(const std::pmr::vector& line_data, const std::pmr::vector* max_data, const std::pmr::vector* min_data, const Spectrum_Render_State* s) { if (s->frequency_point_size <= 0) return; if (line_data.size() != s->frequency_point_size) return; if ((max_data && max_data->size() != s->frequency_point_size) || (min_data && min_data->size() != s->frequency_point_size)) return; cur_powers.assign(line_data.begin(), line_data.end()); if (!has_power_data) { max_powers.assign(cur_powers.begin(), cur_powers.end()); min_powers.assign(cur_powers.begin(), cur_powers.end()); has_power_data = true; } double min = std::numeric_limits::max(); double max = std::numeric_limits::lowest(); int min_index = 0, max_index = 0; for (int i = 0; i < s->frequency_point_size; ++i) { double& cur_data = cur_powers[i]; if (s->max_hold_visible) { double hold_max = max_data ? max_data->at(i) : cur_data; if (hold_max > max_powers.at(i)) max_powers[i] = hold_max; } if (s->min_hold_visible) { double hold_min = min_data ? min_data->at(i) : cur_data; if (hold_min < min_powers.at(i)) min_powers[i] = hold_min; } if (s->max_marker_visible && cur_data > max) { max = cur_data; max_index = i; } if (s->use_min_marker && cur_data < min) { min = cur_data; min_index = i; } } max_marker_key = frequency[max_index]; max_marker_value = cur_powers[max_index]; min_marker_key = frequency[min_index]; min_marker_value = cur_powers[min_index]; } double Spectrum_Private::get_y(double x, bool& ok, Line_Interpolation_Mode mode) { if (cur_powers.size() < 2 || cache_frequency_range.length() == 0.0 || !cache_frequency_range.contains(x)) { ok = false; return -1; } double index = coordinate_to_source_index(cache_frequency_range, cur_powers.size(), x); ok = true; return interpolate_sample_value(index, cur_powers.size(), mode, [this](int i) { return cur_powers.at(i); }); } void Spectrum_Private::draw_power_curve(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s, const std::pmr::vector& powers, const Pen& pen, const Brush& brush) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis) return; if (powers.size() < 2 || s->frequency_range.length() == 0.0) return; Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get(), snapshot); std::pmr::vector points(frame_memory_resource()); int point_count = static_cast(powers.size()); if (s->sampling_strategy == Line_Sampling_Strategy::Pixel_Min_Max_Envelope) { points = Curve_Utils::build_pixel_envelope_points(mapping, s->frequency_range, point_count, point_count, s->visible_range_only, [&powers](int i) { return powers.at(i); }); } else { points = Curve_Utils::build_resampled_points(mapping, s->frequency_range, point_count, point_count, s->visible_range_only, s->interpolation_mode, [&powers](int i) { return powers.at(i); }); } canvas.save(); Curve_Utils::draw_polyline(&canvas, points, pen); Curve_Utils::draw_fill_to_value(&canvas, points, mapping, mapping.value.coord_range.target, brush); canvas.restore(); } void Spectrum_Private::update_marker_items(const Spectrum_Render_State* s, const Render_Frame_Snapshot& snapshot) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); prepared_marker_items.clear(); prepared_label_items.clear(); if (!frequency_axis || !power_axis) return; auto append_marker = [&](double frequency, double power, bool draw_line, bool selected) { Pen pen = selected ? s->selected_marker_pen : s->marker_pen; Marker_Item marker; marker.position = PointF(frequency, power); marker.radius = 2.0; marker.pen = pen; marker.mode = draw_line ? Marker_Mode::Domain_Line : Marker_Mode::Point; prepared_marker_items.push_back(marker); std::string frequency_text = std::to_string(frequency) + Axis_Render_Access::unit_text(frequency_axis.get(), snapshot); std::string power_text = std::to_string(power) + Axis_Render_Access::unit_text(power_axis.get(), snapshot); Label_Item frequency_label; frequency_label.position = marker.position; frequency_label.offset = PointF(0.0, -28.0); frequency_label.text = frequency_text; frequency_label.alignment = Text_Align::Horizontal_Center | Text_Align::Bottom; frequency_label.text_pen = pen; prepared_label_items.push_back(frequency_label); Label_Item power_label = frequency_label; power_label.offset = PointF(0.0, -14.0); power_label.text = power_text; prepared_label_items.push_back(power_label); }; if (s->max_marker_visible) append_marker(max_marker_key, max_marker_value, false, false); if (s->use_min_marker) append_marker(min_marker_key, min_marker_value, false, false); for (int i = 0; i < (int)s->marker_list.size(); ++i) { const Spectrum_Marker& custom_marker = s->marker_list.at(i); bool ok; double value = get_y(custom_marker.frequency, ok, s->interpolation_mode); if (ok) append_marker(custom_marker.frequency, value, custom_marker.is_line_marker, i == s->cur_select_index); } } void Spectrum_Private::draw_spectrum(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s) { if (s->sweep_region_visible) { s->draw_axis_rect(canvas, snapshot, s->sweep_frequency_range, s->center_frequency); } } void Spectrum_Private::draw_marker_items(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis) return; Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get(), snapshot); canvas.save(); for (const Marker_Item& item : prepared_marker_items) { PointF point = mapping.map(item.position); PointF domain_start = mapping.map(item.position.x, mapping.value.coord_range.origin); PointF domain_end = mapping.map(item.position.x, mapping.value.coord_range.target); PointF value_start = mapping.map(mapping.domain.coord_range.origin, item.position.y); PointF value_end = mapping.map(mapping.domain.coord_range.target, item.position.y); canvas.set_pen(item.pen); canvas.set_brush(item.brush); if (item.mode == Marker_Mode::Point) { canvas.draw_ellipse(point, item.radius, item.radius); } else if (item.mode == Marker_Mode::Domain_Line) { canvas.draw_line(domain_start, domain_end); } else if (item.mode == Marker_Mode::Value_Line) { canvas.draw_line(value_start, value_end); } else { canvas.draw_line(domain_start, domain_end); canvas.draw_line(value_start, value_end); } } canvas.restore(); } void Spectrum_Private::draw_label_items(Canvas& canvas, const Render_Frame_Snapshot& snapshot, Spectrum_Render_State* s) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis) return; Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get(), snapshot); for (const Label_Item& item : prepared_label_items) { if (item.text.empty()) continue; canvas.save(); canvas.set_font(item.font); Text_Metrics tm = canvas.measure_text(item.text); PointF anchor = mapping.map(item.position); PointF top_left{anchor.x + item.offset.x, anchor.y + item.offset.y}; if ((item.alignment & Text_Align::Horizontal_Center) == Text_Align::Horizontal_Center) top_left.x -= tm.width / 2.0; else if ((item.alignment & Text_Align::Right) == Text_Align::Right) top_left.x -= tm.width; if ((item.alignment & Text_Align::Vertical_Center) == Text_Align::Vertical_Center) top_left.y -= tm.height / 2.0; else if ((item.alignment & Text_Align::Bottom) == Text_Align::Bottom) top_left.y -= tm.height; RectF rect{top_left.x, top_left.y, tm.width, tm.height}; if (item.background_brush.style != Brush_Style::None || item.border_pen.style != Line_Style::None) { canvas.set_pen(item.border_pen); canvas.set_brush(item.background_brush); canvas.draw_rect(rect); } canvas.set_pen(item.text_pen); canvas.draw_text(rect, Text_Align::Left | Text_Align::Vertical_Center, item.text); canvas.restore(); } } void Spectrum_Private::draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) { auto s = render_state(frame_view); if (!s) return; draw_power_curve(canvas, snapshot, s, cur_powers, s->cur_pen, s->current_brush); draw_spectrum(canvas, snapshot, s); if (s->max_hold_visible) draw_power_curve(canvas, snapshot, s, max_powers, s->max_pen, s->max_brush); if (s->min_hold_visible) draw_power_curve(canvas, snapshot, s, min_powers, s->min_pen, s->min_brush); draw_marker_items(canvas, snapshot, s); draw_label_items(canvas, snapshot, s); if (s->can_show_hover(q())) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (frequency_axis && power_axis) s->draw_hover_tooltip(&canvas, frequency_axis.get(), power_axis.get(), q(), &snapshot); } } void Spectrum::update_samples(std::span line_data) { if (!ok()) return; Spectrum_Private* pd = d(); int frequency_point_size{}; Range frequency_range{}; Line_Interpolation_Mode interpolation_mode{}; bool use_max_line{}; bool use_min_line{}; { Render_State_Lease state(pd); frequency_point_size = state->frequency_point_size; frequency_range = state->frequency_range; interpolation_mode = state->interpolation_mode; use_max_line = state->max_hold_visible; use_min_line = state->min_hold_visible; } if (line_data.size() != frequency_point_size) return; Render_Input_Lease input(pd); input->push(line_data, frequency_range, interpolation_mode, use_max_line, use_min_line); } void Spectrum::update_samples(std::pmr::vector&& line_data) { if (!ok()) return; Spectrum_Private* pd = d(); int frequency_point_size{}; Range frequency_range{}; Line_Interpolation_Mode interpolation_mode{}; bool use_max_line{}; bool use_min_line{}; { Render_State_Lease state(pd); frequency_point_size = state->frequency_point_size; frequency_range = state->frequency_range; interpolation_mode = state->interpolation_mode; use_max_line = state->max_hold_visible; use_min_line = state->min_hold_visible; } if (line_data.size() != frequency_point_size) return; Render_Input_Lease input(pd); input->push(std::move(line_data), frequency_range, interpolation_mode, use_max_line, use_min_line); } Update_Ticket Spectrum::submit_data(std::span line_data) { auto update_state = make_update_state(); if (!ok()) { update_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); return Update_Ticket(update_state); } Spectrum_Private* pd = d(); int frequency_point_size{}; Range frequency_range{}; Line_Interpolation_Mode interpolation_mode{}; bool use_max_line{}; bool use_min_line{}; { Render_State_Lease state(pd); frequency_point_size = state->frequency_point_size; frequency_range = state->frequency_range; interpolation_mode = state->interpolation_mode; use_max_line = state->max_hold_visible; use_min_line = state->min_hold_visible; } if (line_data.size() != frequency_point_size) { update_state->complete_all(std::make_error_code(std::errc::invalid_argument), Update_Outcome::Cancelled); return Update_Ticket(update_state); } { Render_Input_Lease input(pd); input->push(line_data, frequency_range, interpolation_mode, use_max_line, use_min_line); input.set_completion(update_state); } update_state->complete_until(Update_Stage::Input_Released); return Update_Ticket(update_state); } Update_Ticket Spectrum::submit_data(std::pmr::vector&& line_data) { auto update_state = make_update_state(); if (!ok()) { update_state->complete_all(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled); return Update_Ticket(update_state); } Spectrum_Private* pd = d(); int frequency_point_size{}; Range frequency_range{}; Line_Interpolation_Mode interpolation_mode{}; bool use_max_line{}; bool use_min_line{}; { Render_State_Lease state(pd); frequency_point_size = state->frequency_point_size; frequency_range = state->frequency_range; interpolation_mode = state->interpolation_mode; use_max_line = state->max_hold_visible; use_min_line = state->min_hold_visible; } if (line_data.size() != frequency_point_size) { update_state->complete_all(std::make_error_code(std::errc::invalid_argument), Update_Outcome::Cancelled); return Update_Ticket(update_state); } { Render_Input_Lease input(pd); input->push(std::move(line_data), frequency_range, interpolation_mode, use_max_line, use_min_line); input.set_completion(update_state); } update_state->complete_until(Update_Stage::Input_Released); return Update_Ticket(update_state); } void Spectrum::add_custom_marker(double frequency) { Render_Edit_Lease edit(d()); edit->marker_list.push_back(Spectrum_Marker{frequency, false}); } void Spectrum::add_custom_line_marker(double frequency) { Render_Edit_Lease edit(d()); edit->marker_list.push_back(Spectrum_Marker{frequency, true}); } void Spectrum::remove_custom_marker(double frequency) { Render_Edit_Lease edit(d()); for (auto marker = edit->marker_list.begin(); marker != edit->marker_list.end(); ++marker) { if (std::abs(marker->frequency - frequency) < 1e-12) { edit->marker_list.erase(marker); break; } } } void Spectrum::remove_selected_marker() { Render_Edit_Lease edit(d()); if (edit->cur_select_index < 0 || edit->cur_select_index >= (int)edit->marker_list.size()) return; edit->marker_list.erase(edit->marker_list.begin() + edit->cur_select_index); edit->cur_select_index = -1; } void Spectrum::clear_custom_markers() { Render_Edit_Lease edit(d()); edit->marker_list.clear(); } double Spectrum::marker_frequency(int mark_index) { Render_State_Lease state(d()); if (!state || mark_index < 0 || mark_index >= static_cast(state->marker_list.size())) return -1; return state->marker_list[static_cast(mark_index)].frequency; } void Spectrum::set_marker_frequency(int marker_index, double frequency) { Render_Edit_Lease edit(d()); auto& markers = edit->marker_list; if (marker_index < 0 || marker_index >= (int)markers.size()) return; markers[marker_index].frequency = frequency; } void Spectrum::set_current_marker_frequency(double frequency) { Render_Edit_Lease edit(d()); edit->set_cur_frequency(frequency); } int Spectrum::selectable_line_marker_count() { Render_State_Lease state(d()); return state ? static_cast(state->marker_list.size()) : 0; } int Spectrum::selected_marker_index() { return d()->edit_state_value(&Spectrum_Render_State::cur_select_index); } void Spectrum::set_selected_marker_index(int marker_index) { Render_Edit_Lease edit(d()); edit->set_select_index(marker_index); } void Spectrum::select_next_marker() { Render_Edit_Lease edit(d()); edit->select_next_marker(); } void Spectrum::select_previous_marker() { Render_Edit_Lease edit(d()); edit->select_prev_marker(); } void Spectrum::clear_marker_selection() { Render_Edit_Lease edit(d()); edit->un_select(); } double Spectrum::power_at(double frequency, bool& ok) { Spectrum_Private* pd = d(); Render_Input_Read_Lease input(pd); if (!input || !input->has_data) { ok = false; return -1.0; } const auto& powers = input->cur_data; if (powers.size() < 2 || input->frequency_range.length() == 0.0 || !input->frequency_range.contains(frequency)) { ok = false; return -1.0; } double index = coordinate_to_source_index(input->frequency_range, powers.size(), frequency); ok = true; return interpolate_sample_value(index, powers.size(), input->interpolation_mode, [&powers](int i) { return powers.at(i); }); } } // namespace renderive