Skip to content

Commit

Permalink
Midi Takeover Mode (#170)
Browse files Browse the repository at this point in the history
* Midi Pickup Mode

Midi Pickup Mode enables the Deluge to ignore Midi CC Values from Controllers that are out of Sync with the Deluge.

For example, if an External Midi Controller is sending a Midi CC Value of 0 to the Deluge for a Parameter on the Deluge that is currently set to an equivalent Midi CC Value of 127, then the devices are out of sync.

The Deluge will ignore Midi CC Values from the Midi Controller until the Midi Controller has been "reset" / matches the current Parameter Value on the Deluge.

It does this by comparing the incoming Midi CC Value with the Deluge's internal Knob Position (which is derived from the Current Parameter Value).

The Deluge Internal Knob Position can be converted to a Midi CC Value and Vice Versa by subtracting 64 from the Midi CC Value or adding 64 to the Deluge's Knob Position.

If the difference between the Deluge's Internal Knob Position and the Midi CC Value is greater than +/- 1, then the Midi CC Value is ignored the Parameter's Value will not be changed.

The feature can be disabled using the Community Features menu in Settings. on the Deluge.

* Update to Midi Takeover

Updated the code to:
- Move Midi Takeover Mode settings from Community Menu to Midi Menu
- Add Value Scaling mode
- Save Midi Takeover Settings to Midi XML file
- Load Midi Takeover Settings from Midi XML file

* Resolved runtime_feature_settings.h file conflict

Replaced file with latest version from community

* Corrections made based on feedback

Per m-m-adams feedback, I have made the following changes:

1) moved takeover mode description to community features.md

2) replaced * 1000000 multiplication with << 20

3) replaced / 1000000 division with >> 20

* Fixed formatting issues

Corrected formatting issues identified by clang-format style check

* Trying again to fix formatting!

Hopefully this works!

* Fixed clang format issues

Ok now I think I've got it! Let's try this :)! I ran the clang format script this time.

---------

Co-authored-by: Jamie Fenton <jamie@fentonia.com>
  • Loading branch information
seangoodvibes and jamiefaye committed Jul 14, 2023
1 parent 7efd754 commit 1124f4a
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 8 deletions.
8 changes: 7 additions & 1 deletion .cproject
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,9 @@
<listOptionValue builtIn="false" value="&quot;${ProjDirPath}/src&quot;"/>
<listOptionValue builtIn="false" value="&quot;${ProjDirPath}/src/fatfs&quot;"/>
</option>
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="true" id="com.renesas.cdt.managedbuild.gcc.rz.option.compiler.cpp.otherOptimization.1843283088" name="Other optimization flags" superClass="com.renesas.cdt.managedbuild.gcc.rz.option.compiler.cpp.otherOptimization" useByScannerDiscovery="true" valueType="stringList"/>
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="com.renesas.cdt.managedbuild.gcc.rz.option.compiler.cpp.otherOptimization.1843283088" name="Other optimization flags" superClass="com.renesas.cdt.managedbuild.gcc.rz.option.compiler.cpp.otherOptimization" useByScannerDiscovery="true" valueType="stringList">
<listOptionValue builtIn="false" value="-std=gnu++1z"/>
</option>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.noexceptions.425527962" name="Do not use exceptions (-fno-exceptions)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.noexceptions" useByScannerDiscovery="true" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.nortti.1655856323" name="Do not use RTTI (-fno-rtti)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.nortti" useByScannerDiscovery="true" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.nousecxaatexit.2037815225" name="Do not use _cxa_atexit() (-fno-use-cxa-atexit)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.nousecxaatexit" useByScannerDiscovery="true" value="true" valueType="boolean"/>
Expand Down Expand Up @@ -2027,6 +2029,8 @@
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
<storageModule moduleId="refreshScope" versionNumber="2">
<configuration configurationName="e2-build-debug-oled"/>
<configuration configurationName="e2-build-release-7seg"/>
<configuration configurationName="Release_RZ_A1L">
<resource resourceType="PROJECT" workspacePath="/Deluge"/>
</configuration>
Expand All @@ -2043,9 +2047,11 @@
<configuration configurationName="build-release-7seg">
<resource resourceType="PROJECT" workspacePath="/Deluge"/>
</configuration>
<configuration configurationName="e2-build-release-oled"/>
<configuration configurationName="debug-oled">
<resource resourceType="PROJECT" workspacePath="/Deluge"/>
</configuration>
<configuration configurationName="e2-build-debug-7seg"/>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
<storageModule moduleId="scannerConfiguration">
Expand Down
16 changes: 16 additions & 0 deletions CommunityFeatures.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@ Synchronization modes accessible through the "LFO SYNC" shortcut.
### 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.

