Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quantize #129

Merged
merged 2 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
217 changes: 214 additions & 3 deletions src/deluge/gui/views/instrument_clip_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ int InstrumentClipView::buttonAction(int x, int y, bool on, bool inCardRoutine)
else if (x == xEncButtonX && y == xEncButtonY) {

// If user wants to "multiple" Clip contents
if (on && Buttons::isShiftButtonPressed()) {
if (on && Buttons::isShiftButtonPressed() && !isUIModeActiveExclusively(UI_MODE_NOTES_PRESSED)) {
if (isNoUIModeActive()) {
if (inCardRoutine) return ACTION_RESULT_REMIND_ME_OUTSIDE_CARD_ROUTINE;

Expand All @@ -564,7 +564,21 @@ int InstrumentClipView::buttonAction(int x, int y, bool on, bool inCardRoutine)
else {
if (isUIModeActiveExclusively(UI_MODE_NOTES_PRESSED)) {
if (on) {
nudgeNotes(0);
if (runtimeFeatureSettings.get(RuntimeFeatureSettingType::Quantize)
== RuntimeFeatureStateToggle::On) {
if (Buttons::isShiftButtonPressed()) {
nudgeMode = (nudgeMode + 1) % 3;
}
if (nudgeMode == NUDGEMODE_NUDGE) {
nudgeNotes(0);
}
else if (nudgeMode == NUDGEMODE_QUANTIZE || nudgeMode == NUDGEMODE_QUANTIZE_ALL) {
quantizeNotes(0);
}
}
else {
nudgeNotes(0);
}
}
else {
doCancelPopup:
Expand Down Expand Up @@ -3828,7 +3842,18 @@ int InstrumentClipView::horizontalEncoderAction(int offset) {
&& isUIModeWithinRange(noteNudgeUIModes)) {
if (sdRoutineLock)
return ACTION_RESULT_REMIND_ME_OUTSIDE_CARD_ROUTINE; // Just be safe - maybe not necessary
nudgeNotes(offset);

if (runtimeFeatureSettings.get(RuntimeFeatureSettingType::Quantize) == RuntimeFeatureStateToggle::On) {
if (nudgeMode == NUDGEMODE_NUDGE) {
nudgeNotes(offset);
}
else if (nudgeMode == NUDGEMODE_QUANTIZE || nudgeMode == NUDGEMODE_QUANTIZE_ALL) {
quantizeNotes(offset);
}
}
else {
nudgeNotes(offset);
}
}
}
return ACTION_RESULT_DEALT_WITH;
Expand Down Expand Up @@ -3933,6 +3958,192 @@ int InstrumentClipView::horizontalEncoderAction(int offset) {
}
}

void InstrumentClipView::quantizeNotes(int offset) {

shouldIgnoreHorizontalScrollKnobActionIfNotAlsoPressedForThisNotePress = true;

//just popping up
if (!offset) {
quantizeAmount = 0;
if (nudgeMode == NUDGEMODE_QUANTIZE) {
numericDriver.displayPopup(HAVE_OLED ? "QUANTIZE" : "QTZ");
}
else if (nudgeMode == NUDGEMODE_QUANTIZE_ALL) {
numericDriver.displayPopup(HAVE_OLED ? "QUANTIZE ALL ROW" : "QTZA");
}
return;
}

int squareSize = getPosFromSquare(1) - getPosFromSquare(0);
int halfsquareSize = (int)(squareSize / 2);
int quatersquareSize = (int)(squareSize / 4);

if (quantizeAmount >= 10 && offset > 0) return;
if (quantizeAmount <= -10 && offset < 0) return;
quantizeAmount += offset;
if (quantizeAmount >= 10) quantizeAmount = 10;
if (quantizeAmount <= -10) quantizeAmount = -10;

#if HAVE_OLED
char buffer[24];
if (nudgeMode == NUDGEMODE_QUANTIZE) {
strcpy(buffer, (quantizeAmount >= 0) ? "Quantize " : "Humanize ");
}
else {
strcpy(buffer, (quantizeAmount >= 0) ? "Quantize All " : "Humanize All ");
}
intToString(abs(quantizeAmount * 10), buffer + strlen(buffer));
strcpy(buffer + strlen(buffer), "%");
OLED::popupText(buffer);
#else
char buffer[5];
strcpy(buffer, "");
intToString(quantizeAmount * 10, buffer + strlen(buffer)); //Negative means humanize
numericDriver.displayPopup(buffer, 0, true);
#endif

char modelStackMemory[MODEL_STACK_MAX_SIZE];
ModelStack* modelStack = setupModelStackWithSong(modelStackMemory, currentSong);
ModelStackWithTimelineCounter* modelStackWithTimelineCounter =
modelStack->addTimelineCounter(modelStack->song->currentClip);
InstrumentClip* currentClip = getCurrentClip();

if (nudgeMode == NUDGEMODE_QUANTIZE) { // Only the row(s) being pressed

//reset
Action* lastAction = actionLogger.firstAction[BEFORE];
if (lastAction && lastAction->type == ACTION_NOTE_NUDGE && lastAction->openForAdditions)
actionLogger.undoJustOneConsequencePerNoteRow(modelStack);

Action* action = NULL;
if (offset) {
action = actionLogger.getNewAction(ACTION_NOTE_NUDGE, ACTION_ADDITION_ALLOWED);
if (action) action->offset = quantizeAmount;
}

NoteRow* thisNoteRow;
int noteRowId;
for (int i = 0; i < editPadPressBufferSize; i++) {
if (editPadPresses[i].isActive) {

int noteRowIndex;
thisNoteRow = currentClip->getNoteRowOnScreen(editPadPresses[i].yDisplay, currentSong, &noteRowIndex);
noteRowId = currentClip->getNoteRowId(thisNoteRow, noteRowIndex);

ModelStackWithNoteRow* modelStackWithNoteRow =
modelStackWithTimelineCounter->addNoteRow(noteRowId, thisNoteRow);

int32_t noteRowEffectiveLength = modelStackWithNoteRow->getLoopLength();

if (offset) { //store
action->recordNoteArrayChangeDefinitely(
(InstrumentClip*)modelStackWithNoteRow->getTimelineCounter(), modelStackWithNoteRow->noteRowId,
&(thisNoteRow->notes), false);
}

NoteVector tmpNotes;
tmpNotes.cloneFrom(&thisNoteRow->notes); //backup
for (int j = 0; j < tmpNotes.getNumElements(); j++) {

Note* note = tmpNotes.getElement(j);

int32_t destination = (trunc((note->pos - 1 + halfsquareSize) / squareSize)) * squareSize;
if (quantizeAmount < 0) { //Humanize
int32_t hmAmout = trunc(random(quatersquareSize) - (quatersquareSize / 2.5));
destination = note->pos + hmAmout;
}
int32_t distance = destination - note->pos;
distance = trunc((distance * abs(quantizeAmount)) / 10);

if (distance != 0) {
for (int k = 0; k < abs(distance); k++) {
int32_t nowPos = (note->pos + ((distance > 0) ? k : -k) + noteRowEffectiveLength)
% noteRowEffectiveLength;
int error = thisNoteRow->nudgeNotesAcrossAllScreens(
nowPos, modelStackWithNoteRow, NULL, MAX_SEQUENCE_LENGTH, ((distance > 0) ? 1 : -1));
if (error) {
numericDriver.displayError(error);
return;
}
}
}
}
}
}
}
else if (nudgeMode == NUDGEMODE_QUANTIZE_ALL) { //All Row

//reset
Action* lastAction = actionLogger.firstAction[BEFORE];
if (lastAction && lastAction->type == ACTION_NOTE_NUDGE && lastAction->openForAdditions)
actionLogger.undoJustOneConsequencePerNoteRow(modelStack);

Action* action = NULL;
if (offset) {
action = actionLogger.getNewAction(ACTION_NOTE_NUDGE, ACTION_ADDITION_ALLOWED);
if (action) action->offset = offset;
}

for (int i = 0; i < getCurrentClip()->noteRows.getNumElements(); i++) {
NoteRow* thisNoteRow = getCurrentClip()->noteRows.getElement(i);

int noteRowId;
int noteRowIndex;
noteRowId = getCurrentClip()->getNoteRowId(thisNoteRow, i);

ModelStackWithNoteRow* modelStackWithNoteRow =
modelStackWithTimelineCounter->addNoteRow(noteRowId, thisNoteRow);
int32_t noteRowEffectiveLength = modelStackWithNoteRow->getLoopLength();

// If this NoteRow has any notes...
if (!thisNoteRow->hasNoNotes()) {

if (offset) { //store
action->recordNoteArrayChangeDefinitely(
(InstrumentClip*)modelStackWithNoteRow->getTimelineCounter(), modelStackWithNoteRow->noteRowId,
&(thisNoteRow->notes), false);
}

NoteVector tmpNotes;
tmpNotes.cloneFrom(&thisNoteRow->notes); //backup
for (int j = 0; j < tmpNotes.getNumElements(); j++) {
Note* note = tmpNotes.getElement(j);

int32_t destination = (trunc((note->pos - 1 + halfsquareSize) / squareSize)) * squareSize;
if (quantizeAmount < 0) { //Humanize
int32_t hmAmout = trunc(random(quatersquareSize) - (quatersquareSize / 2.5));
destination = note->pos + hmAmout;
}
int32_t distance = destination - note->pos;
distance = trunc((distance * abs(quantizeAmount)) / 10);

if (distance != 0) {
for (int k = 0; k < abs(distance); k++) {
int32_t nowPos = (note->pos + ((distance > 0) ? k : -k) + noteRowEffectiveLength)
% noteRowEffectiveLength;
int error = thisNoteRow->nudgeNotesAcrossAllScreens(
nowPos, modelStackWithNoteRow, NULL, MAX_SEQUENCE_LENGTH, ((distance > 0) ? 1 : -1));
if (error) {
numericDriver.displayError(error);
return;
}
}
}
}
}
}
}

uiNeedsRendering(this, 0xFFFFFFFF, 0);
{
if (playbackHandler.isEitherClockActive() && modelStackWithTimelineCounter->song->isClipActive(currentClip)) {
currentClip->expectEvent();
currentClip->reGetParameterAutomation(modelStackWithTimelineCounter);
}
}
return;
}

// Supply offset as 0 to just popup number, not change anything
void InstrumentClipView::editNoteRepeat(int offset) {

Expand Down
9 changes: 9 additions & 0 deletions src/deluge/gui/views/instrument_clip_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ struct EditPadPress {
#define MPE_RECORD_LENGTH_FOR_NOTE_EDITING 3
#define MPE_RECORD_INTERVAL_TIME (44100 >> 2) // 250ms

#define NUDGEMODE_NUDGE 0
#define NUDGEMODE_QUANTIZE 1
#define NUDGEMODE_QUANTIZE_ALL 2

class InstrumentClipView final : public ClipView, public InstrumentClipMinder {
public:
InstrumentClipView();
Expand Down Expand Up @@ -185,6 +189,9 @@ class InstrumentClipView final : public ClipView, public InstrumentClipMinder {
Drum* drumForNewNoteRow;
uint8_t yDisplayOfNewNoteRow;

int32_t quantizeAmount;
int nudgeMode;

uint32_t getSquareWidth(int32_t square, int32_t effectiveLength);
void drawNoteCode(uint8_t yDisplay);
void sendAuditionNote(bool on, uint8_t yDisplay, uint8_t velocity, uint32_t sampleSyncLength);
Expand Down Expand Up @@ -220,6 +227,8 @@ class InstrumentClipView final : public ClipView, public InstrumentClipMinder {
void editNoteRowLength(ModelStackWithNoteRow* modelStack, int offset, int yDisplay);
ModelStackWithNoteRow* createNoteRowForYDisplay(ModelStackWithTimelineCounter* modelStack, int yDisplay);
ModelStackWithNoteRow* getOrCreateNoteRowForYDisplay(ModelStackWithTimelineCounter* modelStack, int yDisplay);

void quantizeNotes(int offset);
};

extern InstrumentClipView instrumentClipView;
Expand Down
9 changes: 9 additions & 0 deletions src/deluge/model/settings/runtime_feature_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ enum RuntimeFeatureStateToggle : uint32_t { Off = 0, On = 1 };
enum RuntimeFeatureSettingType : uint32_t {
// FileFolderSorting // @TODO: Replace with actual identifier on first use
DrumRandomizer,
Quantize,
MaxElement // Keep as boundary
};

Expand Down Expand Up @@ -88,6 +89,14 @@ class RuntimeFeatureSettings {
.options = {{.displayName = "Off", .value = RuntimeFeatureStateToggle::Off},
{.displayName = "On", .value = RuntimeFeatureStateToggle::On},
{.displayName = NULL, .value = 0}}},

[RuntimeFeatureSettingType::Quantize] =
{.displayName = "Quantize",
.xmlName = "quantize",
.value = RuntimeFeatureStateToggle::On, // Default value
.options = {{.displayName = "Off", .value = RuntimeFeatureStateToggle::Off},
{.displayName = "On", .value = RuntimeFeatureStateToggle::On},
{.displayName = NULL, .value = 0}}},
};

private:
Expand Down