Deluge Firmware 1.3.0
Build date: 2025.06.24
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 {
27public:
28 using HorizontalMenu::HorizontalMenu;
29
30 void renderOLED() override {
31 OLED::main.drawScreenTitle(getTitle(), false);
33 OLED::markChanged();
34 }
35
36 void drawPixelsForOled() override {
37 if (renderingStyle() != HORIZONTAL) {
39 }
40
41 // Update the synth-kit-midi-cv buttons LEDs
42 paging = splitMenuItemsByPages();
43 if (paging.selectedItemPositionOnPage != lastSelectedItemPosition) {
44 lastSelectedItemPosition = paging.selectedItemPositionOnPage;
45 updateSelectedMenuItemLED(lastSelectedItemPosition);
46 }
47
48 // Get the values in 0-50 range
49 for (const auto item : items) {
50 item->readCurrentValue();
51 }
52 const int32_t attack = static_cast<Segment*>(items[0])->getValue();
53 const int32_t decay = static_cast<Segment*>(items[1])->getValue();
54 const int32_t sustain = static_cast<Segment*>(items[2])->getValue();
55 const int32_t release = static_cast<Segment*>(items[3])->getValue();
56
57 // Constants
58 constexpr int32_t totalWidth = OLED_MAIN_WIDTH_PIXELS;
59 constexpr int32_t totalHeight = OLED_MAIN_VISIBLE_HEIGHT - kTextTitleSizeY - 6;
60 constexpr int32_t padding = 4;
61 constexpr int32_t drawX = padding;
62 constexpr int32_t drawY = OLED_MAIN_TOPMOST_PIXEL + kTextTitleSizeY + padding + 3;
63 constexpr int32_t drawWidth = totalWidth - 2 * padding;
64 constexpr int32_t drawHeight = totalHeight - 2 * padding;
65 constexpr float maxSegmentWidth = drawWidth / 4;
66
67 // Calculate widths
68 const float attackWidth = (attack / 50.0f) * maxSegmentWidth;
69 const float decayNormalized = sigmoidLikeCurve(decay, 10.0f, 50.0f); // Maps 0-50 to 0-1 range with steep start
70 const float decayWidth = decayNormalized * maxSegmentWidth;
71
72 // X positions
73 const float attackX = round(drawX + attackWidth);
74 const float decayX = round(attackX + decayWidth);
75 constexpr float sustainX = drawX + maxSegmentWidth * 3; // Fixed sustainX position
76 const float releaseX = round(
77 sustainX + (release / 50.0f) * (drawX + drawWidth - sustainX)); // Make releaseX dynamic, right of sustain
78
79 // Y positions
80 constexpr int32_t baseY = drawY + drawHeight;
81 constexpr int32_t peakY = drawY;
82 const int32_t sustainY = baseY - round((sustain / 50.0f) * drawHeight);
83
84 oled_canvas::Canvas& image = OLED::main;
85
86 // Draw stage lines
87 image.drawLine(drawX, baseY, attackX, peakY);
88 image.drawLine(attackX, peakY, decayX, sustainY);
89 image.drawLine(decayX, sustainY, sustainX, sustainY);
90 image.drawLine(sustainX, sustainY, releaseX, baseY);
91 image.drawLine(releaseX, baseY, drawX + drawWidth, baseY);
92
93 // Draw stage transition point dotted lines
94 for (int32_t y = OLED_MAIN_VISIBLE_HEIGHT; y >= drawY; y -= 4) {
95 // reduce a messy look when lines are close to each other by omitting the line
96 if (attackX > drawX + 3) {
97 image.drawPixel(attackX, y);
98 }
99 if (decayX - attackX > 4) {
100 image.drawPixel(decayX, y);
101 }
102 image.drawPixel(sustainX, y);
103 }
104
105 // Draw transition squares
106 selectedX = -1, selectedY = -1;
107 // Attack → Decay
108 drawTransitionSquare(attackX, peakY, 0);
109 // Decay → Sustain
110 drawTransitionSquare(decayX, sustainY, 1);
111 // Sustain
112 drawTransitionSquare(decayX + (sustainX - decayX) / 2, sustainY, 2);
113 // Release → End
114 drawTransitionSquare(releaseX, baseY, 3);
115 }
116
117private:
118 int32_t selectedX, selectedY;
119
120 void drawTransitionSquare(const float centerX, const float centerY, const int32_t pos) {
121 oled_canvas::Canvas& image = OLED::main;
122
123 const int32_t ix = static_cast<int32_t>(centerX);
124 const int32_t iy = static_cast<int32_t>(centerY);
125
126 if (pos != paging.selectedItemPositionOnPage && ix == selectedX && iy == selectedY) {
127 // Overlap occurred, skip drawing
128 return;
129 }
130
131 // Clear region inside
132 constexpr int32_t squareSize = 2, innerSquareSize = squareSize - 1;
133 for (int32_t x = ix - innerSquareSize; x <= ix + innerSquareSize; x++) {
134 for (int32_t y = iy - innerSquareSize; y <= iy + innerSquareSize; y++) {
135 image.clearPixel(x, y);
136 }
137 }
138
139 if (pos == paging.selectedItemPositionOnPage) {
140 // Invert region inside to highlight selection
141 selectedX = ix, selectedY = iy;
142 image.invertArea(ix - innerSquareSize, (squareSize * 2) - 1, iy - innerSquareSize, iy + innerSquareSize);
143 }
144
145 // Draw a transition square
146 image.drawRectangle(ix - squareSize, iy - squareSize, ix + squareSize, iy + squareSize);
147 };
148
149 // --- Helper functions ----
150 static float sigmoidLikeCurve(const float x, const float softening, const float xMax) {
151 const float raw = x / (x + softening);
152 const float maxVal = xMax / (xMax + softening);
153 return raw / maxVal;
154 };
155};
156
157} // namespace deluge::gui::menu_item::envelope
virtual std::string_view getTitle() const
Get the title to be used when rendering on OLED, both as a deluge::gui::menu_item::Submenu and when d...
Definition menu_item.h:221
void updateSelectedMenuItemLED(int32_t itemNumber)
When updating the selected horizontal menu item, you need to refresh the lit instrument LED's.
Definition horizontal_menu.cpp:314
void drawPixelsForOled() override
Paints the pixels below the standard title block.
Definition submenu.cpp:74
void drawPixelsForOled() override
Paints the pixels below the standard title block.
Definition envelope_menu.h:36
void renderOLED() override
Root rendering routine for OLED.
Definition envelope_menu.h:30