Deluge Firmware 1.3.0
Build date: 2025.08.12
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 totalWidth = OLED_MAIN_WIDTH_PIXELS;
43 constexpr int32_t totalHeight = OLED_MAIN_VISIBLE_HEIGHT - kTextTitleSizeY - 6;
44 constexpr int32_t padding = 4;
45 constexpr int32_t drawX = padding;
46 constexpr int32_t drawY = OLED_MAIN_TOPMOST_PIXEL + kTextTitleSizeY + padding + 3;
47 constexpr int32_t drawWidth = totalWidth - 2 * padding;
48 constexpr int32_t drawHeight = totalHeight - 2 * padding;
49 constexpr float maxSegmentWidth = drawWidth / 4;
50
51 // Calculate widths
52 const float attackWidth = (attack / 50.0f) * maxSegmentWidth;
53 const float decayNormalized = sigmoidLikeCurve(decay, 50.0f, 10.0f); // Maps 0-50 to 0-1 range with steep start
54 const float decayWidth = decayNormalized * maxSegmentWidth;
55
56 // X positions
57 const float attackX = round(drawX + attackWidth);
58 const float decayX = round(attackX + decayWidth);
59 constexpr float sustainX = drawX + maxSegmentWidth * 3; // Fixed sustainX position
60 const float releaseX = round(
61 sustainX + (release / 50.0f) * (drawX + drawWidth - sustainX)); // Make releaseX dynamic, right of sustain
62
63 // Y positions
64 constexpr int32_t baseY = drawY + drawHeight;
65 constexpr int32_t peakY = drawY;
66 const int32_t sustainY = baseY - round((sustain / 50.0f) * drawHeight);
67
68 oled_canvas::Canvas& image = OLED::main;
69
70 // Draw stage lines
71 image.drawLine(drawX, baseY, attackX, peakY);
72 image.drawLine(attackX, peakY, decayX, sustainY);
73 image.drawLine(decayX, sustainY, sustainX, sustainY);
74 image.drawLine(sustainX, sustainY, releaseX, baseY);
75 image.drawLine(releaseX, baseY, drawX + drawWidth, baseY);
76
77 // Draw stage transition point dotted lines
78 for (int32_t y = OLED_MAIN_VISIBLE_HEIGHT; y >= drawY; y -= 4) {
79 // reduce a messy look when lines are close to each other by omitting the line
80 if (attackX > drawX + 3) {
81 image.drawPixel(attackX, y);
82 }
83 if (decayX - attackX > 4) {
84 image.drawPixel(decayX, y);
85 }
86 image.drawPixel(sustainX, y);
87 }
88
89 // Draw transition squares
90 selectedX = -1, selectedY = -1;
91 const int32_t selectedPos = std::distance(items.begin(), std::ranges::find(items, currentItem));
92
93 // Attack → Decay
94 drawTransitionSquare(attackX, peakY, selectedPos == 0);
95 // Decay → Sustain
96 drawTransitionSquare(decayX, sustainY, selectedPos == 1);
97 // Sustain
98 drawTransitionSquare(decayX + (sustainX - decayX) / 2, sustainY, selectedPos == 2);
99 // Release → End
100 drawTransitionSquare(releaseX, baseY, selectedPos == 3);
101 }
102
103private:
104 int32_t selectedX, selectedY;
105
106 void drawTransitionSquare(const float centerX, const float centerY, const bool isSelected) {
107 oled_canvas::Canvas& image = OLED::main;
108
109 const int32_t ix = static_cast<int32_t>(centerX);
110 const int32_t iy = static_cast<int32_t>(centerY);
111
112 if (!isSelected && ix == selectedX && iy == selectedY) {
113 // Overlap occurred, skip drawing
114 return;
115 }
116
117 // Clear region inside
118 constexpr int32_t squareSize = 2, innerSquareSize = squareSize - 1;
119 for (int32_t x = ix - innerSquareSize; x <= ix + innerSquareSize; x++) {
120 for (int32_t y = iy - innerSquareSize; y <= iy + innerSquareSize; y++) {
121 image.clearPixel(x, y);
122 }
123 }
124
125 if (isSelected) {
126 // Invert region inside to highlight selection
127 selectedX = ix, selectedY = iy;
128 image.invertArea(ix - innerSquareSize, (squareSize * 2) - 1, iy - innerSquareSize, iy + innerSquareSize);
129 }
130
131 // Draw a transition square
132 image.drawRectangle(ix - squareSize, iy - squareSize, ix + squareSize, iy + squareSize);
133 };
134};
135
136} // namespace deluge::gui::menu_item::envelope
Base class for all menu items.
Definition menu_item.h:42
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