diff --git a/MicroDexed.ino b/MicroDexed.ino index a73c061..a4ddaa5 100644 --- a/MicroDexed.ino +++ b/MicroDexed.ino @@ -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,158 +934,267 @@ 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) { - // - // Drum Sampler - // -#if NUM_DRUMS > 0 - if (activesample < 6 && seq.running == false && LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_seq_pattern_editor) ) // live play pitched sample + if (seq.midi_learn_active && LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_custom_mappings) ) + learn_key(inChannel, inNumber); + else { - if (drum_counter >= NUM_DRUMS) - drum_counter = 0; - uint8_t slot = drum_get_slot(drum_config[activesample].drum_class); - float pan = mapfloat(drum_config[activesample].pan, -1.0, 1.0, 0.0, 1.0); - drum_mixer_r.gain(slot, (1.0 - pan) * drum_config[activesample].vol_max); - drum_mixer_l.gain(slot, pan * drum_config[activesample].vol_max); + // + // Drum Sampler + // +#if NUM_DRUMS > 0 + if (activesample < 6 && seq.running == false && LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_seq_pattern_editor) ) // live play pitched sample + { + if (drum_counter >= NUM_DRUMS) + drum_counter = 0; + uint8_t slot = drum_get_slot(drum_config[activesample].drum_class); + float pan = mapfloat(drum_config[activesample].pan, -1.0, 1.0, 0.0, 1.0); + drum_mixer_r.gain(slot, (1.0 - pan) * drum_config[activesample].vol_max); + drum_mixer_l.gain(slot, pan * drum_config[activesample].vol_max); #ifdef USE_FX - drum_reverb_send_mixer_r.gain(slot, (1.0 - pan) * volume_transform(drum_config[activesample].reverb_send)); - drum_reverb_send_mixer_l.gain(slot, pan * volume_transform(drum_config[activesample].reverb_send)); + drum_reverb_send_mixer_r.gain(slot, (1.0 - pan) * volume_transform(drum_config[activesample].reverb_send)); + drum_reverb_send_mixer_l.gain(slot, pan * volume_transform(drum_config[activesample].reverb_send)); #endif - if (drum_config[activesample].drum_data != NULL && drum_config[activesample].len > 0) - { - Drum[slot]->enableInterpolation(true); - Drum[slot]->setPlaybackRate( (float)pow (2, (inNumber - 72) / 12.00) * drum_config[activesample].p_offset ); - Drum[slot]->playRaw((int16_t*)drum_config[activesample].drum_data, drum_config[activesample].len, 1); + if (drum_config[activesample].drum_data != NULL && drum_config[activesample].len > 0) + { + Drum[slot]->enableInterpolation(true); + Drum[slot]->setPlaybackRate( (float)pow (2, (inNumber - 72) / 12.00) * drum_config[activesample].p_offset ); + Drum[slot]->playRaw((int16_t*)drum_config[activesample].drum_data, drum_config[activesample].len, 1); + } } - } - else + else #endif - //Ignore the note when playing & recording the same note into the sequencer - if (seq.recording == false || (seq.recording && inNumber != seq.note_in )) - { - // Check for MicroDexed - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + //Ignore the note when playing & recording the same note into the sequencer + if (seq.recording == false || (seq.recording && inNumber != seq.note_in )) { - if (checkMidiChannel(inChannel, instance_id)) + // Check for MicroDexed + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) { - if (inNumber >= configuration.dexed[instance_id].lowest_note && inNumber <= configuration.dexed[instance_id].highest_note) + if (checkMidiChannel(inChannel, instance_id)) { - if (configuration.dexed[instance_id].polyphony > 0) - MicroDexed[instance_id]->keydown(inNumber, uint8_t(float(configuration.dexed[instance_id].velocity_level / 127.0)*inVelocity + 0.5)); + if (inNumber >= configuration.dexed[instance_id].lowest_note && inNumber <= configuration.dexed[instance_id].highest_note) + { + if (configuration.dexed[instance_id].polyphony > 0) + MicroDexed[instance_id]->keydown(inNumber, uint8_t(float(configuration.dexed[instance_id].velocity_level / 127.0)*inVelocity + 0.5)); - midi_voices[instance_id]++; + midi_voices[instance_id]++; #ifdef TEENSY4 - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) - { - midi_decay_timer = 0; - midi_decay[instance_id] = min(inVelocity / 5, 7); - } + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) + { + midi_decay_timer = 0; + midi_decay[instance_id] = min(inVelocity / 5, 7); + } #endif #ifdef DEBUG - char note_name[4]; - getNoteName(note_name, inNumber); - Serial.print(F("KeyDown ")); - Serial.print(note_name); - Serial.print(F(" instance ")); - Serial.print(instance_id, DEC); - Serial.print(F(" MIDI-channel ")); - Serial.print(inChannel, DEC); - Serial.println(); + char note_name[4]; + getNoteName(note_name, inNumber); + Serial.print(F("KeyDown ")); + Serial.print(note_name); + Serial.print(F(" instance ")); + Serial.print(instance_id, DEC); + Serial.print(F(" MIDI-channel ")); + Serial.print(inChannel, DEC); + Serial.println(); #endif - return; + return; + } } } - } #if NUM_DRUMS > 0 - // Check for Drum - if (inChannel == drum_midi_channel || drum_midi_channel == MIDI_CHANNEL_OMNI) - { - if (drum_counter >= NUM_DRUMS) - drum_counter = 0; + // Check for Drum + if (inChannel == drum_midi_channel || drum_midi_channel == MIDI_CHANNEL_OMNI) + { + 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); - Serial.print(F("=> Drum[")); - Serial.print(drum_counter, DEC); - Serial.print(F("]: ")); - Serial.println(note_name); + char note_name[4]; + getNoteName(note_name, inNumber); + Serial.print(F("=> Drum[")); + Serial.print(drum_counter, DEC); + Serial.print(F("]: ")); + Serial.println(note_name); #endif - for (uint8_t d = 0; d < NUM_DRUMSET_CONFIG; d++) - { - if (inNumber == drum_config[d].midinote) + for (uint8_t d = 0; d < NUM_DRUMSET_CONFIG; d++) { - uint8_t slot = drum_get_slot(drum_config[d].drum_class); - float pan = mapfloat(drum_config[d].pan, -1.0, 1.0, 0.0, 1.0); + if (inNumber == drum_config[d].midinote) + { + uint8_t slot = drum_get_slot(drum_config[d].drum_class); + float pan = mapfloat(drum_config[d].pan, -1.0, 1.0, 0.0, 1.0); - drum_mixer_r.gain(slot, (1.0 - pan) * volume_transform(mapfloat(inVelocity, 0, 127, drum_config[d].vol_min, drum_config[d].vol_max))); - drum_mixer_l.gain(slot, pan * volume_transform(mapfloat(inVelocity, 0, 127, drum_config[d].vol_min, drum_config[d].vol_max))); + drum_mixer_r.gain(slot, (1.0 - pan) * volume_transform(mapfloat(inVelocity, 0, 127, drum_config[d].vol_min, drum_config[d].vol_max))); + drum_mixer_l.gain(slot, pan * volume_transform(mapfloat(inVelocity, 0, 127, drum_config[d].vol_min, drum_config[d].vol_max))); #ifdef USE_FX - drum_reverb_send_mixer_r.gain(slot, (1.0 - pan) * volume_transform(drum_config[d].reverb_send)); - drum_reverb_send_mixer_l.gain(slot, pan * volume_transform(drum_config[d].reverb_send)); + drum_reverb_send_mixer_r.gain(slot, (1.0 - pan) * volume_transform(drum_config[d].reverb_send)); + drum_reverb_send_mixer_l.gain(slot, pan * volume_transform(drum_config[d].reverb_send)); #endif - if (drum_config[d].drum_data != NULL && drum_config[d].len > 0) - { - //Drum[slot]->play(drum_config[d].drum_data); - if (drum_config[d].pitch != 0.0) + if (drum_config[d].drum_data != NULL && drum_config[d].len > 0) { - Drum[slot]->enableInterpolation(true); - Drum[slot]->setPlaybackRate(drum_config[d].pitch); + //Drum[slot]->play(drum_config[d].drum_data); + if (drum_config[d].pitch != 0.0) + { + Drum[slot]->enableInterpolation(true); + Drum[slot]->setPlaybackRate(drum_config[d].pitch); + } + Drum[slot]->playRaw((int16_t*)drum_config[d].drum_data, drum_config[d].len, 1); } - Drum[slot]->playRaw((int16_t*)drum_config[d].drum_data, drum_config[d].len, 1); - } #ifdef DEBUG - Serial.print(F("Drum ")); - Serial.print(drum_config[d].shortname); - Serial.print(F(" [")); - Serial.print(drum_config[d].name); - Serial.print(F("], Slot ")); - Serial.print(slot); - Serial.print(F(": V")); - Serial.print(mapfloat(inVelocity, 0, 127, drum_config[d].vol_min, drum_config[d].vol_max), 2); - Serial.print(F(" P")); - Serial.print(drum_config[d].pan, 2); - Serial.print(F(" PAN")); - Serial.print(pan, 2); - Serial.print(F(" RS")); - Serial.println(drum_config[d].reverb_send, 2); -#endif - break; + Serial.print(F("Drum ")); + Serial.print(drum_config[d].shortname); + Serial.print(F(" [")); + Serial.print(drum_config[d].name); + Serial.print(F("], Slot ")); + Serial.print(slot); + Serial.print(F(": V")); + Serial.print(mapfloat(inVelocity, 0, 127, drum_config[d].vol_min, drum_config[d].vol_max), 2); + Serial.print(F(" P")); + Serial.print(drum_config[d].pan, 2); + Serial.print(F(" PAN")); + Serial.print(pan, 2); + Serial.print(F(" RS")); + Serial.println(drum_config[d].reverb_send, 2); +#endif + break; + } } } - } #endif - } + } - // - // E-Piano - // + // + // E-Piano + // #if defined(USE_EPIANO) - if (configuration.epiano.midi_channel == MIDI_CHANNEL_OMNI || configuration.epiano.midi_channel == inChannel) - { - if (inNumber >= configuration.epiano.lowest_note && inNumber <= configuration.epiano.highest_note) + if (configuration.epiano.midi_channel == MIDI_CHANNEL_OMNI || configuration.epiano.midi_channel == inChannel) { - ep.noteOn(inNumber + configuration.epiano.transpose - 24, inVelocity); + if (inNumber >= configuration.epiano.lowest_note && inNumber <= configuration.epiano.highest_note) + { + ep.noteOn(inNumber + configuration.epiano.transpose - 24, inVelocity); #ifdef DEBUG - char note_name[4]; - getNoteName(note_name, inNumber); - Serial.print(F("KeyDown ")); - Serial.print(note_name); - Serial.print(F(" EPIANO ")); - Serial.print(F(" MIDI-channel ")); - Serial.print(inChannel, DEC); - Serial.println(); + char note_name[4]; + getNoteName(note_name, inNumber); + Serial.print(F("KeyDown ")); + Serial.print(note_name); + Serial.print(F(" EPIANO ")); + Serial.print(F(" MIDI-channel ")); + Serial.print(inChannel, DEC); + Serial.println(); #endif + } } - } #endif + } } #if NUM_DRUMS > 0 @@ -1177,216 +1287,252 @@ void handleControlChange(byte inChannel, byte inCtrl, byte inValue) inCtrl = constrain(inCtrl, 0, 127); inValue = constrain(inValue, 0, 127); - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + if (seq.midi_learn_active && LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_cc_mappings) ) + learn_cc(inChannel, inCtrl); + else { - if (checkMidiChannel(inChannel, instance_id)) + + //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)) + { #ifdef DEBUG - Serial.print(F("INSTANCE ")); - Serial.print(instance_id, DEC); - Serial.print(F(": CC#")); - Serial.print(inCtrl, DEC); - Serial.print(F(":")); - Serial.println(inValue, DEC); + Serial.print(F("INSTANCE ")); + Serial.print(instance_id, DEC); + Serial.print(F(": CC#")); + Serial.print(inCtrl, DEC); + Serial.print(F(":")); + Serial.println(inValue, DEC); #endif - switch (inCtrl) { - case 0: // BankSelect MSB + switch (inCtrl) { + case 0: // BankSelect MSB #ifdef DEBUG - Serial.println(F("BANK-SELECT MSB CC")); + Serial.println(F("BANK-SELECT MSB CC")); #endif - configuration.dexed[instance_id].bank = constrain((inValue << 7)&configuration.dexed[instance_id].bank, 0, MAX_BANKS - 1); - /* load_sd_voice(configuration.dexed[instance_id].bank, configuration.dexed[instance_id].voice, instance_id); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } */ - break; - case 1: + configuration.dexed[instance_id].bank = constrain((inValue << 7)&configuration.dexed[instance_id].bank, 0, MAX_BANKS - 1); + /* load_sd_voice(configuration.dexed[instance_id].bank, configuration.dexed[instance_id].voice, instance_id); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } */ + break; + case 1: #ifdef DEBUG - Serial.println(F("MODWHEEL CC")); + Serial.println(F("MODWHEEL CC")); #endif - MicroDexed[instance_id]->setModWheel(inValue); - MicroDexed[instance_id]->ControllersRefresh(); - break; - case 2: + MicroDexed[instance_id]->setModWheel(inValue); + MicroDexed[instance_id]->ControllersRefresh(); + break; + case 2: #ifdef DEBUG - Serial.println(F("BREATH CC")); + Serial.println(F("BREATH CC")); #endif - MicroDexed[instance_id]->setBreathController(inValue); - MicroDexed[instance_id]->ControllersRefresh(); - break; - case 4: + MicroDexed[instance_id]->setBreathController(inValue); + MicroDexed[instance_id]->ControllersRefresh(); + break; + case 4: #ifdef DEBUG - Serial.println(F("FOOT CC")); + Serial.println(F("FOOT CC")); #endif - MicroDexed[instance_id]->setFootController(inValue); - MicroDexed[instance_id]->ControllersRefresh(); - break; - case 5: // Portamento time - configuration.dexed[instance_id].portamento_time = inValue; - MicroDexed[instance_id]->setPortamentoMode(configuration.dexed[instance_id].portamento_mode, configuration.dexed[instance_id].portamento_glissando, configuration.dexed[instance_id].portamento_time); - break; - case 7: // Instance Volume + MicroDexed[instance_id]->setFootController(inValue); + MicroDexed[instance_id]->ControllersRefresh(); + break; + case 5: // Portamento time + configuration.dexed[instance_id].portamento_time = inValue; + MicroDexed[instance_id]->setPortamentoMode(configuration.dexed[instance_id].portamento_mode, configuration.dexed[instance_id].portamento_glissando, configuration.dexed[instance_id].portamento_time); + break; + case 7: // Instance Volume #ifdef DEBUG - Serial.println(F("VOLUME CC")); + Serial.println(F("VOLUME CC")); #endif - configuration.dexed[instance_id].sound_intensity = map(inValue, 0, 127, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX); - MicroDexed[instance_id]->setGain(midi_volume_transform(map(configuration.dexed[instance_id].sound_intensity, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX, 0, 127))); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_sound_intensity)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 10: // Pan + configuration.dexed[instance_id].sound_intensity = map(inValue, 0, 127, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX); + MicroDexed[instance_id]->setGain(midi_volume_transform(map(configuration.dexed[instance_id].sound_intensity, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX, 0, 127))); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_sound_intensity)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 10: // Pan #ifdef DEBUG - Serial.println(F("PANORAMA CC")); + Serial.println(F("PANORAMA CC")); #endif - configuration.dexed[instance_id].pan = map(inValue, 0, 0x7f, PANORAMA_MIN, PANORAMA_MAX); - mono2stereo[instance_id]->panorama(mapfloat(configuration.dexed[instance_id].pan, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_panorama)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 32: // BankSelect LSB + configuration.dexed[instance_id].pan = map(inValue, 0, 0x7f, PANORAMA_MIN, PANORAMA_MAX); + mono2stereo[instance_id]->panorama(mapfloat(configuration.dexed[instance_id].pan, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_panorama)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 32: // BankSelect LSB #ifdef DEBUG - Serial.println(F("BANK-SELECT LSB CC")); + Serial.println(F("BANK-SELECT LSB CC")); #endif - configuration.dexed[instance_id].bank = constrain(inValue, 0, MAX_BANKS - 1); - /*load_sd_voice(configuration.dexed[instance_id].bank, configuration.dexed[instance_id].voice, instance_id); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - }*/ - break; - case 64: - MicroDexed[instance_id]->setSustain(inValue > 63); - if (!MicroDexed[instance_id]->getSustain()) - { - for (uint8_t note = 0; note < MicroDexed[instance_id]->getMaxNotes(); note++) + configuration.dexed[instance_id].bank = constrain(inValue, 0, MAX_BANKS - 1); + /*load_sd_voice(configuration.dexed[instance_id].bank, configuration.dexed[instance_id].voice, instance_id); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + }*/ + break; + case 64: + MicroDexed[instance_id]->setSustain(inValue > 63); + if (!MicroDexed[instance_id]->getSustain()) { - if (MicroDexed[instance_id]->voices[note].sustained && !MicroDexed[instance_id]->voices[note].keydown) + for (uint8_t note = 0; note < MicroDexed[instance_id]->getMaxNotes(); note++) { - MicroDexed[instance_id]->voices[note].dx7_note->keyup(); - MicroDexed[instance_id]->voices[note].sustained = false; + if (MicroDexed[instance_id]->voices[note].sustained && !MicroDexed[instance_id]->voices[note].keydown) + { + MicroDexed[instance_id]->voices[note].dx7_note->keyup(); + MicroDexed[instance_id]->voices[note].sustained = false; + } } } - } - break; - case 65: - MicroDexed[instance_id]->setPortamentoMode(configuration.dexed[instance_id].portamento_mode, configuration.dexed[instance_id].portamento_glissando, configuration.dexed[instance_id].portamento_time); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_portamento_mode)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 94: // CC 94: (de)tune - configuration.dexed[selected_instance_id].tune = map(inValue, 0, 0x7f, TUNE_MIN, TUNE_MAX); - MicroDexed[selected_instance_id]->setMasterTune((int((configuration.dexed[selected_instance_id].tune - 100) / 100.0 * 0x4000) << 11) * (1.0 / 12)); - MicroDexed[selected_instance_id]->doRefreshVoice(); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_tune)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; + break; + case 65: + MicroDexed[instance_id]->setPortamentoMode(configuration.dexed[instance_id].portamento_mode, configuration.dexed[instance_id].portamento_glissando, configuration.dexed[instance_id].portamento_time); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_portamento_mode)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 94: // CC 94: (de)tune + configuration.dexed[selected_instance_id].tune = map(inValue, 0, 0x7f, TUNE_MIN, TUNE_MAX); + MicroDexed[selected_instance_id]->setMasterTune((int((configuration.dexed[selected_instance_id].tune - 100) / 100.0 * 0x4000) << 11) * (1.0 / 12)); + MicroDexed[selected_instance_id]->doRefreshVoice(); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_tune)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; #if defined(USE_FX) - case 91: // CC 91: reverb send - configuration.fx.reverb_send[selected_instance_id] = map(inValue, 0, 0x7f, REVERB_SEND_MIN, REVERB_SEND_MAX); - reverb_mixer_r.gain(selected_instance_id, volume_transform(mapfloat(configuration.fx.reverb_send[selected_instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, VOL_MAX_FLOAT))); - reverb_mixer_l.gain(selected_instance_id, volume_transform(mapfloat(configuration.fx.reverb_send[selected_instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, VOL_MAX_FLOAT))); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_reverb_send)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 93: // CC 93: chorus level - configuration.fx.chorus_level[selected_instance_id] = map(inValue, 0, 0x7f, CHORUS_LEVEL_MIN, CHORUS_LEVEL_MAX); - chorus_mixer[selected_instance_id]->gain(1, volume_transform(mapfloat(configuration.fx.chorus_level[selected_instance_id], CHORUS_LEVEL_MIN, CHORUS_LEVEL_MAX, 0.0, 0.5))); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_chorus_level)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 103: // CC 103: filter resonance - configuration.fx.filter_resonance[instance_id] = map(inValue, 0, 0x7f, FILTER_RESONANCE_MIN, FILTER_RESONANCE_MAX); - MicroDexed[instance_id]->setFilterResonance(mapfloat(configuration.fx.filter_resonance[instance_id], FILTER_RESONANCE_MIN, FILTER_RESONANCE_MAX, 1.0, 0.0)); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_filter_resonance)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 104: // CC 104: filter cutoff - configuration.fx.filter_cutoff[instance_id] = map(inValue, 0, 0x7f, FILTER_CUTOFF_MIN, FILTER_CUTOFF_MAX); - MicroDexed[instance_id]->setFilterCutoff(mapfloat(configuration.fx.filter_cutoff[instance_id], FILTER_CUTOFF_MIN, FILTER_CUTOFF_MAX, 1.0, 0.0));; - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_filter_cutoff)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 105: // CC 105: delay time - configuration.fx.delay_time[instance_id] = map(inValue, 0, 0x7f, DELAY_TIME_MIN, DELAY_TIME_MAX); - delay_fx[instance_id]->delay(0, constrain(configuration.fx.delay_time[instance_id] * 10, DELAY_TIME_MIN * 10, DELAY_TIME_MAX * 10)); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_delay_time)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 106: // CC 106: delay feedback - configuration.fx.delay_feedback[instance_id] = map(inValue, 0, 0x7f, DELAY_FEEDBACK_MIN , DELAY_FEEDBACK_MAX); - delay_fb_mixer[instance_id]->gain(1, midi_volume_transform(map(configuration.fx.delay_feedback[instance_id], DELAY_FEEDBACK_MIN, DELAY_FEEDBACK_MAX, 0, 127))); // amount of feedback - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_delay_feedback)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 107: // CC 107: delay volume - configuration.fx.delay_level[instance_id] = map(inValue, 0, 0x7f, DELAY_LEVEL_MIN, DELAY_LEVEL_MAX); - delay_mixer[instance_id]->gain(1, midi_volume_transform(map(configuration.fx.delay_level[instance_id], DELAY_LEVEL_MIN, DELAY_LEVEL_MAX, 0, 127))); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_delay_level)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; + case 91: // CC 91: reverb send + configuration.fx.reverb_send[selected_instance_id] = map(inValue, 0, 0x7f, REVERB_SEND_MIN, REVERB_SEND_MAX); + reverb_mixer_r.gain(selected_instance_id, volume_transform(mapfloat(configuration.fx.reverb_send[selected_instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, VOL_MAX_FLOAT))); + reverb_mixer_l.gain(selected_instance_id, volume_transform(mapfloat(configuration.fx.reverb_send[selected_instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, VOL_MAX_FLOAT))); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_reverb_send)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 93: // CC 93: chorus level + configuration.fx.chorus_level[selected_instance_id] = map(inValue, 0, 0x7f, CHORUS_LEVEL_MIN, CHORUS_LEVEL_MAX); + chorus_mixer[selected_instance_id]->gain(1, volume_transform(mapfloat(configuration.fx.chorus_level[selected_instance_id], CHORUS_LEVEL_MIN, CHORUS_LEVEL_MAX, 0.0, 0.5))); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_chorus_level)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 103: // CC 103: filter resonance + configuration.fx.filter_resonance[instance_id] = map(inValue, 0, 0x7f, FILTER_RESONANCE_MIN, FILTER_RESONANCE_MAX); + MicroDexed[instance_id]->setFilterResonance(mapfloat(configuration.fx.filter_resonance[instance_id], FILTER_RESONANCE_MIN, FILTER_RESONANCE_MAX, 1.0, 0.0)); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_filter_resonance)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 104: // CC 104: filter cutoff + configuration.fx.filter_cutoff[instance_id] = map(inValue, 0, 0x7f, FILTER_CUTOFF_MIN, FILTER_CUTOFF_MAX); + MicroDexed[instance_id]->setFilterCutoff(mapfloat(configuration.fx.filter_cutoff[instance_id], FILTER_CUTOFF_MIN, FILTER_CUTOFF_MAX, 1.0, 0.0));; + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_filter_cutoff)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 105: // CC 105: delay time + configuration.fx.delay_time[instance_id] = map(inValue, 0, 0x7f, DELAY_TIME_MIN, DELAY_TIME_MAX); + delay_fx[instance_id]->delay(0, constrain(configuration.fx.delay_time[instance_id] * 10, DELAY_TIME_MIN * 10, DELAY_TIME_MAX * 10)); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_delay_time)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 106: // CC 106: delay feedback + configuration.fx.delay_feedback[instance_id] = map(inValue, 0, 0x7f, DELAY_FEEDBACK_MIN , DELAY_FEEDBACK_MAX); + delay_fb_mixer[instance_id]->gain(1, midi_volume_transform(map(configuration.fx.delay_feedback[instance_id], DELAY_FEEDBACK_MIN, DELAY_FEEDBACK_MAX, 0, 127))); // amount of feedback + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_delay_feedback)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 107: // CC 107: delay volume + configuration.fx.delay_level[instance_id] = map(inValue, 0, 0x7f, DELAY_LEVEL_MIN, DELAY_LEVEL_MAX); + delay_mixer[instance_id]->gain(1, midi_volume_transform(map(configuration.fx.delay_level[instance_id], DELAY_LEVEL_MIN, DELAY_LEVEL_MAX, 0, 127))); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_delay_level)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; #endif - case 120: - MicroDexed[instance_id]->panic(); - break; - case 121: - MicroDexed[instance_id]->resetControllers(); - break; - case 123: - MicroDexed[instance_id]->notesOff(); - break; - case 126: - if (inValue > 0) - MicroDexed[instance_id]->setMonoMode(true); - else - MicroDexed[instance_id]->setMonoMode(false); - break; - case 127: - if (inValue > 0) - MicroDexed[instance_id]->setMonoMode(true); - else - MicroDexed[instance_id]->setMonoMode(false); - break; + case 120: + MicroDexed[instance_id]->panic(); + break; + case 121: + MicroDexed[instance_id]->resetControllers(); + break; + case 123: + MicroDexed[instance_id]->notesOff(); + break; + case 126: + if (inValue > 0) + MicroDexed[instance_id]->setMonoMode(true); + else + MicroDexed[instance_id]->setMonoMode(false); + break; + case 127: + if (inValue > 0) + MicroDexed[instance_id]->setMonoMode(true); + 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); diff --git a/UI.hpp b/UI.hpp index 1bc76d9..81bcf7a 100644 --- a/UI.hpp +++ b/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 Disp_Plus 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}; diff --git a/config.h b/config.h index 8499de9..a35a7f0 100644 --- a/config.h +++ b/config.h @@ -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" diff --git a/dexed_sd.cpp b/dexed_sd.cpp index b1fdf5e..9830531 100644 --- a/dexed_sd.cpp +++ b/dexed_sd.cpp @@ -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 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 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) { diff --git a/drums.h b/drums.h index 30ab926..0f3c216 100644 --- a/drums.h +++ b/drums.h @@ -38,7 +38,7 @@ typedef struct drum_config_s { const uint8_t* drum_data; char shortname[2]; // 1 char name for sequencer uint32_t len; // number of elements in drum_data - float32_t pitch; // variable pitch per note for sequencer + float32_t pitch; // variable pitch per note for sequencer float32_t p_offset; // "static" pitch offset to correct root note to root of other samples float32_t pan; // Panorama (-1.0 - +1.0) float32_t vol_max; // max. Volume (0.0 - 1.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 diff --git a/sequencer.h b/sequencer.h index 1287471..a552a73 100644 --- a/sequencer.h +++ b/sequencer.h @@ -27,6 +27,7 @@ typedef struct sequencer_s { + bool midi_learn_active = false; float drums_volume; uint8_t active_track = 0; uint8_t menu;