Merge pull request 'Added custom/user MIDI Note & CC Mappings with MIDI learn' (#112) from positionhigh/MicroDexed:dev into dev

Reviewed-on: https://codeberg.org/dcoredump/MicroDexed/pulls/112
dev
Holger Wirtz 3 years ago
commit a2c442b12b
  1. 148
      MicroDexed.ino
  2. 229
      UI.hpp
  3. 2
      config.h
  4. 149
      dexed_sd.cpp
  5. 10
      drums.h
  6. 1
      sequencer.h

@ -416,6 +416,7 @@ uint8_t drum_counter;
uint8_t drum_type[NUM_DRUMS];
extern void sequencer(void);
uint8_t drum_midi_channel = DRUM_MIDI_CHANNEL;
custom_midi_map_t custom_midi_map[NUM_CUSTOM_MIDI_MAPPINGS];
#endif
#ifdef ENABLE_LCD_UI
@ -933,11 +934,109 @@ void loop()
#endif
}
void learn_key(byte inChannel, byte inNumber)
{
uint8_t found = 199;
if (inChannel == DRUM_MIDI_CHANNEL)
{
for (uint8_t c = 0; c < NUM_CUSTOM_MIDI_MAPPINGS; c++)
{
if (inNumber == custom_midi_map[c].in && custom_midi_map[c].type == 1)
{
found = c;
break;
}
}
if (found != 199) //remap to new destination if it was already mapped before
{
custom_midi_map[found].in = inNumber;
custom_midi_map[found].out = drum_config[activesample].midinote;
custom_midi_map[found].type = 1;
custom_midi_map[found].channel = DRUM_MIDI_CHANNEL;
}
else
{
found = 199;
for (uint8_t c = 0; c < NUM_CUSTOM_MIDI_MAPPINGS; c++)
{
if (custom_midi_map[c].in == 0)
{
found = c;
break;
}
}
if (found != 199) // else map to next empty slot if it was not mapped before
{
custom_midi_map[found].in = inNumber;
custom_midi_map[found].out = drum_config[activesample].midinote;
custom_midi_map[found].type = 1;
custom_midi_map[found].channel = DRUM_MIDI_CHANNEL;
}
else
; // can not be mapped, no empty slot left
}
}
seq.midi_learn_active = false;
//update_midi_learn_button();
print_custom_mappings();
}
void learn_cc(byte inChannel, byte inNumber)
{
uint8_t found = 199;
for (uint8_t c = 0; c < NUM_CUSTOM_MIDI_MAPPINGS; c++)
{
if (inNumber == custom_midi_map[c].in && custom_midi_map[c].type == 2)
{
found = c;
break;
}
}
if (found != 199) //remap to new destination if it was already mapped before
{
custom_midi_map[found].in = inNumber;
custom_midi_map[found].out = cc_dest_values[seq.temp_select_menu];
custom_midi_map[found].type = 2;
custom_midi_map[found].channel = configuration.dexed[selected_instance_id].midi_channel;
}
else
{
found = 199;
for (uint8_t c = 0; c < NUM_CUSTOM_MIDI_MAPPINGS; c++)
{
if (custom_midi_map[c].in == 0)
{
found = c;
break;
}
}
if (found != 199) // else map to next empty slot if it was not mapped before
{
custom_midi_map[found].in = inNumber;
custom_midi_map[found].out = cc_dest_values[seq.temp_select_menu];
custom_midi_map[found].type = 2;
custom_midi_map[found].channel = configuration.dexed[selected_instance_id].midi_channel;
}
else
; // can not be mapped, no empty slot left
}
seq.midi_learn_active = false;
//update_midi_learn_button();
print_custom_mappings();
}
/******************************************************************************
MIDI MESSAGE HANDLER
******************************************************************************/
void handleNoteOn(byte inChannel, byte inNumber, byte inVelocity)
{
if (seq.midi_learn_active && LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_custom_mappings) )
learn_key(inChannel, inNumber);
else
{
//
// Drum Sampler
//
@ -1007,6 +1106,16 @@ void handleNoteOn(byte inChannel, byte inNumber, byte inVelocity)
if (drum_counter >= NUM_DRUMS)
drum_counter = 0;
//check custom midi mapping
for (uint8_t c = 0; c < NUM_CUSTOM_MIDI_MAPPINGS; c++)
{
if (inNumber == custom_midi_map[c].in && custom_midi_map[c].type == 1)
{
inNumber = custom_midi_map[c].out;
break;
}
}
#ifdef DEBUG
char note_name[4];
getNoteName(note_name, inNumber);
@ -1085,6 +1194,7 @@ void handleNoteOn(byte inChannel, byte inNumber, byte inVelocity)
}
}
#endif
}
}
#if NUM_DRUMS > 0
@ -1177,6 +1287,21 @@ void handleControlChange(byte inChannel, byte inCtrl, byte inValue)
inCtrl = constrain(inCtrl, 0, 127);
inValue = constrain(inValue, 0, 127);
if (seq.midi_learn_active && LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_cc_mappings) )
learn_cc(inChannel, inCtrl);
else
{
//check custom midi mapping
for (uint8_t c = 0; c < NUM_CUSTOM_MIDI_MAPPINGS; c++)
{
if (inCtrl == custom_midi_map[c].in && custom_midi_map[c].type == 2)
{
inCtrl = custom_midi_map[c].out;
inChannel = custom_midi_map[c].channel;
break;
}
}
for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++)
{
if (checkMidiChannel(inChannel, instance_id))
@ -1383,10 +1508,31 @@ void handleControlChange(byte inChannel, byte inCtrl, byte inValue)
else
MicroDexed[instance_id]->setMonoMode(false);
break;
case 200: // CC 200: seq start/stop
if (!seq.running)
handleStart();
else
handleStop();
break;
case 201: // CC 201: seq stop
if (seq.running)
handleStop();
break;
case 202: // CC 202: seq record
if (seq.running)
seq.running = true;
seq.recording = true;
seq.note_in = 0;
break;
case 203: // CC 203: dexed panic
MicroDexed[0]->panic();
#if NUM_DEXED > 1
MicroDexed[1]->panic();
#endif
}
}
}
}
#if defined(USE_EPIANO)
if (configuration.epiano.midi_channel == MIDI_CHANNEL_OMNI || configuration.epiano.midi_channel == inChannel)
ep.processMidiController(inCtrl, inValue);

