Deluge Firmware 1.3.0
Build date: 2025.11.04
Loading...
Searching...
No Matches
envelope_menu.h
1/*
2 * Copyright (c) 2014-2023 Synthstrom Audible Limited
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#include "gui/menu_item/horizontal_menu.h"
20#include "hid/display/oled.h"
21#include "segment.h"
22
23using namespace deluge::hid::display;
24
25namespace deluge::gui::menu_item::envelope {
26class EnvelopeMenu final : public HorizontalMenu, FormattedTitle {
27public:
28 EnvelopeMenu(l10n::String newName, std::span<MenuItem*> newItems, int32_t envelopeIndex)
29 : HorizontalMenu(newName, newItems), FormattedTitle(newName, envelopeIndex + 1) {}
30
31 [[nodiscard]] std::string_view getName() const override { return FormattedTitle::title(); }
32 [[nodiscard]] std::string_view getTitle() const override { return FormattedTitle::title(); }
33
34 void renderMenuItems(std::span<MenuItem*> items, const MenuItem* currentItem) override {
35 // Get the values in 0-50 range
36 const int32_t attack = static_cast<Segment*>(items[0])->getValue();
37 const int32_t decay = static_cast<Segment*>(items[1])->getValue();
38 const int32_t sustain = static_cast<Segment*>(items[2])->getValue();
39 const int32_t release = static_cast<Segment*>(items[3])->getValue();
40
41 // Constants
42 constexpr int32_t padding_x = 4;
43 constexpr int32_t start_x = padding_x;
44 constexpr uint8_t start_y = OLED_MAIN_TOPMOST_PIXEL + kTextTitleSizeY + 6;
45 constexpr uint8_t end_y = OLED_MAIN_HEIGHT_PIXELS - 6;
46 constexpr int32_t draw_width = OLED_MAIN_WIDTH_PIXELS - 2 * padding_x;
47 constexpr uint8_t draw_height = end_y - start_y;
48 constexpr float max_segment_width = draw_width / 4;
49
50 // Calculate widths
51 const float attack_width = attack / 50.0f * max_segment_width;
52 const float decay_normalized = sigmoidLikeCurve(decay, 50.0f, 8.0f); // Maps 0-50 to 0-1 range with steep start
53 const float decay_width = decay_normalized * max_segment_width;
54
55 // X positions
56 const float attack_x = round(start_x + attack_width);
57 const float decay_x = round(attack_x + decay_width);
58 constexpr float sustain_x = start_x + max_segment_width * 3; // Fixed sustainX position
59 // Make release x dynamic, right of sustain
60 const float release_x = round(sustain_x + release / 50.0f * (start_x + draw_width - sustain_x));
61
62 // Y positions
63 constexpr int32_t base_y = start_y + draw_height;
64 const int32_t sustain_y = base_y - round(sustain / 50.0f * draw_height);
65
66 oled_canvas::Canvas& image = OLED::main;
67
68 // Draw stage lines
69 image.drawLine(start_x, base_y, attack_x, start_y);
70 image.drawLine(attack_x, start_y, decay_x, sustain_y);
71 image.drawLine(decay_x, sustain_y, sustain_x, sustain_y);
72 image.drawLine(sustain_x, sustain_y, release_x, base_y);
73 image.drawLine(release_x, base_y, start_x + draw_width, base_y);
74
75 // Draw stage transition point dotted lines
76 for (int32_t y = OLED_MAIN_VISIBLE_HEIGHT; y >= start_y - 2; y -= 4) {
77 // reduce a messy look when lines are close to each other by omitting the line
78 if (attack_x > start_x + 3) {
79 image.drawPixel(attack_x, y);
80 }
81 if (decay_x - attack_x > 4) {
82 image.drawPixel(decay_x, y);
83 }
84 if (sustain_y > start_y || y > sustain_y) {
85 image.drawPixel(sustain_x, y);
86 }
87 }
88
89 // Draw transition indicators
90 selected_x_ = -1, selected_y_ = -1;
91 const int32_t selected_pos = std::distance(items.begin(), std::ranges::find(items, currentItem));
92
93 drawTransitionIndicator(attack_x, start_y, selected_pos == 0);
94 drawTransitionIndicator(decay_x, sustain_y, selected_pos == 1);
95 drawTransitionIndicator(decay_x + (sustain_x - decay_x) / 2, sustain_y, selected_pos == 2);
96 drawTransitionIndicator(release_x, base_y, selected_pos == 3);
97 }
98
99private:
100 int32_t selected_x_, selected_y_;
101
102 void drawTransitionIndicator(const float center_x, const float center_y, const bool is_selected) {
103 oled_canvas::Canvas& image = OLED::main;
104
105 const int32_t ix = static_cast<int32_t>(center_x);
106 const int32_t iy = static_cast<int32_t>(center_y);
107
108 if (!is_selected && ix == selected_x_ && iy == selected_y_) {
109 // Overlap occurred, skip drawing
110 return;
111 }
112
113 // Clear region inside
114 constexpr int32_t square_size = 2;
115 constexpr int32_t innerSquareSize = square_size - 1;
116 for (int32_t x = ix - innerSquareSize; x <= ix + innerSquareSize; x++) {
117 for (int32_t y = iy - innerSquareSize; y <= iy + innerSquareSize; y++) {
118 image.clearPixel(x, y);
119 }
120 }
121
122 if (is_selected) {
123 // Invert region inside to highlight selection
124 selected_x_ = ix, selected_y_ = iy;
125 image.invertArea(ix - innerSquareSize, square_size * 2 - 1, iy - innerSquareSize, iy + innerSquareSize);
126 }
127
128 // Draw a transition square
129 image.drawRectangle(ix - square_size, iy - square_size, ix + square_size, iy + square_size);
130 }
131};
132
133} // namespace deluge::gui::menu_item::envelope
Base class for all menu items.
Definition menu_item.h:50
std::string_view getTitle() const override
Get the title to be used when rendering on OLED, both as a deluge::gui::menu_item::Submenu and when d...
Definition envelope_menu.h:32
std::string_view getName() const override
Get the actual name for use on OLED for deluge::gui::menu_item::Submenu s.
Definition envelope_menu.h:31