From 4f36e0376e27036cae3cf178ba12bec801202324 Mon Sep 17 00:00:00 2001 From: Nikodemus Siivola Date: Sun, 21 Jul 2024 01:09:30 +0300 Subject: [PATCH] display quantize/humanize mode for first encoder detent, then quantize (#2291) - on first encoder detent the popup says either "humanize" or "quantize" depending on the encoder direction (plus "all" when necessary) - For 7seg, when displaying the humanize/quantize all amount, prefix it with A as long as the result fits on the screen nicely. - explicit UI_QUANTIZE_MODE, which silences auditions as soon as the tempo encoder is pressed while holding an audition pad - the mode stays on as long as any audition pads remain pressed - fix prevention of tempo-encoder popup on audition + tempo press, instead display "HUMANIZE/QUANTIZE ALL" as a hint. I believe this is a lefover from the original patch, which I believe used pad pressed instead of audition to quantize? --- src/deluge/gui/l10n/english.json | 5 +- src/deluge/gui/l10n/g_english.cpp | 5 +- src/deluge/gui/l10n/g_seven_segment.cpp | 5 +- src/deluge/gui/l10n/seven_segment.json | 5 +- src/deluge/gui/l10n/strings.h | 5 +- src/deluge/gui/ui/ui.h | 1 + src/deluge/gui/views/instrument_clip_view.cpp | 138 ++++++++++++------ src/deluge/gui/views/instrument_clip_view.h | 11 +- src/deluge/hid/display/display.h | 2 + 9 files changed, 125 insertions(+), 52 deletions(-) diff --git a/src/deluge/gui/l10n/english.json b/src/deluge/gui/l10n/english.json index 9cd500e0b6..a0d08ceba1 100644 --- a/src/deluge/gui/l10n/english.json +++ b/src/deluge/gui/l10n/english.json @@ -299,8 +299,11 @@ "STRING_FOR_ARRANGEMENT_CLEARED": "Arrangement cleared", "STRING_FOR_EMPTY_CLIP_INSTANCES_CANT_BE_MOVED_TO_THE_SESSION": "Empty clip instances can't be moved to the session", "STRING_FOR_CANT_EDIT_LENGTH": "Can't edit length", - "STRING_FOR_QUANTIZE_ALL_ROW": "QUANTIZE ALL ROW", + "STRING_FOR_HUMANIZE_OR_QUANTIZE_ALL": "HUMANIZE/QUANTIZE ALL", + "STRING_FOR_QUANTIZE_ALL": "QUANTIZE ALL", "STRING_FOR_QUANTIZE": "QUANTIZE", + "STRING_FOR_HUMANIZE_ALL": "HUMANIZE ALL", + "STRING_FOR_HUMANIZE": "HUMANIZE", "STRING_FOR_VELOCITY_DECREASED": "Velocity decreased", "STRING_FOR_VELOCITY_INCREASED": "Velocity increased", "STRING_FOR_RANDOMIZED": "Randomized", diff --git a/src/deluge/gui/l10n/g_english.cpp b/src/deluge/gui/l10n/g_english.cpp index 40f5eb76fd..1e1885f876 100644 --- a/src/deluge/gui/l10n/g_english.cpp +++ b/src/deluge/gui/l10n/g_english.cpp @@ -261,8 +261,11 @@ PLACE_SDRAM_DATA Language english{ {STRING_FOR_ARRANGEMENT_CLEARED, "Arrangement cleared"}, {STRING_FOR_EMPTY_CLIP_INSTANCES_CANT_BE_MOVED_TO_THE_SESSION, "Empty clip instances can't be moved to the session"}, {STRING_FOR_CANT_EDIT_LENGTH, "Can't edit length"}, - {STRING_FOR_QUANTIZE_ALL_ROW, "QUANTIZE ALL ROW"}, + {STRING_FOR_HUMANIZE_OR_QUANTIZE_ALL, "HUMANIZE/QUANTIZE ALL"}, + {STRING_FOR_QUANTIZE_ALL, "QUANTIZE ALL"}, {STRING_FOR_QUANTIZE, "QUANTIZE"}, + {STRING_FOR_HUMANIZE_ALL, "HUMANIZE ALL"}, + {STRING_FOR_HUMANIZE, "HUMANIZE"}, {STRING_FOR_VELOCITY_DECREASED, "Velocity decreased"}, {STRING_FOR_VELOCITY_INCREASED, "Velocity increased"}, {STRING_FOR_RANDOMIZED, "Randomized"}, diff --git a/src/deluge/gui/l10n/g_seven_segment.cpp b/src/deluge/gui/l10n/g_seven_segment.cpp index 648ac69eb0..22a35be454 100644 --- a/src/deluge/gui/l10n/g_seven_segment.cpp +++ b/src/deluge/gui/l10n/g_seven_segment.cpp @@ -178,8 +178,11 @@ PLACE_SDRAM_DATA Language seven_segment{ {STRING_FOR_ARRANGEMENT_CLEARED, "CLEAR"}, {STRING_FOR_EMPTY_CLIP_INSTANCES_CANT_BE_MOVED_TO_THE_SESSION, "EMPTY"}, {STRING_FOR_CANT_EDIT_LENGTH, "CANT"}, - {STRING_FOR_QUANTIZE_ALL_ROW, "QTZA"}, + {STRING_FOR_HUMANIZE_OR_QUANTIZE_ALL, "ALL"}, + {STRING_FOR_QUANTIZE_ALL, "QTZA"}, {STRING_FOR_QUANTIZE, "QTZ"}, + {STRING_FOR_HUMANIZE_ALL, "HUMA"}, + {STRING_FOR_HUMANIZE, "HUM"}, {STRING_FOR_VELOCITY_DECREASED, "LESS"}, {STRING_FOR_VELOCITY_INCREASED, "MORE"}, {STRING_FOR_RANDOMIZED, "RND"}, diff --git a/src/deluge/gui/l10n/seven_segment.json b/src/deluge/gui/l10n/seven_segment.json index 8496808e74..e924cc0dae 100644 --- a/src/deluge/gui/l10n/seven_segment.json +++ b/src/deluge/gui/l10n/seven_segment.json @@ -187,8 +187,11 @@ "STRING_FOR_ARRANGEMENT_CLEARED": "CLEAR", "STRING_FOR_EMPTY_CLIP_INSTANCES_CANT_BE_MOVED_TO_THE_SESSION": "EMPTY", "STRING_FOR_CANT_EDIT_LENGTH": "CANT", - "STRING_FOR_QUANTIZE_ALL_ROW": "QTZA", + "STRING_FOR_HUMANIZE_OR_QUANTIZE_ALL": "ALL", + "STRING_FOR_QUANTIZE_ALL": "QTZA", "STRING_FOR_QUANTIZE": "QTZ", + "STRING_FOR_HUMANIZE_ALL": "HUMA", + "STRING_FOR_HUMANIZE": "HUM", "STRING_FOR_VELOCITY_DECREASED": "LESS", "STRING_FOR_VELOCITY_INCREASED": "MORE", "STRING_FOR_RANDOMIZED": "RND", diff --git a/src/deluge/gui/l10n/strings.h b/src/deluge/gui/l10n/strings.h index f5072b1743..b009dc843f 100644 --- a/src/deluge/gui/l10n/strings.h +++ b/src/deluge/gui/l10n/strings.h @@ -278,8 +278,11 @@ enum class String : size_t { STRING_FOR_ARRANGEMENT_CLEARED, STRING_FOR_EMPTY_CLIP_INSTANCES_CANT_BE_MOVED_TO_THE_SESSION, STRING_FOR_CANT_EDIT_LENGTH, - STRING_FOR_QUANTIZE_ALL_ROW, + STRING_FOR_HUMANIZE_OR_QUANTIZE_ALL, + STRING_FOR_QUANTIZE_ALL, STRING_FOR_QUANTIZE, + STRING_FOR_HUMANIZE_ALL, + STRING_FOR_HUMANIZE, STRING_FOR_VELOCITY_DECREASED, STRING_FOR_VELOCITY_INCREASED, STRING_FOR_RANDOMIZED, diff --git a/src/deluge/gui/ui/ui.h b/src/deluge/gui/ui/ui.h index 463deba02b..fc729b1470 100644 --- a/src/deluge/gui/ui/ui.h +++ b/src/deluge/gui/ui/ui.h @@ -80,6 +80,7 @@ extern bool pendingUIRenderingLock; // Non-exclusive UI modes, which can (if the code allows) occur at the same time as other ones, including the // "exclusive" ones above. +#define UI_MODE_QUANTIZE (1 << 27) #define UI_MODE_STUTTERING (1 << 28) #define UI_MODE_HORIZONTAL_SCROLL (1 << 29) #define UI_MODE_AUDITIONING (1 << 30) diff --git a/src/deluge/gui/views/instrument_clip_view.cpp b/src/deluge/gui/views/instrument_clip_view.cpp index 635ad827de..24a552213f 100644 --- a/src/deluge/gui/views/instrument_clip_view.cpp +++ b/src/deluge/gui/views/instrument_clip_view.cpp @@ -665,9 +665,11 @@ ActionResult InstrumentClipView::buttonAction(deluge::hid::Button b, bool on, bo copyNotes(); } } - else if (b == TEMPO_ENC && isUIModeActiveExclusively(UI_MODE_NOTES_PRESSED) + else if (b == TEMPO_ENC && isUIModeActive(UI_MODE_AUDITIONING) && runtimeFeatureSettings.get(RuntimeFeatureSettingType::Quantize) == RuntimeFeatureStateToggle::On) { - // prevent Tempo pop-up , when note is pressed + // Prevent Tempo pop-up when auditioning: audition + tempo press is how quantize/humanize all starts. + // Intentionally not using PopupType::QUANITZE, so we display the info for the direction on first detent. + display->popupTextTemporary(deluge::l10n::get(deluge::l10n::String::STRING_FOR_HUMANIZE_OR_QUANTIZE_ALL)); } // Horizontal encoder button else if (b == X_ENC) { @@ -1613,13 +1615,20 @@ ActionResult InstrumentClipView::padAction(int32_t x, int32_t y, int32_t velocit } } + // We're quantizing: either adding a new note to the set being quantized, or removing. + // In the first case we simply defer to auditionPadAction. + else if (isUIModeActive(UI_MODE_QUANTIZE)) { + if (velocity) { + return auditionPadAction(velocity, y, true); + } + else { + return commandStopQuantize(y); + } + } + // Actual basic audition pad press: else if (!velocity || isUIModeWithinRange(auditionPadActionUIModes)) { - exitUIMode(UI_MODE_DRAGGING_KIT_NOTEROW); - if (sdRoutineLock && !allowSomeUserActionsEvenWhenInCardRoutine) { - return ActionResult::REMIND_ME_OUTSIDE_CARD_ROUTINE; // Allowable sometimes if in card routine. - } - auditionPadAction(velocity, y, Buttons::isShiftButtonPressed()); + return auditionPadAction(velocity, y, Buttons::isShiftButtonPressed()); } } } @@ -3462,9 +3471,13 @@ void InstrumentClipView::setSelectedDrum(Drum* drum, bool shouldRedrawStuff, Kit } } -void InstrumentClipView::auditionPadAction(int32_t velocity, int32_t yDisplay, bool shiftButtonDown) { +ActionResult InstrumentClipView::auditionPadAction(int32_t velocity, int32_t yDisplay, bool shiftButtonDown) { + exitUIMode(UI_MODE_DRAGGING_KIT_NOTEROW); + if (sdRoutineLock && !allowSomeUserActionsEvenWhenInCardRoutine) { + return ActionResult::REMIND_ME_OUTSIDE_CARD_ROUTINE; // Allowable sometimes if in card routine. + } + if (editedAnyPerNoteRowStuffSinceAuditioningBegan && !velocity) { - // in case we were editing quantize/humanize actionLogger.closeAction(ActionType::NOTE_NUDGE); } @@ -3488,7 +3501,8 @@ void InstrumentClipView::auditionPadAction(int32_t velocity, int32_t yDisplay, b drum = getAuditionedDrum(velocity, yDisplay, shiftButtonDown, instrument, modelStackWithTimelineCounter, modelStackWithNoteRowOnCurrentClip); if (drum == nullptr) { - return; // don't continue auditioning if drum is null + return ActionResult::DEALT_WITH; + ; // don't continue auditioning if drum is null } } @@ -3545,6 +3559,8 @@ void InstrumentClipView::auditionPadAction(int32_t velocity, int32_t yDisplay, b if (!clipIsActiveOnInstrument && velocity) { indicator_leds::indicateAlertOnLed(IndicatorLED::SESSION_VIEW); } + + return ActionResult::DEALT_WITH; } // sub-function of AuditionPadAction @@ -4766,37 +4782,69 @@ void InstrumentClipView::rotateNoteRowHorizontally(int32_t offset) { } void InstrumentClipView::tempoEncoderAction(int8_t offset, bool encoderButtonPressed, bool shiftButtonPressed) { - - if (isUIModeActive(UI_MODE_AUDITIONING) - && runtimeFeatureSettings.get(RuntimeFeatureSettingType::Quantize) - == RuntimeFeatureStateToggle::On) { // quantize - if (encoderButtonPressed) { - quantizeNotes(offset, NUDGEMODE_QUANTIZE_ALL); - } - else { - quantizeNotes(offset, NUDGEMODE_QUANTIZE); - } + auto quantizeType = encoderButtonPressed ? NudgeMode::QUANTIZE_ALL : NudgeMode::QUANTIZE; + if (isUIModeActive(UI_MODE_QUANTIZE)) { + commandQuantizeNotes(offset, quantizeType); + } + else if (isUIModeActive(UI_MODE_AUDITIONING) + && runtimeFeatureSettings.get(RuntimeFeatureSettingType::Quantize) + == RuntimeFeatureStateToggle::On) { // quantize + commandStartQuantize(offset, quantizeType); } else { playbackHandler.tempoEncoderAction(offset, encoderButtonPressed, shiftButtonPressed); } } -void InstrumentClipView::quantizeNotes(int32_t offset, int32_t nudgeMode) { +void appendQuantizeMode(StringBuf& text, int8_t direction, NudgeMode mode) { + switch (mode) { + case NudgeMode::QUANTIZE: + if (direction >= 0) { + text.append(deluge::l10n::get(deluge::l10n::String::STRING_FOR_QUANTIZE)); + } + else { + text.append(deluge::l10n::get(deluge::l10n::String::STRING_FOR_HUMANIZE)); + } + break; + case NudgeMode::QUANTIZE_ALL: + if (direction >= 0) { + text.append(deluge::l10n::get(deluge::l10n::String::STRING_FOR_QUANTIZE_ALL)); + } + else { + text.append(deluge::l10n::get(deluge::l10n::String::STRING_FOR_HUMANIZE_ALL)); + } + break; + } +} - shouldIgnoreHorizontalScrollKnobActionIfNotAlsoPressedForThisNotePress = true; +void InstrumentClipView::commandStartQuantize(int8_t offset, NudgeMode mode) { + auditioningSilently = true; + reassessAllAuditionStatus(); + enterUIMode(UI_MODE_QUANTIZE); + quantizeAmount = 0; + DEF_STACK_STRING_BUF(text, 30); + appendQuantizeMode(text, offset, mode); + display->popupText(text.c_str(), PopupType::QUANTIZE); +} - // just popping up - if (!offset) { - quantizeAmount = 0; - if (nudgeMode == NUDGEMODE_QUANTIZE) { - display->displayPopup(deluge::l10n::get(deluge::l10n::String::STRING_FOR_QUANTIZE)); - } - else if (nudgeMode == NUDGEMODE_QUANTIZE_ALL) { - display->displayPopup(deluge::l10n::get(deluge::l10n::String::STRING_FOR_QUANTIZE_ALL_ROW)); +ActionResult InstrumentClipView::commandStopQuantize(int32_t y) { + auto res = auditionPadAction(0, y, true); + if (res != ActionResult::DEALT_WITH) { + return res; + } + if (!getNumNoteRowsAuditioning()) { + // No pads pressed, can actually stop quantizing + if (display->hasPopupOfType(PopupType::QUANTIZE)) { + display->cancelPopup(); } - return; + exitUIMode(UI_MODE_QUANTIZE); } + return ActionResult::DEALT_WITH; + ; +} + +void InstrumentClipView::commandQuantizeNotes(int8_t offset, NudgeMode nudgeMode) { + shouldIgnoreHorizontalScrollKnobActionIfNotAlsoPressedForThisNotePress = true; int32_t squareSize = getPosFromSquare(1) - getPosFromSquare(0); @@ -4815,17 +4863,21 @@ void InstrumentClipView::quantizeNotes(int32_t offset, int32_t nudgeMode) { } if (display->haveOLED()) { - char buffer[24]; - snprintf(buffer, sizeof(buffer), "%s %s%d%%", //< - (quantizeAmount >= 0) ? "Quantize" : "Humanize", //< - (nudgeMode == NUDGEMODE_QUANTIZE) ? "" : "All ", //< - abs(quantizeAmount * 10)); - display->popupTextTemporary(buffer); + DEF_STACK_STRING_BUF(text, 24); + appendQuantizeMode(text, quantizeAmount, nudgeMode); + text.append(" "); + text.appendInt(abs(quantizeAmount * 10)); + text.append("%"); + display->popupText(text.c_str(), PopupType::QUANTIZE); } else { - char buffer[5]; - snprintf(buffer, sizeof(buffer), "%d", quantizeAmount * 10); // Negative means humanize - display->displayPopup(buffer, 0, true); + DEF_STACK_STRING_BUF(text, 6); + // Put A in front for QUANTIZE ALL if there's space for it. + if (nudgeMode == NudgeMode::QUANTIZE_ALL && quantizeAmount > -10) { + text.append("A"); + } + text.appendInt(quantizeAmount * 10); // Negative means humanize + display->popupText(text.c_str(), PopupType::QUANTIZE); } char modelStackMemory[MODEL_STACK_MAX_SIZE]; @@ -4851,11 +4903,11 @@ void InstrumentClipView::quantizeNotes(int32_t offset, int32_t nudgeMode) { uint32_t nRows{0}; switch (nudgeMode) { - case NUDGEMODE_QUANTIZE: + case NudgeMode::QUANTIZE: nRows = kDisplayHeight; quantizeAll = false; break; - case NUDGEMODE_QUANTIZE_ALL: + case NudgeMode::QUANTIZE_ALL: nRows = currentClip->noteRows.getNumElements(); quantizeAll = true; break; @@ -4865,7 +4917,7 @@ void InstrumentClipView::quantizeNotes(int32_t offset, int32_t nudgeMode) { uint32_t rowUpdateMask = 0; - for (auto i = 0; i < nRows; ++i) { + for (int32_t i = 0; i < nRows; ++i) { ModelStackWithNoteRow* modelStackWithNoteRow; NoteRow* thisNoteRow{nullptr}; if (quantizeAll) { diff --git a/src/deluge/gui/views/instrument_clip_view.h b/src/deluge/gui/views/instrument_clip_view.h index 87ad3abf8c..0b57d81825 100644 --- a/src/deluge/gui/views/instrument_clip_view.h +++ b/src/deluge/gui/views/instrument_clip_view.h @@ -61,8 +61,7 @@ struct EditPadPress { #define MPE_RECORD_LENGTH_FOR_NOTE_EDITING 3 #define MPE_RECORD_INTERVAL_TIME (kSampleRate >> 2) // 250ms -#define NUDGEMODE_QUANTIZE 1 -#define NUDGEMODE_QUANTIZE_ALL 2 +enum class NudgeMode { QUANTIZE, QUANTIZE_ALL }; class InstrumentClipView final : public ClipView, public InstrumentClipMinder { public: @@ -92,7 +91,7 @@ class InstrumentClipView final : public ClipView, public InstrumentClipMinder { void offsetNoteCodeAction(int32_t newOffset); int32_t getYVisualFromYDisplay(int32_t yDisplay); int32_t getYVisualWithinOctaveFromYDisplay(int32_t yDisplay); - void auditionPadAction(int32_t velocity, int32_t yDisplay, bool shiftButtonDown); + ActionResult auditionPadAction(int32_t velocity, int32_t yDisplay, bool shiftButtonDown); void potentiallyRefreshNoteRowMenu(); void enterScaleMode(uint8_t yDisplay = 255); void exitScaleMode(); @@ -247,7 +246,11 @@ class InstrumentClipView final : public ClipView, public InstrumentClipMinder { void rotateNoteRowHorizontally(ModelStackWithNoteRow* modelStack, int32_t offset, int32_t yDisplay, bool shouldDisplayDirectionEvenIfNoNoteRow = false); - void quantizeNotes(int32_t offset, int32_t nudgeMode); + // TEMPO encoder commands + void commandQuantizeNotes(int8_t offset, NudgeMode nudgeMode); + void commandStartQuantize(int8_t offset, NudgeMode nudgeMode); + ActionResult commandStopQuantize(int32_t y); + void silenceAllAuditions(); // auditionPadAction functions Drum* getAuditionedDrum(int32_t velocity, int32_t yDisplay, bool shiftButtonDown, Instrument* instrument, diff --git a/src/deluge/hid/display/display.h b/src/deluge/hid/display/display.h index 1957c345ba..12243aaffa 100644 --- a/src/deluge/hid/display/display.h +++ b/src/deluge/hid/display/display.h @@ -22,6 +22,8 @@ enum class PopupType { SWING, /// Tempo TEMPO, + /// Quantize and humanize + QUANTIZE, // Note: Add here more popup types };