229
UI.hpp

@ -85,6 +85,7 @@ extern drum_config_t drum_config[NUM_DRUMSET_CONFIG];
extern sequencer_t seq;
uint8_t seq_active_function = 99;
uint8_t activesample;
extern custom_midi_map_t custom_midi_map[NUM_CUSTOM_MIDI_MAPPINGS];
#endif
#ifdef SGTL5000_AUDIO_ENHANCE
@ -175,6 +176,18 @@ int temp_int;
bool menu_select_toggle;
float temp_float;
const char cc_names[8][12] = { "Volume ",
"Panorama ",
"Bank Select",
"Reverb Send",
"Seq. Start ",
"Seq. Stop ",
"Seq. RECORD",
"Panic Dexed"
};
const uint8_t cc_dest_values[8] = { 7, 10, 32, 91, 200, 201, 202, 203 };
#ifdef I2C_DISPLAY
#include <LiquidCrystal_I2C.h>
Disp_Plus<LiquidCrystal_I2C> display(LCD_I2C_ADDRESS, _LCDML_DISP_cols, _LCDML_DISP_rows);
@ -4788,6 +4801,222 @@ void UI_func_drum_pitch(uint8_t param)
encoderDir[ENC_R].reset();
}
}
void print_custom_mappings()
{
// display.setTextSize(2);
// display.setTextColor(WHITE, BLACK);
//
// for (uint8_t y = 0; y < 10; y++)
// {
// display.setCursor_textGrid(1, y + 8);
// display.setTextColor(WHITE, BLACK);
// seq_print_formatted_number(y + 1, 2); //entry no.
//
// if (custom_midi_map[y].type == 0)
// {
// display.setTextColor(GREY1, BLACK);
// display.show(y + 8, 4, 5, "NONE" );
// }
// else if (custom_midi_map[y].type == 1)
// {
// display.setTextColor(DX_CYAN, BLACK);
// display.show(y + 8, 4, 7, "KEY/PAD" );
// }
// else if (custom_midi_map[y].type == 2)
// {
// display.setTextColor(DX_CYAN, BLACK);
// display.show(y + 8, 4, 7, "MIDI CC" );
// }
// display.setTextColor(DX_ORANGE, BLACK);
// display.show(y + 8, 12, 3, custom_midi_map[y].in );
//
// display.setTextColor(DX_MAGENTA, BLACK);
// display.show(y + 8, 16, 3, custom_midi_map[y].out );
//
// display.setTextColor(LIGHTBLUE, BLACK);
// display.show(y + 8, 20, 3, custom_midi_map[y].channel );
//
//
// display.setTextColor(DX_PURPLE, BLACK);
// if (custom_midi_map[y].in == 0)
// display.show(y + 8, 24, 12, "EMPTY SLOT");
// else if (custom_midi_map[y].type == 1)
// {
// display.setTextColor(PINK, BLACK);
// display.show(y + 8, 24, 13, find_long_drum_name_from_note(custom_midi_map[y].out) );
// }
// else if (custom_midi_map[y].type == 2)
// {
// display.setTextColor(LIGHTBLUE, BLACK);
// for (uint8_t i = 0; i < sizeof(cc_dest_values); i++)
// {
// if (custom_midi_map[y].out == cc_dest_values[i])
// display.show(y + 8, 24, 13, cc_names[i] );
// }
// }
// }
;
}
void UI_func_custom_mappings(uint8_t param)
{
//char displayname[8] = {0, 0, 0, 0, 0, 0, 0};
if (LCDML.FUNC_setup()) // ****** SETUP *********
{
encoderDir[ENC_R].reset();
// display.fillScreen(BLACK);
// display.setTextColor(WHITE, BLACK);
// display.setTextSize(2);
// border1();
// border2();
// border3_large();
// display.setCursor_textGrid(1, 1);
// display.print(F("CUSTOM MAPPINGS"));
//
// display.setTextColor(WHITE, BLUE);
// display.setTextSize(2);
// display.fillRect (240 + CHAR_width, CHAR_height, 8 * CHAR_width, 4 * CHAR_height, BLUE);
// display.setCursor(240 + CHAR_width * 2 + 5, 2 * CHAR_height );
// display.print(F("TOUCH"));
//
// display.drawBitmap(240 + CHAR_width * 2 + 4, CHAR_height * 4 - 12, special_chars[19], 8, 8, GREEN);
// display.setCursor(240 + CHAR_width * 3 + 10, CHAR_height * 4 - 12 );
// display.setTextSize(1);
// display.print(F("PREVIEW"));
//
// display.fillRect (240 + CHAR_width + CHAR_width * 10 - 2, CHAR_height, 8 * CHAR_width, 4 * CHAR_height, BLUE);
// display.setCursor(240 + CHAR_width * 12 + 4, 2 * CHAR_height);
// display.setTextSize(2);
// display.print(F("TOUCH"));
// display.setCursor(240 + CHAR_width * 12 + 3, CHAR_height * 4 - 12 );
// display.setTextSize(1);
// display.print(F("MIDI LEARN"));
//
// //scrollbar
// display.fillRect (480 - 28, 8 * CHAR_height, 14, 10 * CHAR_height, WHITE);
// display.fillRect (480 - 27, 8 * CHAR_height + 1, 12, 4 * CHAR_height, GREY1);
//
// display.setTextSize(2);
// display.setTextColor(WHITE, BLACK);
// display.setCursor_textGrid(1, 7);
// display.print(F("NO TYPE IN OUT CH. NAME"));
print_custom_mappings();
}
if (LCDML.FUNC_loop()) // ****** LOOP *********
{
if ((LCDML.BT_checkDown() && encoderDir[ENC_R].Down()) || (LCDML.BT_checkUp() && encoderDir[ENC_R].Up()) || (LCDML.BT_checkEnter() && encoderDir[ENC_R].ButtonShort()))
{
if (LCDML.BT_checkDown())
{
smart_filter(1);
}
else if (LCDML.BT_checkUp())
{
smart_filter(0);
}
}
if (LCDML.BT_checkEnter())
{
;
}
// display.setTextColor(WHITE, BLACK);
// display.setTextSize(2);
// display.setCursor_textGrid(1, 2);
// display.print("[");
// display.setCursor_textGrid(4, 2);
// display.print("]");
// display.setCursor_textGrid(2, 2);
//
// sprintf(displayname, "%02d", activesample);
// display.print(displayname);
// display.setTextColor(DX_CYAN, BLACK);
// display.show(2, 5, 13, basename(drum_config[activesample].name));
}
if (LCDML.FUNC_close()) // ****** STABLE END *********
{
seq.midi_learn_active = false;
encoderDir[ENC_R].reset();
}
}
void UI_func_cc_mappings(uint8_t param)
{
if (LCDML.FUNC_setup()) // ****** SETUP *********
{
encoderDir[ENC_R].reset();
// display.fillScreen(BLACK);
// display.setTextColor(WHITE, BLACK);
// display.setTextSize(2);
// border1();
// border2();
// border3_large();
UI_update_instance_icons();
// display.setCursor_textGrid(1, 1);
// display.print("CUSTOM DEXED CC");
//
// display.setTextColor(WHITE, BLUE);
// display.setTextSize(2);
//
// display.fillRect (240 + CHAR_width + CHAR_width * 10 - 2, CHAR_height, 8 * CHAR_width, 4 * CHAR_height, BLUE);
// display.setCursor(240 + CHAR_width * 12 + 4, 2 * CHAR_height);
// display.setTextSize(2);
// display.print(F("TOUCH"));
// display.setCursor(240 + CHAR_width * 12 + 3, CHAR_height * 4 - 12 );
// display.setTextSize(1);
// display.print(F("MIDI LEARN"));
//
// //scrollbar
// display.fillRect (480 - 28, 8 * CHAR_height, 14, 10 * CHAR_height, WHITE);
// display.fillRect (480 - 27, 8 * CHAR_height + 1, 12, 4 * CHAR_height, GREY1);
//
// display.setTextSize(2);
// display.setTextColor(WHITE, BLACK);
// display.setCursor_textGrid(1, 7);
// display.print(F("NO TYPE IN OUT CH. NAME"));
print_custom_mappings();
}
if (LCDML.FUNC_loop()) // ****** LOOP *********
{
if ((LCDML.BT_checkDown() && encoderDir[ENC_R].Down()) || (LCDML.BT_checkUp() && encoderDir[ENC_R].Up()) || (LCDML.BT_checkEnter() && encoderDir[ENC_R].ButtonShort()))
{
if (LCDML.BT_checkDown())
{
seq.temp_select_menu = constrain(seq.temp_select_menu + ENCODER[ENC_R].speed(), 0, 7);
}
else if (LCDML.BT_checkUp())
{
seq.temp_select_menu = constrain(seq.temp_select_menu - ENCODER[ENC_R].speed(), 0, 7);
}
}
if (LCDML.BT_checkEnter())
{
selected_instance_id = !selected_instance_id;
UI_update_instance_icons();
}
// display.setTextSize(2);
// display.setTextColor(DX_CYAN, BLACK);
// display.show(2, 1, 13, cc_names[seq.temp_select_menu] );
}
if (LCDML.FUNC_close()) // ****** STABLE END *********
{
seq.midi_learn_active = false;
encoderDir[ENC_R].reset();
}
}
void UI_func_drum_volume(uint8_t param)
{
char displayname[8] = {0, 0, 0, 0, 0, 0, 0};

@ -119,6 +119,7 @@
#endif
// DEFAULT MIDI CHANNEL FOR DRUMSAMPLER
#define DRUM_MIDI_CHANNEL 10
#define NUM_CUSTOM_MIDI_MAPPINGS 20 //Number of Custom Key, CC and Button Mappings
// NUMBER OF SAMPLES IN DRUMSET
#if defined(ARDUINO_TEENSY41)
@ -335,6 +336,7 @@
#define PERFORMANCE_CONFIG_PATH "PERFORMANCE"
#define SEQUENCER_CONFIG_NAME "sequencer"
#define DRUMS_CONFIG_NAME "drums"
#define DRUMS_MAPPING_NAME "drmmap"
#define PATTERN_CONFIG_NAME "patterns"
#define VELOCITY_CONFIG_NAME "velocity"
#define FX_CONFIG_NAME "fx"

@ -38,6 +38,7 @@ using namespace TeensyTimerTool;
#include "drums.h"
extern void set_drums_volume(float vol);
extern drum_config_t drum_config[];
extern custom_midi_map_t custom_midi_map[NUM_CUSTOM_MIDI_MAPPINGS];
#endif
extern void init_MIDI_send_CC(void);
@ -434,6 +435,148 @@ bool save_sd_bank(const char* bank_filename, uint8_t* data)
return (true);
}
/******************************************************************************
SD DRUM CUSTOM MAPPINGS
******************************************************************************/
bool load_sd_drummappings_json(uint8_t number)
{
if (number < 0)
return (false);
number = constrain(number, PERFORMANCE_NUM_MIN, PERFORMANCE_NUM_MAX);
if (sd_card > 0)
{
File json;
StaticJsonDocument<JSON_BUFFER_SIZE> data_json;
char filename[CONFIG_FILENAME_LEN];
sprintf(filename, "/%s/%d/%s.json", PERFORMANCE_CONFIG_PATH, number, DRUMS_MAPPING_NAME);
// first check if file exists...
AudioNoInterrupts();
if (SD.exists(filename))
{
// ... and if: load
#ifdef DEBUG
Serial.print(F("Found drum mapping ["));
Serial.print(filename);
Serial.println(F("]... loading..."));
#endif
json = SD.open(filename);
if (json)
{
deserializeJson(data_json, json);
json.close();
AudioInterrupts();
#ifdef DEBUG
Serial.println(F("Read JSON data:"));
serializeJsonPretty(data_json, Serial);
Serial.println();
#endif
for (uint8_t i = 0; i < NUM_CUSTOM_MIDI_MAPPINGS - 1; i++)
{
custom_midi_map[i].type = data_json["type"][i];
custom_midi_map[i].in = data_json["in"][i];
custom_midi_map[i].out = data_json["out"][i];
custom_midi_map[i].channel = data_json["channel"][i];
}
return (true);
}
#ifdef DEBUG
else
{
Serial.print(F("E : Cannot open "));
Serial.print(filename);
Serial.println(F(" on SD."));
}
}
else
{
Serial.print(F("No "));
Serial.print(filename);
Serial.println(F(" available."));
#endif
}
}
return (false);
}
bool save_sd_drummappings_json(uint8_t number)
{
char filename[CONFIG_FILENAME_LEN];
number = constrain(number, 0, 99);
if (sd_card > 0)
{
File json;
StaticJsonDocument<JSON_BUFFER_SIZE> data_json;
if (check_performance_directory(number))
{
sprintf(filename, "/%s/%d/%s.json", PERFORMANCE_CONFIG_PATH, number, DRUMS_MAPPING_NAME);
#ifdef DEBUG
Serial.print(F("Saving drum mapping "));
Serial.print(number);
Serial.print(F(" to "));
Serial.println(filename);
#endif
AudioNoInterrupts();
if (SD.exists(filename)) {
Serial.println("remove old drum mapping file");
SD.begin();
SD.remove(filename);
}
json = SD.open(filename, FILE_WRITE);
if (json)
{
for (uint8_t i = 0; i < NUM_CUSTOM_MIDI_MAPPINGS - 1; i++)
{
data_json["type"][i] = custom_midi_map[i].type;
data_json["in"][i] = custom_midi_map[i].in;
data_json["out"][i] = custom_midi_map[i].out;
data_json["channel"][i] = custom_midi_map[i].channel;
}
#ifdef DEBUG
Serial.println(F("Write JSON data:"));
serializeJsonPretty(data_json, Serial);
Serial.println();
#endif
serializeJsonPretty(data_json, json);
json.close();
AudioInterrupts();
return (true);
}
else
{
#ifdef DEBUG
Serial.print(F("E : Cannot open "));
Serial.print(filename);
Serial.println(F(" on SD."));
#endif
AudioInterrupts();
return (false);
}
}
else
{
AudioInterrupts();
return (false);
}
}
#ifdef DEBUG
else
{
Serial.println(F("E: SD card not available"));
}
#endif
return (false);
}
/******************************************************************************
SD DRUMSETTINGS
******************************************************************************/
@ -532,7 +675,9 @@ bool save_sd_drumsettings_json(uint8_t number)
#endif
AudioNoInterrupts();
if (SD.exists(filename)) {
#ifdef DEBUG
Serial.println("remove old drumsettings file");
#endif
SD.begin();
SD.remove(filename);
}
@ -1371,6 +1516,7 @@ bool save_sd_performance_json(uint8_t number)
save_sd_seq_sub_vel_json(number);
save_sd_seq_sub_patterns_json(number);
save_sd_drummappings_json(number);
save_sd_fx_json(number);
save_sd_epiano_json(number);
@ -1456,6 +1602,7 @@ bool save_sd_performance_json(uint8_t number)
return (true);
}
json.close();
AudioInterrupts();
}
#ifdef DEBUG
else
@ -1463,6 +1610,7 @@ bool save_sd_performance_json(uint8_t number)
Serial.print(F("E : Cannot open "));
Serial.print(filename);
Serial.println(F(" on SD."));
AudioInterrupts();
}
#endif
@ -1718,6 +1866,7 @@ bool load_sd_performance_json(uint8_t number)
load_sd_seq_sub_vel_json(number);
load_sd_fx_json(number);
load_sd_epiano_json(number);
load_sd_drummappings_json(number);
if (sd_card > 0)
{

@ -46,6 +46,14 @@ typedef struct drum_config_s {
float32_t reverb_send; // how much signal to send to the reverb (0.0 - 1.0)
} drum_config_t;
enum {DRUM_NONE, DRUM_BASS, DRUM_SNARE, DRUM_HIHAT, DRUM_HANDCLAP, DRUM_RIDE, DRUM_CRASH, DRUM_LOWTOM, DRUM_MIDTOM, DRUM_HIGHTOM, DRUM_PERCUSSION,DRUM_POLY};
enum {DRUM_NONE, DRUM_BASS, DRUM_SNARE, DRUM_HIHAT, DRUM_HANDCLAP, DRUM_RIDE, DRUM_CRASH, DRUM_LOWTOM, DRUM_MIDTOM, DRUM_HIGHTOM, DRUM_PERCUSSION, DRUM_POLY};
typedef struct custom_midi_map_s {
uint8_t type; // 0 = empty, 1 = Key/Pad, 2 = CC / Values, 3 = Button push on/off
uint8_t channel; // Midi Channel
uint8_t in; // Midi Input Key/Pad / Value
uint8_t out; // Destination Key / Value
} custom_midi_map_t;
#endif

@ -27,6 +27,7 @@
typedef struct sequencer_s
{
bool midi_learn_active = false;
float drums_volume;
uint8_t active_track = 0;
uint8_t menu;

Loading…
Cancel
Save