28class EqMenu final :
public HorizontalMenu {
30 EqMenu(l10n::String newName, std::initializer_list<MenuItem*> newItems) : HorizontalMenu(newName, newItems) {}
32 void renderMenuItems(std::span<MenuItem*> items,
const MenuItem* currentItem)
override {
33 const auto [bass, treble, bass_freq, treble_freq, order_changed] = ensureCorrectItemsOrderAndGetValues();
39 constexpr uint8_t start_y = OLED_MAIN_TOPMOST_PIXEL + kTextTitleSizeY + 5;
40 constexpr uint8_t end_y = OLED_MAIN_HEIGHT_PIXELS - 6;
41 constexpr uint8_t center_y = start_y + (end_y - start_y) / 2;
42 constexpr uint8_t height = end_y - start_y;
44 constexpr uint8_t padding_x = 4;
45 constexpr uint8_t start_x = padding_x - 1;
46 constexpr uint8_t end_x = OLED_MAIN_WIDTH_PIXELS - padding_x;
47 constexpr uint8_t slope_width = 12;
48 constexpr uint8_t bass_band_travel_width = (end_x - start_x) / 2 - slope_width;
49 constexpr uint8_t treble_band_travel_width = (end_x - start_x) * 0.75f;
51 constexpr uint8_t bass_x0 = start_x;
52 uint8_t bass_x1 = std::lerp(bass_x0, bass_x0 + bass_band_travel_width, bass_freq);
53 uint8_t bass_x2 = bass_x1 + slope_width;
54 uint8_t bass_y1 = std::lerp(end_y, end_y - height, bass);
55 uint8_t bass_y2 = center_y;
57 constexpr uint8_t treble_x0 = end_x;
58 uint8_t treble_x1 = std::lerp(end_x - treble_band_travel_width, end_x, treble_freq);
59 uint8_t treble_x2 = treble_x1 - slope_width;
60 uint8_t treble_y1 = std::lerp(end_y, end_y - height, treble);
61 uint8_t treble_y2 = center_y;
65 if (bass_x2 > treble_x2) {
66 const uint8_t diff = bass_x2 - treble_x2;
73 auto center_between = [](uint8_t a, uint8_t b) {
return std::min(a, b) + std::abs(b - a) / 2; };
74 const float morph = 1.0f - (treble_x2 - bass_x2) / 14.f;
76 const uint8_t target_y = center_between(bass_y1, treble_y1);
77 bass_y2 = std::lerp(bass_y2, target_y, morph);
78 treble_y2 = std::lerp(treble_y2, target_y, morph);
82 image.drawLine(bass_x0, bass_y1, bass_x1, bass_y1);
83 image.drawLine(bass_x1, bass_y1, bass_x2, bass_y2);
84 image.drawLine(bass_x2, bass_y2, treble_x2, treble_y2);
85 image.drawLine(treble_x2, treble_y2, treble_x1, treble_y1);
86 image.drawLine(treble_x1, treble_y1, treble_x0, treble_y1);
89 if (std::abs(center_y - bass_y1) > 1) {
90 for (uint8_t x = 0; x <= bass_x2; x++) {
91 if (x % 6 == 3 && std::abs(x - bass_x2) > 1 && std::abs(x - treble_x2) > 1) {
92 image.drawPixel(x, center_y);
96 if (std::abs(center_y - treble_y1) > 1) {
97 for (uint8_t x = 0; x <= end_x; x++) {
98 if (x % 6 == 3 && std::abs(x - bass_x2) > 1 && std::abs(x - treble_x2) > 1) {
99 image.drawPixel(x, center_y);
103 for (uint8_t y = start_y - 1; y <= end_y + 1; y += 4) {
104 image.drawPixel(bass_x2, y);
105 image.drawPixel(treble_x2, y);
109 selected_x_ = -1, selected_y_ = -1;
110 drawControlIndicator(center_between(bass_x0, bass_x1), bass_y1, currentItem == items[0]);
111 drawControlIndicator(bass_x2, bass_y2, currentItem == items[1]);
112 drawControlIndicator(treble_x2, treble_y2, currentItem == items[2]);
113 drawControlIndicator(center_between(treble_x1, treble_x0), treble_y1, currentItem == items[3]);
117 int32_t selected_x_, selected_y_;
122 float bass_freq{0.f};
123 float treble_freq{0.f};
124 bool order_changed{
false};
128 using namespace deluge::modulation;
130 const uint8_t current_item_pos = std::distance(items.begin(), current_item_);
131 UnpatchedParam* desired_order_items[4] = {
nullptr,
nullptr,
nullptr,
nullptr};
132 EqualizerValues result{};
134 for (
auto* i : items) {
135 switch (
const auto as_unpatched =
static_cast<UnpatchedParam*
>(i); as_unpatched->getP()) {
136 case params::UNPATCHED_BASS:
137 desired_order_items[0] = as_unpatched;
138 result.bass = as_unpatched->getValue() / 50.f;
140 case params::UNPATCHED_BASS_FREQ:
141 desired_order_items[1] = as_unpatched;
142 result.bass_freq = as_unpatched->getValue() / 50.f;
144 case params::UNPATCHED_TREBLE_FREQ:
145 desired_order_items[2] = as_unpatched;
147 result.treble_freq = std::clamp<int32_t>(as_unpatched->getValue(), 0, 32) / 32.f;
149 case params::UNPATCHED_TREBLE:
150 desired_order_items[3] = as_unpatched;
151 result.treble = as_unpatched->getValue() / 50.f;
158 for (
int idx = 0; idx < items.size(); ++idx) {
159 if (items[idx] != desired_order_items[idx] && desired_order_items[idx]) {
160 items[idx] = desired_order_items[idx];
161 result.order_changed =
true;
165 if (result.order_changed) {
166 current_item_ = items.begin() + current_item_pos;
167 lastSelectedItemPosition = kNoSelection;
173 void drawControlIndicator(
const float center_x,
const float center_y,
const bool is_selected) {
174 oled_canvas::Canvas& image = OLED::main;
176 const int32_t ix =
static_cast<int32_t
>(center_x);
177 const int32_t iy =
static_cast<int32_t
>(center_y);
179 if (!is_selected && ix == selected_x_ && iy == selected_y_) {
185 constexpr int32_t square_size = 2;
186 constexpr int32_t innerSquareSize = square_size - 1;
187 for (int32_t x = ix - innerSquareSize; x <= ix + innerSquareSize; x++) {
188 for (int32_t y = iy - innerSquareSize; y <= iy + innerSquareSize; y++) {
189 image.clearPixel(x, y);
195 selected_x_ = ix, selected_y_ = iy;
196 image.invertArea(ix - innerSquareSize, square_size * 2 - 1, iy - innerSquareSize, iy + innerSquareSize);
200 image.drawRectangle(ix - square_size, iy - square_size, ix + square_size, iy + square_size);