Deluge Firmware 1.3.0
Build date: 2025.11.26
Loading...
Searching...
No Matches
filter_container.h
1/*
2 * Copyright (c) 2025 Leonid Burygin
3 *
4 * This file is part of The Synthstrom Audible Deluge Firmware.
5 *
6 * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software Foundation,
8 * either version 3 of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 * See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along with this program.
15 * If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#pragma once
19
20#include "gui/menu_item/horizontal_menu_container.h"
21#include "hid/led/indicator_leds.h"
22#include "param.h"
23
24namespace deluge::gui::menu_item::filter {
25
26using namespace deluge::hid::display;
27
28class FilterContainer final : public HorizontalMenuContainer {
29public:
30 FilterContainer(std::initializer_list<MenuItem*> items, FilterParam* morph_item)
31 : HorizontalMenuContainer(items), morph_item_{morph_item} {}
32 FilterContainer(std::initializer_list<MenuItem*> items, UnpatchedFilterParam* morph_item)
33 : HorizontalMenuContainer(items), morph_item_unpatched_{morph_item} {}
34
35 void render(const SlotPosition& slots, const MenuItem* selected_item, HorizontalMenu* parent) override {
36 oled_canvas::Canvas& image = OLED::main;
37
38 const auto [freq_raw, reso_raw, morph_raw, is_morphable, is_hpf] = getFilterValues();
39 const float freq_value = freq_raw / 50.f;
40 const float reso_value = sigmoidLikeCurve(reso_raw, 50.f, 30.f);
41 const float morph_value = [&] {
42 float result = 0.f;
43 if (is_morphable) {
44 result = morph_raw / 50.f;
45 }
46 if (is_hpf) {
47 result = 1.0f - result; // treat HPF as fully morphed LPF visually
48 }
49 return result;
50 }();
51
52 constexpr uint8_t reso_segment_width = 6;
53 constexpr uint8_t freq_slope_width = 5;
54 constexpr uint8_t padding_x = 5;
55 const uint8_t total_width = slots.width - 2 - padding_x * 2;
56 const uint8_t base_width = total_width - freq_slope_width - reso_segment_width;
57
58 uint8_t min_x = slots.start_x + padding_x;
59 uint8_t max_x = min_x + total_width;
60 uint8_t reso_x0 = min_x - reso_segment_width + base_width * freq_value;
61 uint8_t reso_x1 = reso_x0 + reso_segment_width;
62 uint8_t reso_x2 = reso_x1 + reso_segment_width;
63 int8_t left_slope_x0 = reso_x0 - base_width - freq_slope_width;
64 int8_t left_slope_x1 = left_slope_x0 + freq_slope_width;
65 uint8_t right_slope_x0 = reso_x2;
66 uint8_t right_slope_x1 = right_slope_x0 + freq_slope_width;
67
68 if (morph_value > 0) {
69 const uint8_t slope_shift = std::lerp(0, total_width, morph_value);
70 const uint8_t reso_shift = std::lerp(0, freq_slope_width + reso_segment_width, morph_value);
71 left_slope_x0 += slope_shift;
72 left_slope_x1 += slope_shift;
73 right_slope_x0 += slope_shift;
74 right_slope_x1 += slope_shift;
75 reso_x0 += reso_shift;
76 reso_x1 += reso_shift;
77 reso_x2 += reso_shift;
78 }
79
80 constexpr uint8_t container_height = 23;
81 const uint8_t start_y = slots.start_y + 1;
82 const uint8_t end_y = start_y + container_height - 1;
83 const uint8_t center_y = start_y + (end_y - start_y) / 2;
84 const uint8_t reso_y = std::lerp(center_y, start_y, reso_value);
85
86 // Outline
87 image.drawRectangleRounded(min_x - 1, start_y - 1, max_x + 1, end_y + 1);
88
89 auto draw_segment = [&](int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
90 oled_canvas::Point last_point{-1, -1};
91 auto draw_fill_pattern = [&](oled_canvas::Point point) {
92 if (last_point.x != point.x && point.x % 3 == 0) {
93 for (int32_t y = point.y; y <= end_y; y++) {
94 if (y % 3 == 1) {
95 image.drawPixel(point.x, y);
96 }
97 }
98 }
99 last_point = point;
100 };
101 image.drawLine(x0, y0, x1, y1, {.min_x = min_x, .max_x = max_x, .point_callback = draw_fill_pattern});
102 };
103
104 // Left slope
105 draw_segment(left_slope_x0, end_y, left_slope_x1, center_y);
106
107 // Body
108 draw_segment(left_slope_x1, center_y, reso_x0, center_y);
109
110 // Resonance
111 draw_segment(reso_x0, center_y, reso_x1, reso_y);
112 draw_segment(reso_x1, reso_y, reso_x2, center_y);
113 draw_segment(reso_x2, center_y, right_slope_x0, center_y);
114
115 // Right slope
116 draw_segment(right_slope_x0, center_y, right_slope_x1, end_y);
117 };
118
119private:
120 FilterParam* morph_item_{nullptr};
121 UnpatchedFilterParam* morph_item_unpatched_{nullptr};
122
124 int32_t freq_value{0};
125 int32_t reso_value{0};
126 int32_t morph_value{0};
127 bool is_morphable{false};
128 bool is_hpf{false};
129 };
130
131 [[nodiscard]] FilterValues getFilterValues() const {
132 if (morph_item_ != nullptr) {
133 // Get from patched params
134 auto freq_item = static_cast<FilterParam*>(items_[0]);
135 auto reso_item = static_cast<FilterParam*>(items_[1]);
136 bool is_morphable = morph_item_->getFilterInfo().isMorphable();
137 bool is_hpf = freq_item->getP() == params::LOCAL_HPF_FREQ;
138 return {freq_item->getValue(), reso_item->getValue(), morph_item_->getValue(), is_morphable, is_hpf};
139 }
140
141 // Get from unpatched params
142 auto freq_item = static_cast<UnpatchedFilterParam*>(items_[0]);
143 auto reso_item = static_cast<UnpatchedFilterParam*>(items_[1]);
144 bool is_morphable = morph_item_unpatched_->getFilterInfo().isMorphable();
145 bool is_hpf = freq_item->getP() == params::UNPATCHED_HPF_FREQ;
146 return {freq_item->getValue(), reso_item->getValue(), morph_item_unpatched_->getValue(), is_morphable, is_hpf};
147 }
148};
149} // namespace deluge::gui::menu_item::filter
Base class for all menu items.
Definition menu_item.h:50
Definition horizontal_menu.h:28
Definition menu_item.h:33