32 using Selection::Selection;
34 enum Direction : uint8_t { USE_SONG_STUTTER = 0, FORWARD, REVERSED, FORWARD_PING_PONG, REVERSED_PING_PONG };
36 deluge::vector<std::string_view> getOptions(OptType optType = OptType::FULL)
override {
37 using namespace deluge::l10n;
39 deluge::vector<std::string_view> result;
41 if (showUseSongOption()) {
42 result.push_back(l10n::getView(optType == OptType::SHORT ? String::STRING_FOR_USE_SONG_SHORT
43 : String::STRING_FOR_USE_SONG));
45 result.push_back(l10n::getView(String::STRING_FOR_FORWARD));
46 result.push_back(l10n::getView(String::STRING_FOR_REVERSED));
47 result.push_back(l10n::getView(String::STRING_FOR_FORWARD_PING_PONG));
48 result.push_back(l10n::getView(String::STRING_FOR_REVERSED_PING_PONG));
54 const auto* stutter = &soundEditor.currentModControllable->stutterConfig;
56 if (showUseSongOption() && stutter->useSongStutter) {
57 setValue(USE_SONG_STUTTER);
59 else if (stutter->reversed && stutter->pingPong) {
60 setValue(REVERSED_PING_PONG);
62 else if (stutter->reversed) {
65 else if (stutter->pingPong) {
66 setValue(FORWARD_PING_PONG);
75 void writeCurrentValue()
override {
76 Direction value = getValue();
79 if (currentUIMode == UI_MODE_HOLDING_AFFECT_ENTIRE_IN_SOUND_EDITOR && soundEditor.editingKitRow()) {
80 Kit* kit = getCurrentKit();
81 for (
Drum* thisDrum = kit->firstDrum; thisDrum !=
nullptr; thisDrum = thisDrum->next) {
82 if (thisDrum->type == DrumType::SOUND) {
83 auto* soundDrum =
static_cast<SoundDrum*
>(thisDrum);
84 applyOptionToStutterConfig(value, soundDrum->stutterConfig);
90 applyOptionToStutterConfig(value, soundEditor.currentModControllable->stutterConfig);
95 Direction getValue() {
96 const auto value = Selection::getValue();
97 const auto shift = showUseSongOption() ? 0 : 1;
98 return static_cast<Direction
>(value + shift);
101 void setValue(Direction value) {
102 const auto shift = showUseSongOption() ? 0 : 1;
103 return Selection::setValue(value - shift);
106 static bool showUseSongOption() {
return !soundEditor.currentModControllable->isSong(); }
108 static void applyOptionToStutterConfig(
const Direction value, StutterConfig& stutter) {
109 stutter.useSongStutter = value == USE_SONG_STUTTER;
110 stutter.reversed = value == REVERSED || value == REVERSED_PING_PONG;
111 stutter.pingPong = value == FORWARD_PING_PONG || value == REVERSED_PING_PONG;
113 if (stutter.useSongStutter) {
114 stutter.quantized = currentSong->globalEffectable.stutterConfig.quantized;
115 stutter.reversed = currentSong->globalEffectable.stutterConfig.reversed;
116 stutter.pingPong = currentSong->globalEffectable.stutterConfig.pingPong;
121 const auto value = Selection::getValue();
122 valueBuf.append(getOptions(OptType::SHORT)[value]);
125 void renderInHorizontalMenu(int32_t startX, int32_t width, int32_t startY, int32_t height)
override {
126 using namespace deluge::hid::display;
129 const auto value = getValue();
131 if (value == USE_SONG_STUTTER) {
132 const auto& icon = OLED::songIcon;
133 constexpr int32_t songIconWidth = 9;
136 const int32_t x = startX + ((width - songIconWidth) / 2) - 2;
137 const int32_t y = startY + ((height - songIconWidth) / 2);
138 return image.drawGraphicMultiLine(icon, x, y, songIconWidth);
141 constexpr int32_t numBytesTall = 2;
142 const bool reversed = value == REVERSED || value == REVERSED_PING_PONG;
144 reversed ? reverseBitmap(OLED::stutterDirectionIcon, numBytesTall) : OLED::stutterDirectionIcon;
146 constexpr int32_t iconHeight = numBytesTall * 8;
147 const int32_t iconWidth = icon.size() / numBytesTall;
149 if (value == FORWARD_PING_PONG || value == REVERSED_PING_PONG) {
151 constexpr int32_t iconOffset = 4;
152 image.drawChar(
'P', startX + 3, startY + 3, 5, kTextSpacingY);
153 image.drawGraphicMultiLine(reversed ? icon.data() : icon.data() + iconOffset * numBytesTall, startX + 11,
154 startY, iconWidth - iconOffset, iconHeight, numBytesTall);
158 const int32_t x = startX + (width - iconWidth) / 2 - 1;
159 image.drawGraphicMultiLine(icon.data(), x, startY, iconWidth, iconHeight, numBytesTall);
163 static std::vector<uint8_t> reverseBitmap(
const std::vector<uint8_t>& bitmap, int32_t numBytesTall) {
164 const int32_t columnCount = bitmap.size() / numBytesTall;
166 std::vector<uint8_t> output(bitmap.size());
168 for (
size_t col = 0; col < columnCount; ++col) {
169 const int32_t inputIndex = col * numBytesTall;
170 const int32_t reversedCol = columnCount - 1 - col;
171 const int32_t outputIndex = reversedCol * numBytesTall;
173 for (int32_t
byte = 0;
byte < numBytesTall; ++byte) {
174 uint8_t b = bitmap[inputIndex + byte];
175 const uint8_t reversed = std::bit_cast<uint8_t>(b);
176 output[outputIndex + byte] = reversed;