### Takeover Mode

- ([#170]) The Takeover menu consists of three modes that can be selected from:

1) Jump: This is the default mode for the Deluge. As soon as a Midi Knob/Fader position is changed,
the Deluge's internal Knob position/Parameter value jumps to the position of the Midi Knob/Fader.

2) Pickup: The deluge will ignore changes to its internal Knob position/Parameter value until the
Midi Knob/Fader's position is equal to the Deluge Knob position. After which the Midi Knob/Fader
will move in sync with the Deluge.
3) Scale: The deluge will increase/decrease its internal Knob position/Parameter value relative
to the change of the Midi Knob/Fader position and the amount of "runway" remaining on the Midi
controller. Once the Midi controller reaches its maximum or minimum position, the Midi Knob/Fader
will move in sync with the Deluge. The Deluge will value will always decrease/increase in the
same direction as the Midi controller.

<h1 id="runtime-features">Runtime settings aka Community Features Menu</h1>

Expand Down
7 changes: 6 additions & 1 deletion src/definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,6 @@ typedef enum SyncLevel_ {
#define AUTOPILOT_TEST_ENABLED 0
#define LAUNCH_CLIP_TEST_ENABLED 0

#define NUM_GLOBAL_MIDI_COMMANDS 8
#define GLOBAL_MIDI_COMMAND_PLAYBACK_RESTART 0
#define GLOBAL_MIDI_COMMAND_PLAY 1
#define GLOBAL_MIDI_COMMAND_RECORD 2
Expand All @@ -593,6 +592,12 @@ typedef enum SyncLevel_ {
#define GLOBAL_MIDI_COMMAND_LOOP_CONTINUOUS_LAYERING 5
#define GLOBAL_MIDI_COMMAND_UNDO 6
#define GLOBAL_MIDI_COMMAND_REDO 7
#define NUM_GLOBAL_MIDI_COMMANDS 8

#define MIDI_TAKEOVER_JUMP 0
#define MIDI_TAKEOVER_PICKUP 1
#define MIDI_TAKEOVER_SCALE 2
#define NUM_MIDI_TAKEOVER_MODES 3

#define NUM_CLUSTERS_LOADED_AHEAD 2

Expand Down
34 changes: 34 additions & 0 deletions src/deluge/gui/menu_item/midi/takeover.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2014-2023 Synthstrom Audible Limited
*
* This file is part of The Synthstrom Audible Deluge Firmware.
*
* The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "gui/menu_item/selection.h"
#include "io/midi/midi_engine.h"
#include "gui/ui/sound_editor.h"

namespace menu_item::midi {
class Takeover final : public Selection {
public:
using Selection::Selection;
void readCurrentValue() { soundEditor.currentValue = midiEngine.midiTakeover; }
void writeCurrentValue() { midiEngine.midiTakeover = soundEditor.currentValue; }
char const** getOptions() {
static char const* options[] = {"Jump", "Pickup", "Scale", NULL};
return options;
}
int getNumOptions() { return NUM_MIDI_TAKEOVER_MODES; }
};
} // namespace menu_item::midi
11 changes: 8 additions & 3 deletions src/deluge/gui/ui/menus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
#include "gui/menu_item/midi/preset.h"
#include "gui/menu_item/midi/sub.h"
#include "gui/menu_item/midi/thru.h"
#include "gui/menu_item/midi/takeover.h"
#include "gui/menu_item/modulator/destination.h"
#include "gui/menu_item/modulator/transpose.h"
#include "gui/menu_item/mod_fx/depth.h"
Expand Down Expand Up @@ -571,6 +572,9 @@ runtime_feature::Settings runtimeFeatureSettingsMenu{HAVE_OLED ? "Community fts.
// MIDI thru
midi::Thru midiThruMenu{HAVE_OLED ? "MIDI-thru" : "THRU"};

// MIDI Takeover
midi::Takeover midiTakeoverMenu{HAVE_OLED ? "TAKEOVER" : "TOVR"};

// MIDI commands submenu
midi::Command playbackRestartMidiCommand{"Restart", GLOBAL_MIDI_COMMAND_PLAYBACK_RESTART};
midi::Command playMidiCommand{"PLAY", GLOBAL_MIDI_COMMAND_PLAY};
Expand Down Expand Up @@ -605,11 +609,12 @@ midi::ClockOutStatus midiClockOutStatusMenu{HAVE_OLED ? "Output" : "OUT"};
midi::ClockInStatus midiClockInStatusMenu{HAVE_OLED ? "Input" : "IN"};
tempo::MagnitudeMatching tempoMagnitudeMatchingMenu{HAVE_OLED ? "Tempo magnitude matching" : "MAGN"};
MenuItem* midiClockMenuItems[] = {&midiClockInStatusMenu, &midiClockOutStatusMenu, &tempoMagnitudeMatchingMenu, NULL};
Submenu midiClockMenu{"CLOCK", midiClockMenuItems};

//MIDI menu
Submenu midiClockMenu{"CLOCK", midiClockMenuItems};
MenuItem* midiMenuItems[] = {&midiClockMenu, &midiThruMenu, &midiCommandsMenu, &midiInputDifferentiationMenu,
&midi::devicesMenu, NULL};
MenuItem* midiMenuItems[] = {
&midiClockMenu, &midiThruMenu, &midiTakeoverMenu, &midiCommandsMenu, &midiInputDifferentiationMenu,
&midi::devicesMenu, NULL};
Submenu midiMenu{"MIDI", midiMenuItems};

// Trigger clock in menu
Expand Down
1 change: 1 addition & 0 deletions src/deluge/io/midi/midi_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ MidiEngine::MidiEngine() {
lastStatusByteSent = 0;
currentlyReceivingSysExSerial = false;
midiThru = false;
midiTakeover = 0;

g_usb_peri_connected = 0; // Needs initializing with A2 driver

Expand Down
1 change: 1 addition & 0 deletions src/deluge/io/midi/midi_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class MidiEngine {
[NUM_GLOBAL_MIDI_COMMANDS]; // If bit "16" (actually bit 4) is 1, this is a program change. (Wait, still?)

bool midiThru;
uint8_t midiTakeover;

private:
uint8_t serialMidiInput[3];
Expand Down
128 changes: 125 additions & 3 deletions src/deluge/model/mod_controllable/mod_controllable_audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "model/song/song.h"
#include "model/model_stack.h"
#include "io/midi/midi_device.h"
#include "io/midi/midi_engine.h"
#include "model/clip/instrument_clip.h"
#include "model/note/note_row.h"
#include "modulation/params/param_set.h"
Expand Down Expand Up @@ -1412,15 +1413,136 @@ bool ModControllableAudio::offerReceivedCCToLearnedParams(MIDIDevice* fromDevice
}
}
else {
newKnobPos = 64;
if (value < 127) {
newKnobPos = (int)value - 64;
if (midiEngine.midiTakeover == MIDI_TAKEOVER_JUMP) { //Midi Takeover Mode = Jump
newKnobPos = 64;
if (value < 127) {
newKnobPos = (int)value - 64;
}
knob->previousPositionSaved = false;
}
else { //Midi Takeover Mode = Pickup or Value Scaling
/*
Step #1: Convert Midi Controller's CC Value to Deluge Knob Position Value
- Midi CC Values for non endless encoders typically go from 0 to 127
- Deluge Knob Position Value goes from -64 to 64
To convert Midi CC Value to Deluge Knob Position Value, subtract 64 from the Midi CC Value
So a Midi CC Value of 0 is equal to a Deluge Knob Position Value of -64 (0 less 64).
Similarly a Midi CC Value of 127 is equal to a Deluge Knob Position Value of +63 (127 less 64)
*/

int midiKnobPos = value - 64;

//Save previous knob position for first time
//The first time a midi knob is turned in a session, no previous midi knob position information exists, so to start, it will be equal to the current midiKnobPos
//This code is also executed when takeover mode is changed to Jump and back to Pickup/Scale because in Jump mode no previousPosition information gets saved

if (!knob->previousPositionSaved) {
knob->previousPosition = midiKnobPos;

knob->previousPositionSaved = true;
}

//adjust previous knob position saved

//Here we check to see if the midi knob position previously saved is greater or less than the current midi knob position +/- 1
//If it's by more than 1, the previous position is adjusted.
//This could happen for example if you changed banks and the previous position is no longer valid.
//By resetting the previous position we ensure that the there isn't unwanted jumpyness in the calculation of the midi knob position change amount
if (knob->previousPosition > (midiKnobPos + 1) || knob->previousPosition < (midiKnobPos - 1)) {

knob->previousPosition = midiKnobPos;
}

//Here we obtain the current Parameter Value on the Deluge
int32_t previousValue =
modelStackWithParam->autoParam->getValuePossiblyAtPos(modPos, modelStackWithParam);

//Here we convert the current Parameter Value on the Deluge to a Knob Position Value
int knobPos = modelStackWithParam->paramCollection->paramValueToKnobPos(previousValue,
modelStackWithParam);

//Here is where we check if the Knob/Fader on the Midi Controller is out of sync with the Deluge Knob Position

//First we check if the Midi Knob/Fader is sending a Value that is less the current Deluge Knob Position
//If less, check by how much its less. If the difference is greater than 1, ignore the CC value change (or scale it if value scaling is on)
if (midiKnobPos == (knobPos - 1)) {
newKnobPos = knobPos - 1;
}

//Next we check if the Midi Knob/Fader is sending a Value that is greater than the current Deluge Knob Position
//If greater, check by how much its greater. If the difference is greater than 1, ignore the CC value change (or scale it if value scaling is on)
else if (midiKnobPos == (knobPos + 1)) {
newKnobPos = knobPos + 1;
}

else {

//if the first two conditions fail and pickup mode is enabled, then the Deluge Knob Position (and therefore the Parameter Value with it) remains unchanged
if (midiEngine.midiTakeover == MIDI_TAKEOVER_PICKUP) { //Midi Pickup Mode On
newKnobPos = knobPos;
}
//if the first two conditions fail and value scaling mode is enabled, then the Deluge Knob Position is scaled upwards or downwards based on relative
//positions of Midi Controller Knob and Deluge Knob to min/max of knob range.
else { //Midi Value Scaling Mode On
//Set the max and min of the deluge midi knob position range
int knobMaxPos = 64;
int knobMinPos = -64;

//calculate amount of deluge "knob runway" is remaining from current knob position to max and min of knob position range
int delugeKnobMaxPosDelta = knobMaxPos - knobPos; //Positive Runway
int delugeKnobMinPosDelta = knobPos - knobMinPos; //Negative Runway

//calculate amount of midi "knob runway" is remaining from current knob position to max and min of knob position range
int midiKnobMaxPosDelta = knobMaxPos - midiKnobPos; //Positive Runway
int midiKnobMinPosDelta = midiKnobPos - knobMinPos; //Negative Runway

//calculate by how much the current midiKnobPos has changed from the previous midiKnobPos recorded
int midiKnobPosChange = midiKnobPos - knob->previousPosition;

//Set fixed point variable which will be used calculate the percentage in midi knob position
int midiKnobPosChangePercentage;

//if midi knob position change is greater than 0, then the midi knob position has increased (e.g. turned knob right)
if (midiKnobPosChange > 0) {
//fixed point math calculation of new deluge knob position when midi knob position has increased

midiKnobPosChangePercentage = (midiKnobPosChange << 20) / midiKnobMaxPosDelta;

newKnobPos =
knobPos + ((delugeKnobMaxPosDelta * midiKnobPosChangePercentage) >> 20);
}
//if midi knob position change is less than 0, then the midi knob position has decreased (e.g. turned knob left)
else if (midiKnobPosChange < 0) {
//fixed point math calculation of new deluge knob position when midi knob position has decreased

midiKnobPosChangePercentage = (midiKnobPosChange << 20) / midiKnobMinPosDelta;

newKnobPos =
knobPos + ((delugeKnobMinPosDelta * midiKnobPosChangePercentage) >> 20);
}
//if midi knob position change is 0, then the midi knob position has not changed and thus no change in deluge knob position / parameter value is required
else {
newKnobPos = knobPos;
}
}
}

//save the current midi knob position as the previous midi knob position so that it can be used next time the takeover code is executed
knob->previousPosition = midiKnobPos;
}
}

//Convert the New Knob Position to a Parameter Value
int32_t newValue =
modelStackWithParam->paramCollection->knobPosToParamValue(newKnobPos, modelStackWithParam);

//Set the new Parameter Value for the MIDI Learned Parameter
modelStackWithParam->autoParam->setValuePossiblyForRegion(newValue, modelStackWithParam, modPos,
modLength);
}
Expand Down
2 changes: 2 additions & 0 deletions src/deluge/modulation/knob.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class MIDIKnob : public Knob {
bool topValueIs127() { return (midiInput.noteOrCC < 128 && !relative); }
LearnedMIDI midiInput;
bool relative;
bool previousPositionSaved;
int previousPosition;
};

class ModKnob : public Knob {
Expand Down
4 changes: 4 additions & 0 deletions src/deluge/storage/flash_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ void resetSettings() {
PadLEDs::flashCursor = FLASH_CURSOR_SLOW;

midiEngine.midiThru = false;
midiEngine.midiTakeover = 0;

for (int i = 0; i < NUM_GLOBAL_MIDI_COMMANDS; i++) {
midiEngine.globalMIDICommands[i].clear();
Expand Down Expand Up @@ -363,6 +364,7 @@ void readSettings() {
defaultBendRange[BEND_RANGE_MAIN] = 12;
}
}
midiEngine.midiTakeover = buffer[113];
}

void writeSettings() {
Expand Down Expand Up @@ -455,6 +457,8 @@ void writeSettings() {

buffer[112] = defaultBendRange[BEND_RANGE_MAIN];

buffer[113] = midiEngine.midiTakeover;

R_SFLASH_EraseSector(0x80000 - 0x1000, SPIBSC_CH, SPIBSC_CMNCR_BSZ_SINGLE, 1, SPIBSC_OUTPUT_ADDR_24);
R_SFLASH_ByteProgram(0x80000 - 0x1000, buffer, 256, SPIBSC_CH, SPIBSC_CMNCR_BSZ_SINGLE, SPIBSC_1BIT,
SPIBSC_OUTPUT_ADDR_24);
Expand Down

0 comments on commit 1124f4a

Please sign in to comment.