Skip to content

Commit

Permalink
Merge pull request #141 from topisani/feature/audio-clip-rotate
Browse files Browse the repository at this point in the history
Horizontal Shift for Audio Clips
  • Loading branch information
jamiefaye committed Jul 5, 2023
2 parents bc71465 + 479a27b commit 2e1ba62
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 89 deletions.
4 changes: 4 additions & 0 deletions CommunityFeatures.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ Synchronization modes accessible through the "LFO SYNC" shortcut.

### Kit Keyboard View
- ([#112]) All-new use for the "keyboard" button in kit clips, uses the main pad grid for MPC-style 16 level playing. Horizonatal encoder scrolls by one pad at a time, allowing positioning drums left to right, and vertical encoder jumps vertically by rows.

### Audio Clip View
- ([#141]) Holding the vertical encoder down while turning the horizontal encoder will shift the clip along the underlying audio file, similar to the same interface for instrument clips.


<h1 id="runtime-features">Runtime settings aka Community Features Menu</h1>
Expand Down Expand Up @@ -74,3 +77,4 @@ This list includes all preprocessor switches that can alter firmware behaviour a
[#103]: https://github.com/SynthstromAudible/DelugeFirmware/pull/103
[#112]: https://github.com/SynthstromAudible/DelugeFirmware/pull/112
[#122]: https://github.com/SynthstromAudible/DelugeFirmware/pull/122
[#141]: https://github.com/SynthstromAudible/DelugeFirmware/pull/141
101 changes: 81 additions & 20 deletions src/deluge/gui/views/clip_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,23 @@
#include "model/clip/clip_minder.h"
#include "gui/views/view.h"
#include "definitions.h"
#include "model/consequence/consequence_clip_horizontal_shift.h"
#include "memory/general_memory_allocator.h"
#include <new>

static Clip* getCurrentClip() {
return currentSong->currentClip;
}

ClipView::ClipView() {
}

unsigned int ClipView::getMaxZoom() {
return currentSong->currentClip->getMaxZoom();
return getCurrentClip()->getMaxZoom();
}

uint32_t ClipView::getMaxLength() {
return currentSong->currentClip->getMaxLength();
return getCurrentClip()->getMaxLength();
}

void ClipView::focusRegained() {
Expand All @@ -48,14 +55,14 @@ void ClipView::focusRegained() {
int ClipView::buttonAction(int x, int y, bool on, bool inCardRoutine) {

// Horizontal encoder button press-down - don't let it do its zoom level thing if zooming etc not currently accessible
if (x == xEncButtonX && y == xEncButtonY && on && !currentSong->currentClip->currentlyScrollableAndZoomable()) {}
if (x == xEncButtonX && y == xEncButtonY && on && !getCurrentClip()->currentlyScrollableAndZoomable()) {}

#ifdef BUTTON_SEQUENCE_DIRECTION_X
else if (x == BUTTON_SEQUENCE_DIRECTION_X && y == BUTTON_SEQUENCE_DIRECTION_Y) {
if (on && isNoUIModeActive()) {
currentSong->currentClip->sequenceDirection++;
if (currentSong->currentClip->sequenceDirection == NUM_SEQUENCE_DIRECTION_OPTIONS) {
currentSong->currentClip->sequenceDirection = 0;
getCurrentClip()->sequenceDirection++;
if (getCurrentClip()->sequenceDirection == NUM_SEQUENCE_DIRECTION_OPTIONS) {
getCurrentClip()->sequenceDirection = 0;
}
view.setModLedStates();
}
Expand All @@ -78,7 +85,7 @@ Action* ClipView::lengthenClip(int32_t newLength) {
// If the last action was a shorten, undo it
bool undoing = (actionLogger.firstAction[BEFORE] && actionLogger.firstAction[BEFORE]->openForAdditions
&& actionLogger.firstAction[BEFORE]->type == ACTION_CLIP_LENGTH_DECREASE
&& actionLogger.firstAction[BEFORE]->currentClip == currentSong->currentClip);
&& actionLogger.firstAction[BEFORE]->currentClip == getCurrentClip());

if (undoing) {
allowResyncingDuringClipLengthChange =
Expand All @@ -88,16 +95,16 @@ Action* ClipView::lengthenClip(int32_t newLength) {
}

// Only if that didn't get us directly to the correct length, manually set length. This will do a resync if playback active
if (currentSong->currentClip->loopLength != newLength) {
int actionType = (newLength < currentSong->currentClip->loopLength) ? ACTION_CLIP_LENGTH_DECREASE
: ACTION_CLIP_LENGTH_INCREASE;
if (getCurrentClip()->loopLength != newLength) {
int actionType =
(newLength < getCurrentClip()->loopLength) ? ACTION_CLIP_LENGTH_DECREASE : ACTION_CLIP_LENGTH_INCREASE;

action = actionLogger.getNewAction(actionType, true);
if (action && action->currentClip != currentSong->currentClip) {
if (action && action->currentClip != getCurrentClip()) {
action = actionLogger.getNewAction(actionType, false);
}

currentSong->setClipLength(currentSong->currentClip, newLength, action);
currentSong->setClipLength(getCurrentClip(), newLength, action);
}

// Otherwise, do the resync that we missed out on doing
Expand All @@ -119,12 +126,12 @@ Action* ClipView::shortenClip(int32_t newLength) {
Action* action = NULL;

action = actionLogger.getNewAction(ACTION_CLIP_LENGTH_DECREASE, true);
if (action && action->currentClip != currentSong->currentClip) {
if (action && action->currentClip != getCurrentClip()) {
action = actionLogger.getNewAction(ACTION_CLIP_LENGTH_DECREASE, false);
}

currentSong->setClipLength(
currentSong->currentClip, newLength,
getCurrentClip(), newLength,
action); // Subsequently shortening by more squares won't cause additional Consequences to be added to the same
// Action - it checks, and only stores the data (snapshots and original length) once
return action;
Expand All @@ -137,12 +144,12 @@ int ClipView::horizontalEncoderAction(int offset) {
&& (Buttons::isShiftButtonPressed() || Buttons::isButtonPressed(clipViewButtonX, clipViewButtonY))) {

// If tempoless recording, don't allow
if (!currentSong->currentClip->currentlyScrollableAndZoomable()) {
if (!getCurrentClip()->currentlyScrollableAndZoomable()) {
numericDriver.displayPopup(HAVE_OLED ? "Can't edit length" : "CANT");
return ACTION_RESULT_DEALT_WITH;
}

uint32_t oldLength = currentSong->currentClip->loopLength;
uint32_t oldLength = getCurrentClip()->loopLength;

// If we're not scrolled all the way to the right, go there now
if (scrollRightToEndOfLengthIfNecessary(oldLength)) {
Expand Down Expand Up @@ -214,11 +221,65 @@ int ClipView::horizontalEncoderAction(int offset) {
return ACTION_RESULT_DEALT_WITH;
}

// Or, maybe shift everything horizontally
else if ((isNoUIModeActive() && Buttons::isButtonPressed(yEncButtonX, yEncButtonY))
|| (isUIModeActiveExclusively(UI_MODE_HOLDING_HORIZONTAL_ENCODER_BUTTON)
&& Buttons::isButtonPressed(clipViewButtonX, clipViewButtonY))) {
if (sdRoutineLock) return ACTION_RESULT_REMIND_ME_OUTSIDE_CARD_ROUTINE; // Just be safe - maybe not necessary
int squareSize = getPosFromSquare(1) - getPosFromSquare(0);
int shiftAmount = offset * squareSize;
Clip* clip = getCurrentClip();

char modelStackMemory[MODEL_STACK_MAX_SIZE];
ModelStackWithTimelineCounter* modelStack = currentSong->setupModelStackWithCurrentClip(modelStackMemory);

bool wasShifted = clip->shiftHorizontally(modelStack, shiftAmount);
if (!wasShifted) {
// No need to show the user why it didnt succeed, usually these cases are fairly trivial
return ACTION_RESULT_DEALT_WITH;
}

uiNeedsRendering(this, 0xFFFFFFFF, 0);

// If possible, just modify a previous Action to add this new shift amount to it.
Action* action = actionLogger.firstAction[BEFORE];
if (action && action->type == ACTION_CLIP_HORIZONTAL_SHIFT && action->openForAdditions
&& action->currentClip == clip) {

// If there's no Consequence in the Action, that's probably because we deleted it a previous time with the code just below.
// Or possibly because the Action was created but there wasn't enough RAM to create the Consequence. Anyway, just go add a consequence now.
if (!action->firstConsequence) goto addConsequenceToAction;

ConsequenceClipHorizontalShift* consequence = (ConsequenceClipHorizontalShift*)action->firstConsequence;
consequence->amount += shiftAmount;

// It might look tempting that if we've completed one whole loop, we could delete the Consequence because everything would be back the same -
// but no! Remember different NoteRows might have different lengths.
}

// Or if no previous Action, go create a new one now.
else {

action = actionLogger.getNewAction(ACTION_CLIP_HORIZONTAL_SHIFT, ACTION_ADDITION_NOT_ALLOWED);
if (action) {
addConsequenceToAction:
void* consMemory = generalMemoryAllocator.alloc(sizeof(ConsequenceClipHorizontalShift));

if (consMemory) {
ConsequenceClipHorizontalShift* newConsequence =
new (consMemory) ConsequenceClipHorizontalShift(shiftAmount);
action->addConsequence(newConsequence);
}
}
}
return ACTION_RESULT_DEALT_WITH;
}

// Or, if shift button not pressed...
else {

// If tempoless recording, don't allow
if (!currentSong->currentClip->currentlyScrollableAndZoomable()) {
if (!getCurrentClip()->currentlyScrollableAndZoomable()) {
return ACTION_RESULT_DEALT_WITH;
}

Expand Down Expand Up @@ -268,14 +329,14 @@ int32_t ClipView::getLengthExtendAmount(int32_t square) {

int ClipView::getTickSquare() {

int newTickSquare = getSquareFromPos(currentSong->currentClip->getLivePos());
int newTickSquare = getSquareFromPos(getCurrentClip()->getLivePos());

// See if we maybe want to do an auto-scroll
if (currentSong->currentClip->getCurrentlyRecordingLinearly()) {
if (getCurrentClip()->getCurrentlyRecordingLinearly()) {

if (newTickSquare == displayWidth && (!currentUIMode || currentUIMode == UI_MODE_AUDITIONING)
&& getCurrentUI() == this && // currentPlaybackMode == &session &&
(!currentSong->currentClip->armState || xScrollBeforeFollowingAutoExtendingLinearRecording != -1)) {
(!getCurrentClip()->armState || xScrollBeforeFollowingAutoExtendingLinearRecording != -1)) {

if (xScrollBeforeFollowingAutoExtendingLinearRecording == -1) {
xScrollBeforeFollowingAutoExtendingLinearRecording = currentSong->xScroll[NAVIGATION_CLIP];
Expand Down
55 changes: 0 additions & 55 deletions src/deluge/gui/views/instrument_clip_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
#include "gui/ui/rename/rename_drum_ui.h"
#include "model/song/song.h"
#include "model/clip/clip.h"
#include "model/consequence/consequence_instrument_clip_horizontal_shift.h"
#include "model/consequence/consequence_note_array_change.h"
#include "gui/menu_item/colour.h"
#include "hid/led/pad_leds.h"
Expand Down Expand Up @@ -4184,60 +4183,6 @@ int InstrumentClipView::horizontalEncoderAction(int offset) {
return ACTION_RESULT_DEALT_WITH;
}

// Or, maybe shift everything horizontally
else if ((isNoUIModeActive() && Buttons::isButtonPressed(yEncButtonX, yEncButtonY))
|| (isUIModeActiveExclusively(UI_MODE_HOLDING_HORIZONTAL_ENCODER_BUTTON)
&& Buttons::isButtonPressed(clipViewButtonX, clipViewButtonY))) {
if (sdRoutineLock) {
return ACTION_RESULT_REMIND_ME_OUTSIDE_CARD_ROUTINE; // Just be safe - maybe not necessary
}
int squareSize = getPosFromSquare(1) - getPosFromSquare(0);
int shiftAmount = offset * squareSize;
InstrumentClip* clip = getCurrentClip();

char modelStackMemory[MODEL_STACK_MAX_SIZE];
ModelStackWithTimelineCounter* modelStack = currentSong->setupModelStackWithCurrentClip(modelStackMemory);

clip->shiftHorizontally(modelStack, shiftAmount);
uiNeedsRendering(this, 0xFFFFFFFF, 0);

// If possible, just modify a previous Action to add this new shift amount to it.
Action* action = actionLogger.firstAction[BEFORE];
if (action && action->type == ACTION_INSTRUMENT_CLIP_HORIZONTAL_SHIFT && action->openForAdditions
&& action->currentClip == clip) {

// If there's no Consequence in the Action, that's probably because we deleted it a previous time with the code just below.
// Or possibly because the Action was created but there wasn't enough RAM to create the Consequence. Anyway, just go add a consequence now.
if (!action->firstConsequence) {
goto addConsequenceToAction;
}

ConsequenceInstrumentClipHorizontalShift* consequence =
(ConsequenceInstrumentClipHorizontalShift*)action->firstConsequence;
consequence->amount += shiftAmount;

// It might look tempting that if we've completed one whole loop, we could delete the Consequence because everything would be back the same -
// but no! Remember different NoteRows might have different lengths.
}

// Or if no previous Action, go create a new one now.
else {

action = actionLogger.getNewAction(ACTION_INSTRUMENT_CLIP_HORIZONTAL_SHIFT, ACTION_ADDITION_NOT_ALLOWED);
if (action) {
addConsequenceToAction:
void* consMemory = generalMemoryAllocator.alloc(sizeof(ConsequenceInstrumentClipHorizontalShift));

if (consMemory) {
ConsequenceInstrumentClipHorizontalShift* newConsequence =
new (consMemory) ConsequenceInstrumentClipHorizontalShift(shiftAmount);
action->addConsequence(newConsequence);
}
}
}
return ACTION_RESULT_DEALT_WITH;
}

// Or, let parent deal with it
else {
return ClipView::horizontalEncoderAction(offset);
Expand Down
2 changes: 1 addition & 1 deletion src/deluge/model/action/action.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class ModelStack;
#define ACTION_ARRANGEMENT_TIME_CONTRACT 17
#define ACTION_ARRANGEMENT_CLEAR 18
#define ACTION_ARRANGEMENT_RECORD 19
#define ACTION_INSTRUMENT_CLIP_HORIZONTAL_SHIFT 20
#define ACTION_CLIP_HORIZONTAL_SHIFT 20
#define ACTION_NOTE_NUDGE 21
#define ACTION_NOTE_REPEAT_EDIT 22
#define ACTION_EUCLIDEAN_NUM_EVENTS_EDIT 23
Expand Down
39 changes: 39 additions & 0 deletions src/deluge/model/clip/audio_clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,45 @@ void AudioClip::setPos(ModelStackWithTimelineCounter* modelStack, int32_t newPos
setPosForParamManagers(modelStack, useActualPosForParamManagers);
}

bool AudioClip::shiftHorizontally(ModelStackWithTimelineCounter* modelStack, int amount) {
// No horizontal shift when recording
if (recorder) return false;
// No horizontal shift when no sample is loaded
if (!sampleHolder.audioFile) return false;

int64_t newStartPos = int64_t(sampleHolder.startPos) - getSamplesFromTicks(amount);
uint64_t sampleLength = ((Sample*)sampleHolder.audioFile)->lengthInSamples;

if (newStartPos < 0 || newStartPos > sampleLength) {
return false;
}

if (paramManager.containsAnyParamCollectionsIncludingExpression()) {
paramManager.shiftHorizontally(
modelStack->addOtherTwoThingsButNoNoteRow(output->toModControllable(), &paramManager), amount, loopLength);
}

uint64_t length = sampleHolder.endPos - sampleHolder.startPos;

// Stop the clip if it is playing
bool active = (playbackHandler.isEitherClockActive() && modelStack->song->isClipActive(this) && voiceSample);
unassignVoiceSample();

sampleHolder.startPos = newStartPos;
sampleHolder.endPos = newStartPos + length;

sampleHolder.claimClusterReasons(sampleControls.reversed, CLUSTER_LOAD_IMMEDIATELY_OR_ENQUEUE);

if (active) {
expectEvent();
reGetParameterAutomation(modelStack);

// Resume the clip if it was playing before
currentSong->currentClip->resumePlayback(modelStack, true);
}
return true;
}

uint64_t AudioClip::getCullImmunity() {
uint32_t distanceFromEnd = loopLength - getLivePos();
// We're gonna cull time-stretching ones first
Expand Down
2 changes: 2 additions & 0 deletions src/deluge/model/clip/audio_clip.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class AudioClip final : public Clip {
void sampleZoneChanged(ModelStackWithTimelineCounter const* modelStack);
int64_t getNumSamplesTilLoop(ModelStackWithTimelineCounter* modelStack);
void setPos(ModelStackWithTimelineCounter* modelStack, int32_t newPos, bool useActualPosForParamManagers);
/// Return true if successfully shifted, as clip cannot be shifted past beginning
bool shiftHorizontally(ModelStackWithTimelineCounter* modelStack, int amount);

int readFromFile(Song* song);
void writeDataToFile(Song* song);
Expand Down
2 changes: 2 additions & 0 deletions src/deluge/model/clip/clip.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ class Clip : public TimelineCounter {
void setSequenceDirectionMode(ModelStackWithTimelineCounter* modelStack, int newSequenceDirection);
bool possiblyCloneForArrangementRecording(ModelStackWithTimelineCounter* modelStack);
virtual void incrementPos(ModelStackWithTimelineCounter* modelStack, int32_t numTicks);
/// Return true if successfully shifted
virtual bool shiftHorizontally(ModelStackWithTimelineCounter* modelStack, int amount) = 0;

// ----- PlayPositionCounter implementation -------
int32_t getLoopLength();
Expand Down
3 changes: 2 additions & 1 deletion src/deluge/model/clip/instrument_clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3181,7 +3181,7 @@ NoteRow* InstrumentClip::getNoteRowFromId(int id) {
}
}

void InstrumentClip::shiftHorizontally(ModelStackWithTimelineCounter* modelStack, int amount) {
bool InstrumentClip::shiftHorizontally(ModelStackWithTimelineCounter* modelStack, int amount) {

if (paramManager.containsAnyParamCollectionsIncludingExpression()) {
paramManager.shiftHorizontally(
Expand All @@ -3199,6 +3199,7 @@ void InstrumentClip::shiftHorizontally(ModelStackWithTimelineCounter* modelStack
expectEvent();
reGetParameterAutomation(modelStack); // Re-gets all NoteRow-level param automation too
}
return true;
}

void InstrumentClip::shiftOnlyOneNoteRowHorizontally(ModelStackWithNoteRow* modelStack, int shiftAmount) {
Expand Down
3 changes: 2 additions & 1 deletion src/deluge/model/clip/instrument_clip.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ class InstrumentClip final : public Clip {
void restoreBackedUpParamManagerMIDI(ModelStackWithModControllable* modelStack);
int getNoteRowId(NoteRow* noteRow, int noteRowIndex);
NoteRow* getNoteRowFromId(int id);
void shiftHorizontally(ModelStackWithTimelineCounter* modelStack, int amount);
/// Return true if successfully shifted. Instrument clips always succeed
bool shiftHorizontally(ModelStackWithTimelineCounter* modelStack, int amount);
bool containsAnyNotes();
ModelStackWithNoteRow* getNoteRowOnScreen(int yDisplay, ModelStackWithTimelineCounter* modelStack);
NoteRow* getNoteRowOnScreen(int yDisplay, Song* song, int* getIndex = NULL);
Expand Down

0 comments on commit 2e1ba62

Please sign in to comment.