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

New 'Launch Exclusively' Option for Clip Sections #3213

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ also affect normal sequenced notes while arpeggiator is Off.
- A white playhead is now rendered in Song Grid and Performance Views that let's you know when a clip or section launch event is scheduled to occur. The playhead only renders the last 16 notes before a launch event.
- Note: this playhead can be turned off in the Community Features submenu titled: `Enable Launch Event Playhead (PLAY)`
- The display now shows the number of Bars (or Quarter Notes for the last bar) remaining until a clip or section launch event in all Song views (Grid, Row, Performance).
- A new option, 'Launch Exclusively', isolates a clip section from all other launch activity. This option is found to the left of option 'Launch non-exclusively' when selecting the section's number of repetitions. As a complement to non-exclusive sections that arm and turn off when another section is launched, exclusive sections remain independant.

#### <ins>Audio Clips</ins>

Expand Down
5 changes: 5 additions & 0 deletions docs/community_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,9 @@ and a comb filter. Controls are the normal rate/depth/feedback/offset.
set.
- ([#2788]) Added LPF to Mutable Instruments Model. 50 (default) corresponds to 20khz and 0 corresponds to 0hz.

#### 4.2.10 Launch Exclusively
- ([#3213]) A new option, 'Launch Exclusively', isolates a clip section from all other launch activity. This option is found to the left of option 'Launch non-exclusively' when selecting the section's number of repetitions. As a complement to non-exclusive sections that arm and turn off when another section is launched, exclusive sections remain independant and continue playing.

### 4.3 - Instrument Clip View - General Features

These features were added to the Instrument Clip View and affect Synth, Kit and MIDI instrument clip types.
Expand Down Expand Up @@ -1567,6 +1570,8 @@ different firmware

[#3079]: https://github.com/SynthstromAudible/DelugeFirmware/pull/3079

[#3213]: https://github.com/SynthstromAudible/DelugeFirmware/pull/3213

[#3226]: https://github.com/SynthstromAudible/DelugeFirmware/pull/3226

[#3279]: https://github.com/SynthstromAudible/DelugeFirmware/pull/3279
Expand Down
11 changes: 7 additions & 4 deletions src/deluge/gui/views/session_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1226,7 +1226,10 @@ void SessionView::drawSectionRepeatNumber() {
char const* outputText;
if (display->haveOLED()) {
char buffer[21];
if (number == -1) {
if (number == -2) {
outputText = "Launch \nexclusively"; // Need line break to match format of next label.
}
else if (number == -1) {
outputText = "Launch non-\nexclusively"; // Need line break cos line splitter doesn't deal with hyphens.
}
else {
Expand Down Expand Up @@ -1273,8 +1276,8 @@ void SessionView::commandChangeSectionRepeats(int8_t offset) {
if (*numRepetitions > 9999) {
*numRepetitions = 9999;
}
else if (*numRepetitions < -1) {
*numRepetitions = -1;
else if (*numRepetitions < -2) {
*numRepetitions = -2;
}
drawSectionRepeatNumber();
}
Expand Down Expand Up @@ -3766,7 +3769,7 @@ void SessionView::gridClonePad(uint32_t sourceX, uint32_t sourceY, uint32_t targ

void SessionView::gridStartSection(uint32_t section, bool instant) {
if (instant) {
currentSong->turnSoloingIntoJustPlaying(currentSong->sections[section].numRepetitions != -1);
currentSong->turnSoloingIntoJustPlaying(currentSong->sections[section].numRepetitions > -1);

for (int32_t idxClip = 0; idxClip < currentSong->sessionClips.getNumElements(); ++idxClip) {
Clip* clip = currentSong->sessionClips.getClipAtIndex(idxClip);
Expand Down
5 changes: 3 additions & 2 deletions src/deluge/model/song/song.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1854,7 +1854,7 @@ Error Song::readFromFile(Deserializer& reader) {

else if (!strcmp(tagName, "numRepeats")) {
numRepeats = reader.readTagOrAttributeValueInt();
if (numRepeats < -1 || numRepeats > 9999) {
if (numRepeats < -2 || numRepeats > 9999) {
numRepeats = 0;
}
}
Expand Down Expand Up @@ -3155,7 +3155,8 @@ void Song::turnSoloingIntoJustPlaying(bool getRidOfArmingToo) {
// Just get rid of arming
for (int32_t l = 0; l < sessionClips.getNumElements(); l++) {
Clip* loopable = sessionClips.getClipAtIndex(l);
if (loopable->launchStyle == LaunchStyle::DEFAULT) {
if (loopable->launchStyle == LaunchStyle::DEFAULT
&& currentSong->sections[loopable->section].numRepetitions != -2) {
loopable->armState = ArmState::OFF;
}
}
Expand Down
98 changes: 64 additions & 34 deletions src/deluge/playback/mode/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ const Colour defaultClipSectionColours[] = {RGB::fromHue(102), // bright light b

Session::Session() {
cancelAllLaunchScheduling();
lastSectionArmed = 255;
lastSectionArmed = REALLY_OUT_OF_RANGE;
}

void Session::armAllClipsToStop(int32_t afterNumRepeats) {
Expand Down Expand Up @@ -137,18 +137,20 @@ void Session::armAllClipsToStop(int32_t afterNumRepeats) {
}

void Session::armNextSection(int32_t oldSection, int32_t numRepetitions) {
if (numRepetitions == -1) {
if (numRepetitions <= -1) {
numRepetitions = currentSong->sections[oldSection].numRepetitions;
}
if (currentSong->sessionLayout == SessionLayoutType::SessionLayoutTypeRows) {
if (currentSong->sessionClips.getClipAtIndex(0)->section != oldSection) {

int32_t newSection = SECTION_OUT_OF_RANGE;
for (int32_t c = 1; c < currentSong->sessionClips.getNumElements(); c++) { // NOTE: starts at 1, not 0
Clip* clip = currentSong->sessionClips.getClipAtIndex(c);

if (clip->section == oldSection) {
int32_t newSection =
currentSong->sessionClips.getClipAtIndex(c - 1)->section; // Grab section from next Clip down
int32_t tempSection = currentSong->sessionClips.getClipAtIndex(c - 1)->section;
if (currentSong->sections[tempSection].numRepetitions != LAUNCH_EXCLUSIVE) {
newSection = tempSection;
}
if (clip->section == oldSection && newSection != SECTION_OUT_OF_RANGE) {
userWantsToArmClipsToStartOrSolo(newSection, nullptr, true, false, false, numRepetitions, false);
lastSectionArmed = newSection;
return;
Expand All @@ -159,15 +161,24 @@ void Session::armNextSection(int32_t oldSection, int32_t numRepetitions) {
// grid mode - just go to the next section, no need to worry about what order they're in
else if (currentSong->sessionLayout == SessionLayoutType::SessionLayoutTypeGrid) {
if (oldSection < kMaxNumSections) {
userWantsToArmClipsToStartOrSolo(oldSection + 1, nullptr, true, false, false, numRepetitions, false);
lastSectionArmed = oldSection + 1;
int32_t newSection = SECTION_OUT_OF_RANGE;
for (int32_t c = oldSection + 1; c <= kMaxNumSections; ++c) {
if (currentSong->sections[c].numRepetitions != LAUNCH_EXCLUSIVE) {
newSection = c;
break;
}
}
if (newSection != SECTION_OUT_OF_RANGE) {
userWantsToArmClipsToStartOrSolo(newSection, nullptr, true, false, false, numRepetitions, false);
lastSectionArmed = newSection;
}
return;
}
}

// If we're here, that was the last section
armAllClipsToStop(numRepetitions);
lastSectionArmed = 254;
lastSectionArmed = SECTION_OUT_OF_RANGE;
}

// Returns whether it began
Expand Down Expand Up @@ -520,7 +531,7 @@ void Session::doLaunch(bool isFillLaunch) {
// separate undoability
actionLogger.closeAction(ActionType::RECORD);

bool sectionWasJustLaunched = (lastSectionArmed < 254);
bool sectionWasJustLaunched = (lastSectionArmed < SECTION_OUT_OF_RANGE);
bool anyLinearRecordingAfter = false;

// Now action the launching of Clips
Expand Down Expand Up @@ -634,7 +645,8 @@ void Session::doLaunch(bool isFillLaunch) {

// If we found a playing Clip outside of the armed section, or vice versa, then we can't say we legitimately
// just launched a section
if (clip->launchStyle != LaunchStyle::FILL && clipActiveAfter != (clip->section == lastSectionArmed)) {
if (clip->launchStyle != LaunchStyle::FILL && clipActiveAfter != (clip->section == lastSectionArmed)
&& currentSong->sections[clip->section].numRepetitions != LAUNCH_EXCLUSIVE) {
sectionWasJustLaunched = false;
}
}
Expand All @@ -650,9 +662,9 @@ void Session::doLaunch(bool isFillLaunch) {
// Otherwise...
else {

bool sectionManuallyStopped = (lastSectionArmed == 254);
bool sectionManuallyStopped = (lastSectionArmed == SECTION_OUT_OF_RANGE);

lastSectionArmed = 255;
lastSectionArmed = REALLY_OUT_OF_RANGE;

// If no Clips active anymore...
if (!anyClipsStillActiveAfter) {
Expand All @@ -674,7 +686,7 @@ void Session::doLaunch(bool isFillLaunch) {

int32_t sectionToArm;
// And re-activate the first section
if (currentSong->sectionToReturnToAfterSongEnd < 254) {
if (currentSong->sectionToReturnToAfterSongEnd < SECTION_OUT_OF_RANGE) {
sectionToArm = currentSong->sectionToReturnToAfterSongEnd;
}
else {
Expand Down Expand Up @@ -976,7 +988,9 @@ void Session::toggleClipStatus(Clip* clip, int32_t* clipIndex, bool doInstant, i
return;
}

lastSectionArmed = 255;
if (currentSong->sections[clip->section].numRepetitions != LAUNCH_EXCLUSIVE) {
lastSectionArmed = REALLY_OUT_OF_RANGE;
}

// If Clip armed, cancel arming - but not if it's an "instant" toggle
if (clip->launchStyle == LaunchStyle::FILL && clip->armState != ArmState::OFF && !clip->isActiveOnOutput()) {
Expand Down Expand Up @@ -1138,7 +1152,7 @@ void Session::armClipToStopAction(Clip* clip) {
}

void Session::soloClipAction(Clip* clip, int32_t buttonPressLatency) {
lastSectionArmed = 255;
lastSectionArmed = REALLY_OUT_OF_RANGE;

bool anyClipsDeleted = false;

Expand Down Expand Up @@ -1188,7 +1202,7 @@ void Session::soloClipAction(Clip* clip, int32_t buttonPressLatency) {
void Session::armSection(uint8_t section, int32_t buttonPressLatency) {

// Get rid of soloing. And if we're not a "share" section, get rid of arming too
currentSong->turnSoloingIntoJustPlaying(currentSong->sections[section].numRepetitions != -1);
currentSong->turnSoloingIntoJustPlaying(currentSong->sections[section].numRepetitions >= 0);

// If every Clip in this section is already playing, and no other Clips are (unless we're a "share" section), then
// there's no need to launch the section because it's already playing. So, make sure this isn't the case before we
Expand All @@ -1202,7 +1216,7 @@ void Session::armSection(uint8_t section, int32_t buttonPressLatency) {
}

// If a Clip in another section is playing and we're not a "share" section...
if (currentSong->sections[section].numRepetitions != -1 && clip->section != section
if (currentSong->sections[section].numRepetitions > -1 && clip->section != section
&& ((clip->armState != ArmState::OFF) != clip->activeIfNoSolo)) {
goto yupThatsFine;
}
Expand Down Expand Up @@ -1235,7 +1249,10 @@ void Session::armSection(uint8_t section, int32_t buttonPressLatency) {
userWantsToArmClipsToStartOrSolo(
section, nullptr, stopAllOtherClips, false,
false); // Don't allow "late start". It's too fiddly to implement, and rarely even useful for sections
lastSectionArmed = section;

if (currentSong->sections[section].numRepetitions != LAUNCH_EXCLUSIVE) {
lastSectionArmed = section;
}
}

armingChanged();
Expand All @@ -1255,7 +1272,8 @@ void Session::armSectionWhenNeitherClockActive(ModelStack* modelStack, int32_t s
modelStack->song->assertActiveness(modelStackWithTimelineCounter);
}

if (stopAllOtherClips && clip->section != section && clip->activeIfNoSolo) {
if (stopAllOtherClips && clip->section != section && clip->activeIfNoSolo
&& currentSong->sections[clip->section].numRepetitions != LAUNCH_EXCLUSIVE) {
// thisClip->expectNoFurtherTicks(currentSong); // No, don't need this, cos it's not playing!
clip->activeIfNoSolo = false;
}
Expand Down Expand Up @@ -1689,8 +1707,15 @@ void Session::armClipsToStartOrSoloWithQuantization(uint32_t pos, uint32_t quant
if (stopAllOtherClips) {

weWantThisClipInactive:
// Clip is launched exclusively
if (currentSong->sections[thisClip->section].numRepetitions == LAUNCH_EXCLUSIVE) {
if (thisClip->activeIfNoSolo) {
thisClip->armState = ArmState::OFF;
}
}

// If it's active, arm it to stop
if (thisClip->activeIfNoSolo) {
else if (thisClip->activeIfNoSolo) {
thisClip->armState = ArmState::ON_NORMAL;
}

Expand Down Expand Up @@ -1941,8 +1966,8 @@ void Session::armClipLowLevel(Clip* clipToArm, ArmState armState, bool mustUnarm
int32_t Session::userWantsToArmNextSection(int32_t numRepetitions) {

int32_t currentSection = getCurrentSection();
if (currentSection < 254) {
if (numRepetitions == -1) {
if (currentSection < SECTION_OUT_OF_RANGE) {
if (numRepetitions <= -1) {
numRepetitions = currentSong->sections[currentSection].numRepetitions;
}

Expand All @@ -1960,23 +1985,28 @@ int32_t Session::userWantsToArmNextSection(int32_t numRepetitions) {
int32_t Session::getCurrentSection() {

if (currentSong->getAnyClipsSoloing()) {
return 255;
return REALLY_OUT_OF_RANGE;
}

int32_t section = 255;
int32_t section = REALLY_OUT_OF_RANGE;

bool anyUnlaunchedLoopablesInSection[kMaxNumSections];
memset(anyUnlaunchedLoopablesInSection, 0, sizeof(anyUnlaunchedLoopablesInSection));

for (int32_t l = 0; l < currentSong->sessionClips.getNumElements(); l++) {
Clip* clip = currentSong->sessionClips.getClipAtIndex(l);

// Launch exclusively section
if (currentSong->sections[clip->section].numRepetitions == LAUNCH_EXCLUSIVE) {
continue;
}

if (clip->activeIfNoSolo) {
if (section == 255) {
if (section == REALLY_OUT_OF_RANGE) {
section = clip->section;
}
else if (section != clip->section) {
return 254;
return SECTION_OUT_OF_RANGE;
}
}
else {
Expand All @@ -1988,7 +2018,7 @@ int32_t Session::getCurrentSection() {
}

if (anyUnlaunchedLoopablesInSection[section]) {
return 255;
return REALLY_OUT_OF_RANGE;
}
return section;
}
Expand Down Expand Up @@ -2031,12 +2061,12 @@ void Session::setupPlayback() {

currentSong->setParamsInAutomationMode(playbackHandler.recording == RecordingMode::ARRANGEMENT);

lastSectionArmed = 255;
lastSectionArmed = REALLY_OUT_OF_RANGE;
}

// Returns whether to do an instant song swap
bool Session::endPlayback() {
lastSectionArmed = 255;
lastSectionArmed = REALLY_OUT_OF_RANGE;

bool anyClipsRemoved = currentSong->deletePendingOverdubs();

Expand Down Expand Up @@ -2260,7 +2290,7 @@ bool Session::considerLaunchEvent(int32_t numTicksBeingIncremented) {
// If we're doing a song swap...
if (preLoadedSong) {
cancelAllLaunchScheduling();
lastSectionArmed = 255;
lastSectionArmed = REALLY_OUT_OF_RANGE;
playbackHandler.doSongSwap();
swappedSong = true;

Expand Down Expand Up @@ -2323,11 +2353,11 @@ bool Session::considerLaunchEvent(int32_t numTicksBeingIncremented) {
if (playbackHandler.lastSwungTickActioned == 0 || enforceSettingUpArming) {
int32_t currentSection = userWantsToArmNextSection();

if (currentSection < 254 && currentSong->areAllClipsInSectionPlaying(currentSection)) {
if (currentSection < SECTION_OUT_OF_RANGE && currentSong->areAllClipsInSectionPlaying(currentSection)) {
currentSong->sectionToReturnToAfterSongEnd = currentSection;
}
else {
currentSong->sectionToReturnToAfterSongEnd = 255;
currentSong->sectionToReturnToAfterSongEnd = REALLY_OUT_OF_RANGE;
}
}

Expand Down Expand Up @@ -2584,7 +2614,7 @@ void Session::stopOutputRecordingAtLoopEnd() {
// If no launch-event currently, plan one
if (!launchEventAtSwungTickCount) {
armAllClipsToStop(1);
lastSectionArmed = 254;
lastSectionArmed = SECTION_OUT_OF_RANGE;
armingChanged();
}

Expand Down
7 changes: 7 additions & 0 deletions src/deluge/playback/mode/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ class ModelStackWithTimelineCounter;
class ModelStack;
enum class LaunchStatus;

#define SECTION_OUT_OF_RANGE 254
#define REALLY_OUT_OF_RANGE 255
#define LAUNCH_EXCLUSIVE -2
#define LAUNCH_NON_EXCLUSIVE -1
#define LAUNCH_REPEAT_INFINITELY 0
#define LAUNCH_REPEAT_ONCE 1

class Session final : public PlaybackMode {
public:
Session();
Expand Down
Loading