Skip to content

Commit

Permalink
Bugfix - Remove song control entirely (SynthstromAudible#1777)
Browse files Browse the repository at this point in the history
Removed song control entirely from midi follow mode
  • Loading branch information
seangoodvibes authored Apr 27, 2024
1 parent 4b8338d commit 3083949
Show file tree
Hide file tree
Showing 8 changed files with 35 additions and 102 deletions.
3 changes: 1 addition & 2 deletions docs/community_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ Here is a list of general improvements that have been made, ordered from newest
but any already sounding notes will be stopped.

- ([#889]) `Master MIDI Follow Mode` whereby after setting a master MIDI follow channel for Synth/MIDI/CV clips, Kit
clips, and for Parameters, all MIDI (notes + cc’s) received will be directed to control the active view (e.g. arranger
view, song view, audio clip view, instrument clip view).
clips, and for Parameters, all MIDI (notes + cc’s) received will be directed to control the active or selected clip).
- For a detailed description of this feature, please refer to the feature
documentation: [MIDI Follow Mode Documentation]
- Comes with a MIDI feedback mode to send updated parameter values on the MIDI follow channel for learned MIDI cc's.
Expand Down
20 changes: 7 additions & 13 deletions docs/features/midi_follow_mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@

## Description:

Master MIDI follow mode whereby after setting a master MIDI follow channel for Synth/MIDI/CV clips, Kit clips, and for Parameters, all MIDI (notes + cc’s) received will be directed to control the active view (e.g. arranger view, song view, audio clip view, instrument clip view).
Master MIDI follow mode whereby after setting a master MIDI follow channel for Synth/MIDI/CV clips, Kit clips, and for Parameters, all MIDI (notes + cc’s) received will be directed to control the active or selected clip. MIDI follow mode does not control song parameters.

- Note: although there are three MIDI follow channel's (A/B/C), all three channels will control the instrument of the active context. The three follow channel's allows you to learn different devices to MIDI follow, should you require device specific channel settings.

- Note: you can configure MIDI follow mode to only control clip parameters

Comes with a MIDI feedback mode to send updated parameter values on the MIDI follow feedback channel for mapped MIDI cc's. Feedback is sent whenever you change context on the deluge and whenever parameter values for the active context are changed.

Comes with an XML file (MIDIFollow.XML) with default CC to Deluge parameter mappings. You can customize this XML file to map CC's differently as required.
Expand All @@ -18,7 +16,6 @@ Comes with an XML file (MIDIFollow.XML) with default CC to Deluge parameter mapp
- Set your follow and feedback channel(s)
- Set your MIDI Controller(s) to the same channel(s)
- Set a root note for your kits
- Set whether you want to control song params
- Confirm that your controller cc's are mapped to the parameters you want (via MIDIFollow.XML)
- Play and control the deluge instruments and parameters with ease!

Expand Down Expand Up @@ -76,17 +73,15 @@ Notes and note associated performance data received (e.g. CC1, MPE CC74) on the
- Note 3: MIDI Follow mode will always send notes to the active clip. This means that if you leave or unselect a clip, you can still send notes to that clip because in the Deluge, that clip is still recognized as the last active clip.

### **CC's:**
CC's received on the master MIDI channel that have been mapped to a parameter will change the value of that parameter in the active context (e.g. song, arranger, audio clip, instrument clip).
CC's received on the master MIDI channel that have been mapped to a parameter will change the value of that parameter in the active context (e.g. audio clip or instrument clip).

The parameters are controlled only in the current context.

- So if you are controlling filter, for example, while in song view it will only control the song’s filter. If you enter a specific synth clip, it will only control that synths filter. If you are in a kit clip it will either control the entire kit or a specific row in that clip (depending on whether you have affect entire enabled or not)
- So if you enter a specific synth clip, it will only control that synths filter. If you are in a kit clip it will either control the entire kit or a specific row in that clip (depending on whether you have affect entire enabled or not)
- If you are in song view or arranger view and holding down a clip (selecting it), it will only control the parameters for that selected clip.
- If you are in song view and haven't selected a clip, it will control the parameters of the active instrument of the last clip selected.
- In other words it checks what context you’re in and controls the parameters of that context.

Note 1: You can control the parameters of a synth or kit clip without entering the clip from arranger or song view. Simply press and hold the clip in arranger or song view to preview the clip (as you would to change the parameters of that clip with the gold encoders) and then send MIDI cc's from your MIDI controller to adjust the parameters.

Note 2: if you have disabled control of song params, then CC's received will control the active clip of the last instrument selected (e.g. synth, kit, audio clip, midi clip).

#### Default MIDI CC Mappings
A default set of MIDI CC # to Deluge Parameter mappings has been created for MIDI Follow Mode. When you launch the Deluge after installing the firmware with MIDI Follow Mode, an XML file will be created to the root folder of the SD card titled "MIDIFollow.XML"

Expand Down Expand Up @@ -122,7 +117,7 @@ For mapped MIDI CC's, a pop up is shown on the display whenever MIDI CC's are re

<img width="170" alt="Screen Shot 2023-12-04 at 2 32 25 AM" src="https://github.com/SynthstromAudible/DelugeFirmware/assets/138174805/f4e8115c-c2af-4cfe-94cf-d2e117201cd5">

Note: if the MIDI CC being received is for a Parameter that cannot be controlled in the current context (e.g. trying to control Attack while in Song View), the pop-up message will say "Can't Control: Parameter Name".
Note: if the MIDI CC being received is for a Parameter that cannot be controlled in the current context (e.g. trying to control Attack while in a Kit with Affect Entire enabled), the pop-up message will say "Can't Control: Parameter Name".

<img width="191" alt="Screen Shot 2023-12-04 at 2 32 03 AM" src="https://github.com/SynthstromAudible/DelugeFirmware/assets/138174805/b2dcefb2-4f90-4b23-804c-3250bfd24862">

Expand All @@ -136,8 +131,7 @@ Note: if the MIDI CC being received is for a Parameter that cannot be controlled
- Enable or Disable MIDI Follow Feedback by setting/unsetting the MIDI Follow Feedback Channel
- Enable or Disable MIDI Follow Feedback for Automated Parameters and set the MIDI Feedback Update Rate
- Enable or Disable MIDI Follow Feedback Filtering of MIDI CC responses received within 1 second of sending feedback
- Enable or Disable MIDI Follow Control of Song Parameter's
2. When MIDI Follow Mode is enabled, a MIDI Follow Channel has been set, and you have mapped your MIDI CC's, your external controller's Notes and MIDI CC's will be automatically directed to control the Notes of the Active Instrument (e.g. Synth, Kit, MIDI, CV) or the Parameters of the Active View (e.g. Song View, Arranger View, Audio Clip View, Instrument Clip View).
2. When MIDI Follow Mode is enabled, a MIDI Follow Channel has been set, and you have mapped your MIDI CC's, your external controller's Notes and MIDI CC's will be automatically directed to control the Notes of the Active Instrument (e.g. Synth, Kit, MIDI, CV) and Parameters of the Active Instrument or Selected Clip.
3. By default, the root note for kit's is C1 for the bottom kit row but this can be configured in the MIDI Follow menu.
4. Pop-up's are shown on the display for mapped MIDI CC's to show the name of the parameter being controlled and value being set for the parameter. This can be disabled in the menu.
5. MIDI feedback is sent for mapped CC's when the active context changes, change presets, or you change the value of a mapped parameter on the deluge (e.g. using mod encoders or select encoder if you're int he menu). MIDI feedback can be disabled in the menu by setting the MIDI feedback channel to NONE.
Expand Down
3 changes: 0 additions & 3 deletions src/deluge/gui/ui/menus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -767,8 +767,6 @@ midi::FollowFeedbackChannelType midiFollowFeedbackChannelMenu{STRING_FOR_CHANNEL
midi::FollowFeedbackAutomation midiFollowFeedbackAutomationMenu{STRING_FOR_FOLLOW_FEEDBACK_AUTOMATION};
ToggleBool midiFollowFeedbackFilterMenu{STRING_FOR_FOLLOW_FEEDBACK_FILTER, STRING_FOR_FOLLOW_FEEDBACK_FILTER,
midiEngine.midiFollowFeedbackFilter};
ToggleBool midiFollowControlSongParamMenu{STRING_FOR_FOLLOW_CONTROL_SONG_PARAM, STRING_FOR_FOLLOW_CONTROL_SONG_PARAM,
midiEngine.midiFollowControlSongParam};

Submenu midiFollowChannelSubmenu{
STRING_FOR_CHANNEL,
Expand Down Expand Up @@ -798,7 +796,6 @@ Submenu midiFollowSubmenu{
&midiFollowKitRootNoteMenu,
&midiFollowFeedbackSubmenu,
&midiFollowDisplayParamMenu,
&midiFollowControlSongParamMenu,
},
};

Expand Down
1 change: 0 additions & 1 deletion src/deluge/io/midi/midi_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ MidiEngine::MidiEngine() {
midiFollowFeedbackChannelType = MIDIFollowChannelType::NONE;
midiFollowFeedbackAutomation = MIDIFollowFeedbackAutomationMode::DISABLED;
midiFollowFeedbackFilter = false;
midiFollowControlSongParam = true;
midiTakeover = MIDITakeoverMode::JUMP;
midiSelectKitRow = false;

Expand Down
1 change: 0 additions & 1 deletion src/deluge/io/midi/midi_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ class MidiEngine {
bool midiFollowDisplayParam;
MIDIFollowFeedbackAutomationMode midiFollowFeedbackAutomation;
bool midiFollowFeedbackFilter;
bool midiFollowControlSongParam;
MIDITakeoverMode midiTakeover;
bool midiSelectKitRow;

Expand Down
99 changes: 26 additions & 73 deletions src/deluge/io/midi/midi_follow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,17 +134,17 @@ Clip* getSelectedClip() {
if (currentSong->lastClipInstanceEnteredStartPos != -1) {
clip = arrangerView.getClipForSelection();
}
break;
case UIType::AUTOMATION_VIEW:
if (automationView.getAutomationSubType() == AutomationSubType::ARRANGER) {
// if you're in arranger automation view, no clip will be selected for param control
break;
}
[[fallthrough]];
default:
// if you're not in sessionView, arrangerView, or performanceView, then you're in a clip
}

if (!clip) {
// if you haven't selected a clip above, return the activeClip of the current instrument
clip = getCurrentClip();
break;
if (clip) {
Output* output = clip->output;
if (output) {
clip = output->activeClip;
}
}
}

return clip;
Expand All @@ -158,18 +158,19 @@ Clip* getSelectedClip(ModelStack* modelStack) {
// when auditioning a clip for that output,
// the active clip for that output should be set to the current clip.
Clip* currentClip = getCurrentClip();
if (currentClip->type == ClipType::INSTRUMENT) {
InstrumentClipMinder::makeCurrentClipActiveOnInstrumentIfPossible(modelStack);
return currentClip->output->activeClip;
if (currentClip && (currentClip->type == ClipType::INSTRUMENT)) {
if (currentClip->output) {
InstrumentClipMinder::makeCurrentClipActiveOnInstrumentIfPossible(modelStack);
return currentClip->output->activeClip;
}
}
return nullptr;
}

/// based on the current context, as determined by clip returned from the getSelectedClip function
/// obtain the modelStackWithParam for that context and return it so it can be used by midi follow
ModelStackWithAutoParam*
MidiFollow::getModelStackWithParam(ModelStackWithThreeMainThings* modelStackWithThreeMainThings,
ModelStackWithTimelineCounter* modelStackWithTimelineCounter, Clip* clip,
MidiFollow::getModelStackWithParam(ModelStackWithTimelineCounter* modelStackWithTimelineCounter, Clip* clip,
int32_t xDisplay, int32_t yDisplay, int32_t ccNumber, bool displayError) {
ModelStackWithAutoParam* modelStackWithParam = nullptr;

Expand All @@ -180,12 +181,6 @@ MidiFollow::getModelStackWithParam(ModelStackWithThreeMainThings* modelStackWith
getModelStackWithParamForClip(modelStackWithTimelineCounter, clip, xDisplay, yDisplay);
}
}
// null clip means you're dealing with the song context
else {
if (modelStackWithThreeMainThings) {
modelStackWithParam = getModelStackWithParamForSong(modelStackWithThreeMainThings, xDisplay, yDisplay);
}
}

if (displayError && (paramToCC[xDisplay][yDisplay] == ccNumber)
&& (!modelStackWithParam || !modelStackWithParam->autoParam)) {
Expand All @@ -195,23 +190,6 @@ MidiFollow::getModelStackWithParam(ModelStackWithThreeMainThings* modelStackWith
return modelStackWithParam;
}

ModelStackWithAutoParam*
MidiFollow::getModelStackWithParamForSong(ModelStackWithThreeMainThings* modelStackWithThreeMainThings,
int32_t xDisplay, int32_t yDisplay) {
ModelStackWithAutoParam* modelStackWithParam = nullptr;
int32_t paramID = unpatchedGlobalParamShortcuts[xDisplay][yDisplay];

if (paramID != kNoParamID) {
// can't control Pitch or Sidechain params in Song view
if ((paramID != params::UNPATCHED_PITCH_ADJUST) && (paramID != params::UNPATCHED_SIDECHAIN_SHAPE)
&& (paramID != params::UNPATCHED_SIDECHAIN_VOLUME)) {
modelStackWithParam = currentSong->getModelStackWithParam(modelStackWithThreeMainThings, paramID);
}
}

return modelStackWithParam;
}

ModelStackWithAutoParam*
MidiFollow::getModelStackWithParamForClip(ModelStackWithTimelineCounter* modelStackWithTimelineCounter, Clip* clip,
int32_t xDisplay, int32_t yDisplay) {
Expand Down Expand Up @@ -444,12 +422,6 @@ void MidiFollow::midiCCReceived(MIDIDevice* fromDevice, uint8_t channel, uint8_t
// clip is allowed to be null here because there may not be an active clip
// e.g. you want to control the song level parameters

// if clip is null and you do not want to control song params
// control the active clip of the last instrument selected
if (!clip && !midiEngine.midiFollowControlSongParam) {
clip = getCurrentClip()->output->activeClip;
}

bool isMIDIClip = false;
bool isCVClip = false;
if (clip) {
Expand All @@ -464,7 +436,8 @@ void MidiFollow::midiCCReceived(MIDIDevice* fromDevice, uint8_t channel, uint8_t
// don't offer to handleReceivedCC if it's a MIDI or CV Clip
// this is because this function is used to control internal deluge parameters only (patched, unpatched)
// midi/cv clip cc parameters are handled below in the offerReceivedCCToMelodicInstrument function
if (!isMIDIClip && !isCVClip && (match == MIDIMatchType::MPE_MASTER || match == MIDIMatchType::CHANNEL)) {
if (clip && !isMIDIClip && !isCVClip
&& (match == MIDIMatchType::MPE_MASTER || match == MIDIMatchType::CHANNEL)) {
// if midi follow feedback and feedback filter is enabled,
// check time elapsed since last midi cc was sent with midi feedback for this same ccNumber
// if it was greater or equal than 1 second ago, allow received midi cc to go through
Expand Down Expand Up @@ -505,30 +478,23 @@ void MidiFollow::midiCCReceived(MIDIDevice* fromDevice, uint8_t channel, uint8_t
void MidiFollow::handleReceivedCC(ModelStack* modelStack, Clip* clip, int32_t ccNumber, int32_t value) {
char modelStackMemory[MODEL_STACK_MAX_SIZE];

ModelStackWithThreeMainThings* modelStackWithThreeMainThings = nullptr;
ModelStackWithTimelineCounter* modelStackWithTimelineCounter = nullptr;

// setup model stack for the active context
// if clip is null, it means you want to control the song level parameters
if (!clip) {
if (currentSong->affectEntire) {
modelStackWithThreeMainThings = currentSong->setupModelStackWithSongAsTimelineCounter(modelStackMemory);
}
}
else if (modelStack) {
if (modelStack) {
modelStackWithTimelineCounter = modelStack->addTimelineCounter(clip);
}

// check that model stack is valid
if (modelStackWithThreeMainThings || modelStackWithTimelineCounter) {
if (modelStackWithTimelineCounter) {
// loop through the grid to see if any parameters have been learned to the ccNumber received
for (int32_t xDisplay = 0; xDisplay < kDisplayWidth; xDisplay++) {
for (int32_t yDisplay = 0; yDisplay < kDisplayHeight; yDisplay++) {
if (paramToCC[xDisplay][yDisplay] == ccNumber) {
// obtain the model stack for the parameter the ccNumber received is learned to
ModelStackWithAutoParam* modelStackWithParam =
getModelStackWithParam(modelStackWithThreeMainThings, modelStackWithTimelineCounter, clip,
xDisplay, yDisplay, ccNumber, midiEngine.midiFollowDisplayParam);
getModelStackWithParam(modelStackWithTimelineCounter, clip, xDisplay, yDisplay, ccNumber,
midiEngine.midiFollowDisplayParam);
// check if model stack is valid
if (modelStackWithParam && modelStackWithParam->autoParam) {
// get current value
Expand Down Expand Up @@ -608,41 +574,28 @@ void MidiFollow::handleReceivedCC(ModelStack* modelStack, Clip* clip, int32_t cc
void MidiFollow::sendCCWithoutModelStackForMidiFollowFeedback(int32_t channel, bool isAutomation) {
char modelStackMemory[MODEL_STACK_MAX_SIZE];

ModelStackWithThreeMainThings* modelStackWithThreeMainThings = nullptr;
ModelStackWithTimelineCounter* modelStackWithTimelineCounter = nullptr;

// obtain clip for active context
Clip* clip = getSelectedClip();

// if clip is null and you do not want to control song params
// send feedback for the active clip
if (!clip && !midiEngine.midiFollowControlSongParam) {
clip = getCurrentClip()->output->activeClip;
}

// setup model stack for the active context
if (!clip) {
if (currentSong->affectEntire) {
modelStackWithThreeMainThings = currentSong->setupModelStackWithSongAsTimelineCounter(modelStackMemory);
}
}
else {
if (clip) {
ModelStack* modelStack = setupModelStackWithSong(modelStackMemory, currentSong);
if (modelStack) {
modelStackWithTimelineCounter = modelStack->addTimelineCounter(clip);
}
}

// check that model stack is valid
if (modelStackWithThreeMainThings || modelStackWithTimelineCounter) {
if (modelStackWithTimelineCounter) {
// loop through the grid to see if any parameters have been learned
for (int32_t xDisplay = 0; xDisplay < kDisplayWidth; xDisplay++) {
for (int32_t yDisplay = 0; yDisplay < kDisplayHeight; yDisplay++) {
if (paramToCC[xDisplay][yDisplay] != MIDI_CC_NONE) {
// obtain the model stack for the parameter that has been learned
ModelStackWithAutoParam* modelStackWithParam =
getModelStackWithParam(modelStackWithThreeMainThings, modelStackWithTimelineCounter, clip,
xDisplay, yDisplay, MIDI_CC_NONE, false);
ModelStackWithAutoParam* modelStackWithParam = getModelStackWithParam(
modelStackWithTimelineCounter, clip, xDisplay, yDisplay, MIDI_CC_NONE, false);
// check that model stack is valid
if (modelStackWithParam && modelStackWithParam->autoParam) {
if (!isAutomation || (isAutomation && modelStackWithParam->autoParam->isAutomated())) {
Expand Down
3 changes: 1 addition & 2 deletions src/deluge/io/midi/midi_follow.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ class MidiFollow final {
MidiFollow();
void readDefaultsFromFile(StorageManager& bdsm);

ModelStackWithAutoParam* getModelStackWithParam(ModelStackWithThreeMainThings* modelStackWithThreeMainThings,
ModelStackWithTimelineCounter* modelStackWithTimelineCounter,
ModelStackWithAutoParam* getModelStackWithParam(ModelStackWithTimelineCounter* modelStackWithTimelineCounter,
Clip* clip, int32_t xDisplay, int32_t yDisplay, int32_t ccNumber,
bool displayError = true);
void noteMessageReceived(MIDIDevice* fromDevice, bool on, int32_t channel, int32_t note, int32_t velocity,
Expand Down
Loading

0 comments on commit 3083949

Please sign in to comment.