29class FilterContainer final :
public HorizontalMenuContainer {
31 FilterContainer(std::initializer_list<MenuItem*> items,
FilterParam* morph_item)
32 : HorizontalMenuContainer(items), morph_item_{morph_item} {}
34 : HorizontalMenuContainer(items), morph_item_unpatched_{morph_item} {}
36 void render(int32_t start_x, int32_t width, int32_t start_y, int32_t height,
const MenuItem* selected_item,
40 const auto [freq_raw, reso_raw, morph_raw, filter_mode, is_hpf] = getFilterValues();
41 const float freq_value = freq_raw / 50.f;
42 const float reso_value = sigmoidLikeCurve(reso_raw, 50.f, 15.f);
43 const float morph_value = [&] {
45 if (util::one_of(filter_mode, {FilterMode::SVF_BAND, FilterMode::SVF_NOTCH})) {
46 result = morph_raw / 50.f;
49 result = 1.0f - result;
54 constexpr uint8_t reso_segment_width = 5;
55 constexpr uint8_t freq_slope_width = 5;
56 constexpr uint8_t padding_x = 3;
57 const uint8_t total_width = width - 4 - padding_x * 2;
58 const uint8_t base_width = total_width - freq_slope_width - reso_segment_width;
60 uint8_t min_x = start_x + padding_x;
61 uint8_t max_x = min_x + total_width;
62 uint8_t reso_x0 = min_x - reso_segment_width + base_width * freq_value;
63 uint8_t reso_x1 = reso_x0 + reso_segment_width;
64 uint8_t reso_x2 = reso_x1 + reso_segment_width;
65 int8_t slope0_x0 = reso_x0 - base_width - freq_slope_width;
66 int8_t slope0_x1 = slope0_x0 + freq_slope_width;
67 uint8_t slope1_x0 = reso_x2;
68 uint8_t slope1_x1 = slope1_x0 + freq_slope_width;
70 if (morph_value > 0) {
71 constexpr uint8_t padding = padding_x - 1;
72 const uint8_t slope_shift = std::lerp(0, total_width + padding, morph_value);
73 const uint8_t reso_shift = std::lerp(0, freq_slope_width + reso_segment_width + padding, morph_value);
74 const uint8_t base_shift = std::lerp(0, padding, morph_value);
77 slope0_x0 += slope_shift;
78 slope0_x1 += slope_shift;
79 slope1_x0 += slope_shift;
80 slope1_x1 += slope_shift;
81 reso_x0 += reso_shift;
82 reso_x1 += reso_shift;
83 reso_x2 += reso_shift;
86 constexpr uint8_t padding_y = 2;
87 const uint8_t peak_y = start_y + padding_y;
88 const uint8_t floor_y = start_y + height - 3;
89 const uint8_t full_reso_y = start_y + (height >> 1) + 1;
90 const uint8_t body_y = std::lerp(start_y + padding_y, full_reso_y, reso_value);
92 auto draw_segment = [&](int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
95 if (last_point.x != point.x && point.x % 3 == 0) {
96 for (int32_t y = point.y; y <= floor_y + 2; y++) {
98 image.drawPixel(point.x, y);
104 image.drawLine(x0, y0, x1, y1, {.min_x = min_x, .max_x = max_x, .point_callback = draw_fill_pattern});
110 : draw_segment(slope0_x0, floor_y, slope0_x1, body_y);
113 draw_segment(slope0_x1, body_y, reso_x0, body_y);
116 draw_segment(reso_x0, body_y, reso_x1, peak_y);
117 draw_segment(reso_x1, peak_y, reso_x2, body_y);
118 draw_segment(reso_x2, body_y, slope1_x0, body_y);
122 : draw_segment(slope1_x0, body_y, slope1_x1, floor_y);
125 constexpr uint8_t line_offset = 3;
126 constexpr uint8_t line_interval = 5;
127 for (uint8_t x = min_x + line_offset; x <= max_x - line_offset; x += line_interval) {
128 if (x < slope0_x1 - line_offset || x > slope1_x0 + line_offset) {
129 image.drawPixel(x, body_y);
134 auto freq_point = pickFreqPoint(slope0_last_point, slope1_last_point, min_x, max_x);
135 drawIndicatorSquare(freq_point.x, freq_point.y, selected_item == items_[0]);
136 drawIndicatorSquare(reso_x1, peak_y, selected_item == items_[1]);
138 syncIndicatorsPositionWithLEDs(freq_point == slope1_last_point, selected_item, parent,
139 halt_remaining_rendering);
147 int32_t freq_value{0};
148 int32_t reso_value{0};
149 int32_t morph_value{0};
150 FilterMode mode{FilterMode::OFF};
155 if (morph_item_ !=
nullptr) {
157 auto freq_item =
static_cast<FilterParam*
>(items_[0]);
158 auto reso_item =
static_cast<FilterParam*
>(items_[1]);
159 FilterMode filter_mode = morph_item_->getFilterInfo().getMode();
160 bool is_hpf = freq_item->getP() == params::LOCAL_HPF_FREQ;
161 return {freq_item->getValue(), reso_item->getValue(), morph_item_->getValue(), filter_mode, is_hpf};
165 auto freq_item =
static_cast<UnpatchedFilterParam*
>(items_[0]);
166 auto reso_item =
static_cast<UnpatchedFilterParam*
>(items_[1]);
167 FilterMode filter_mode = morph_item_unpatched_->getFilterInfo().getMode();
168 bool is_hpf = freq_item->getP() == params::UNPATCHED_HPF_FREQ;
169 return {freq_item->getValue(), reso_item->getValue(), morph_item_unpatched_->getValue(), filter_mode, is_hpf};
174 if (slope0_last_point.x > min_x && slope0_last_point.x < max_x) {
175 return slope0_last_point;
177 if (slope1_last_point.x > min_x && slope1_last_point.x < max_x) {
178 return slope1_last_point;
180 return slope0_last_point.y > slope1_last_point.y ? slope0_last_point : slope1_last_point;
183 static void drawIndicatorSquare(int32_t center_x, int32_t center_y,
bool is_selected) {
184 oled_canvas::Canvas& image = OLED::main;
186 for (int32_t x = center_x - 1; x <= center_x + 1; x++) {
187 for (int32_t y = center_y - 1; y <= center_y + 1; y++) {
188 image.clearPixel(x, y);
192 image.invertArea(center_x - 1, 3, center_y - 1, center_y + 1);
194 image.drawRectangle(center_x - 2, center_y - 2, center_x + 2, center_y + 2);
197 void syncIndicatorsPositionWithLEDs(
bool freq_is_on_right_side,
const MenuItem* selected_item,
198 HorizontalMenu* parent,
bool* halt_remaining_rendering)
const {
199 MenuItem* freq = items_[0];
200 MenuItem* reso = items_[1];
202 uint8_t freq_index = 1, reso_index = 2;
203 if (freq_is_on_right_side) {
204 std::swap(freq_index, reso_index);
207 auto& parent_items = parent->getItems();
208 if (parent_items[freq_index] != freq) {
209 parent_items[freq_index] = freq;
210 parent_items[reso_index] = reso;
213 auto host_menu = parent->parent !=
nullptr ? parent->parent : parent;
216 host_menu->setCurrentItem(selected_item);
219 OLED::clearMainImage();
220 host_menu->renderOLED();
221 *halt_remaining_rendering =
true;