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 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;
52 const float attackWidth = (attack / 50.0f) * maxSegmentWidth;
53 const float decayNormalized = sigmoidLikeCurve(decay, 50.0f, 10.0f);
54 const float decayWidth = decayNormalized * maxSegmentWidth;
57 const float attackX = round(drawX + attackWidth);
58 const float decayX = round(attackX + decayWidth);
59 constexpr float sustainX = drawX + maxSegmentWidth * 3;
60 const float releaseX = round(
61 sustainX + (release / 50.0f) * (drawX + drawWidth - sustainX));
64 constexpr int32_t baseY = drawY + drawHeight;
65 constexpr int32_t peakY = drawY;
66 const int32_t sustainY = baseY - round((sustain / 50.0f) * drawHeight);
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);
78 for (int32_t y = OLED_MAIN_VISIBLE_HEIGHT; y >= drawY; y -= 4) {
80 if (attackX > drawX + 3) {
81 image.drawPixel(attackX, y);
83 if (decayX - attackX > 4) {
84 image.drawPixel(decayX, y);
86 image.drawPixel(sustainX, y);
90 selectedX = -1, selectedY = -1;
91 const int32_t selectedPos = std::distance(items.begin(), std::ranges::find(items, currentItem));
94 drawTransitionSquare(attackX, peakY, selectedPos == 0);
96 drawTransitionSquare(decayX, sustainY, selectedPos == 1);
98 drawTransitionSquare(decayX + (sustainX - decayX) / 2, sustainY, selectedPos == 2);
100 drawTransitionSquare(releaseX, baseY, selectedPos == 3);
104 int32_t selectedX, selectedY;
106 void drawTransitionSquare(
const float centerX,
const float centerY,
const bool isSelected) {
109 const int32_t ix =
static_cast<int32_t
>(centerX);
110 const int32_t iy =
static_cast<int32_t
>(centerY);
112 if (!isSelected && ix == selectedX && iy == selectedY) {
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);
127 selectedX = ix, selectedY = iy;
128 image.invertArea(ix - innerSquareSize, (squareSize * 2) - 1, iy - innerSquareSize, iy + innerSquareSize);
132 image.drawRectangle(ix - squareSize, iy - squareSize, ix + squareSize, iy + squareSize);