26class EnvelopeMenu final :
public HorizontalMenu, FormattedTitle {
28 EnvelopeMenu(l10n::String newName, std::span<MenuItem*> newItems, int32_t envelopeIndex)
29 : HorizontalMenu(newName, newItems), FormattedTitle(newName, envelopeIndex + 1) {}
31 [[nodiscard]] std::string_view
getName()
const override {
return FormattedTitle::title(); }
32 [[nodiscard]] std::string_view
getTitle()
const override {
return FormattedTitle::title(); }
34 void renderMenuItems(std::span<MenuItem*> items,
const MenuItem* currentItem)
override {
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();
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;
51 const float attack_width = attack / 50.0f * max_segment_width;
52 const float decay_normalized = sigmoidLikeCurve(decay, 50.0f, 8.0f);
53 const float decay_width = decay_normalized * max_segment_width;
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;
60 const float release_x = round(sustain_x + release / 50.0f * (start_x + draw_width - sustain_x));
63 constexpr int32_t base_y = start_y + draw_height;
64 const int32_t sustain_y = base_y - round(sustain / 50.0f * draw_height);
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);
76 for (int32_t y = OLED_MAIN_VISIBLE_HEIGHT; y >= start_y - 2; y -= 4) {
78 if (attack_x > start_x + 3) {
79 image.drawPixel(attack_x, y);
81 if (decay_x - attack_x > 4) {
82 image.drawPixel(decay_x, y);
84 if (sustain_y > start_y || y > sustain_y) {
85 image.drawPixel(sustain_x, y);
90 selected_x_ = -1, selected_y_ = -1;
91 const int32_t selected_pos = std::distance(items.begin(), std::ranges::find(items, currentItem));
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);
100 int32_t selected_x_, selected_y_;
102 void drawTransitionIndicator(
const float center_x,
const float center_y,
const bool is_selected) {
103 oled_canvas::Canvas& image = OLED::main;
105 const int32_t ix =
static_cast<int32_t
>(center_x);
106 const int32_t iy =
static_cast<int32_t
>(center_y);
108 if (!is_selected && ix == selected_x_ && iy == selected_y_) {
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);
124 selected_x_ = ix, selected_y_ = iy;
125 image.invertArea(ix - innerSquareSize, square_size * 2 - 1, iy - innerSquareSize, iy + innerSquareSize);
129 image.drawRectangle(ix - square_size, iy - square_size, ix + square_size, iy + square_size);