diff --git a/MicroDexed.ino b/MicroDexed.ino index 13953fe..6fcdf77 100644 --- a/MicroDexed.ino +++ b/MicroDexed.ino @@ -610,6 +610,10 @@ void handleControlChange(byte inChannel, byte inCtrl, byte inValue) MicroDexed[instance_id]->controllers.foot_cc = inValue; MicroDexed[instance_id]->controllers.refresh(); 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: // Volume #ifdef DEBUG Serial.println(F("VOLUME CC")); @@ -644,6 +648,9 @@ void handleControlChange(byte inChannel, byte inCtrl, byte inValue) } } 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); + break; case 103: // CC 103: filter resonance configuration.dexed[instance_id].filter_resonance = map(inValue, 0, 0x7f, FILTER_RESONANCE_MIN, FILTER_RESONANCE_MAX); MicroDexed[instance_id]->fx.Reso = mapfloat(configuration.dexed[instance_id].filter_resonance, FILTER_RESONANCE_MIN, FILTER_RESONANCE_MAX, 1.0, 0.0); @@ -832,6 +839,7 @@ void handleSystemExclusive(byte * sysex, uint len) MicroDexed[instance_id]->controllers.at.setRange(MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_AT_RANGE]); MicroDexed[instance_id]->controllers.at.setTarget(MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_AT_ASSIGN]); MicroDexed[instance_id]->controllers.masterTune = (MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MASTER_TUNE] / 10 * 0x4000 << 11) * (1.0 / 12); + MicroDexed[instance_id]->setPortamentoMode(MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_MODE], MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_GLISSANDO], MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_TIME]); MicroDexed[instance_id]->controllers.refresh(); data_index = DEXED_GLOBAL_PARAMETER_OFFSET - 63 + sysex[4]; } @@ -1182,20 +1190,23 @@ void check_configuration(void) configuration.dexed[instance_id].filter_resonance = constrain(configuration.dexed[instance_id].filter_resonance, FILTER_RESONANCE_MIN, FILTER_RESONANCE_MAX); configuration.dexed[instance_id].loudness = constrain(configuration.dexed[instance_id].loudness, LOUDNESS_MIN, LOUDNESS_MAX); configuration.dexed[instance_id].transpose = constrain(configuration.dexed[instance_id].transpose, TRANSPOSE_MIN, TRANSPOSE_MAX); - configuration.dexed[instance_id].tune = constrain(configuration.dexed[instance_id].tune, TUNE_MIN, TUNE_MAX); + configuration.dexed[instance_id].tune = constrain(configuration.dexed[instance_id].tune, TUNE_MIN, TUNE_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MASTER_TUNE] = configuration.dexed[instance_id].tune; configuration.dexed[instance_id].polyphony = constrain(configuration.dexed[instance_id].polyphony, POLYPHONY_MIN, POLYPHONY_MAX); configuration.dexed[instance_id].engine = constrain(configuration.dexed[instance_id].engine, ENGINE_MIN, ENGINE_MAX); configuration.dexed[instance_id].monopoly = constrain(configuration.dexed[instance_id].monopoly, MONOPOLY_MIN, MONOPOLY_MAX); - configuration.dexed[instance_id].pb_range = constrain(configuration.dexed[instance_id].pb_range, PB_RANGE_MIN, PB_RANGE_MAX); - configuration.dexed[instance_id].pb_step = constrain(configuration.dexed[instance_id].pb_step, PB_STEP_MIN, PB_STEP_MAX); - configuration.dexed[instance_id].mw_range = constrain(configuration.dexed[instance_id].mw_range, MW_RANGE_MIN, MW_RANGE_MAX); - configuration.dexed[instance_id].mw_assign = constrain(configuration.dexed[instance_id].mw_assign, MW_ASSIGN_MIN, MW_ASSIGN_MAX); - configuration.dexed[instance_id].fc_range = constrain(configuration.dexed[instance_id].fc_range, FC_RANGE_MIN, FC_RANGE_MAX); - configuration.dexed[instance_id].fc_assign = constrain(configuration.dexed[instance_id].fc_assign, FC_ASSIGN_MIN, FC_ASSIGN_MAX); - configuration.dexed[instance_id].bc_range = constrain(configuration.dexed[instance_id].bc_range, BC_RANGE_MIN, BC_RANGE_MAX); - configuration.dexed[instance_id].bc_assign = constrain(configuration.dexed[instance_id].bc_assign, BC_ASSIGN_MIN, BC_ASSIGN_MAX); - configuration.dexed[instance_id].at_range = constrain(configuration.dexed[instance_id].at_range, AT_RANGE_MIN, AT_RANGE_MAX); - configuration.dexed[instance_id].at_assign = constrain(configuration.dexed[instance_id].at_assign, AT_ASSIGN_MIN, AT_ASSIGN_MAX); + configuration.dexed[instance_id].pb_range = constrain(configuration.dexed[instance_id].pb_range, PB_RANGE_MIN, PB_RANGE_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PITCHBEND_RANGE] = configuration.dexed[instance_id].pb_range; + configuration.dexed[instance_id].pb_step = constrain(configuration.dexed[instance_id].pb_step, PB_STEP_MIN, PB_STEP_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PITCHBEND_STEP] = configuration.dexed[instance_id].pb_step; + configuration.dexed[instance_id].mw_range = constrain(configuration.dexed[instance_id].mw_range, MW_RANGE_MIN, MW_RANGE_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MODWHEEL_RANGE] = configuration.dexed[instance_id].mw_range; + configuration.dexed[instance_id].mw_assign = constrain(configuration.dexed[instance_id].mw_assign, MW_ASSIGN_MIN, MW_ASSIGN_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MODWHEEL_ASSIGN] = configuration.dexed[instance_id].mw_assign; + configuration.dexed[instance_id].fc_range = constrain(configuration.dexed[instance_id].fc_range, FC_RANGE_MIN, FC_RANGE_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_FOOTCTRL_RANGE] = configuration.dexed[instance_id].fc_range; + configuration.dexed[instance_id].fc_assign = constrain(configuration.dexed[instance_id].fc_assign, FC_ASSIGN_MIN, FC_ASSIGN_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_FOOTCTRL_ASSIGN] = configuration.dexed[instance_id].fc_assign; + configuration.dexed[instance_id].bc_range = constrain(configuration.dexed[instance_id].bc_range, BC_RANGE_MIN, BC_RANGE_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_BREATHCTRL_RANGE] = configuration.dexed[instance_id].bc_range; + configuration.dexed[instance_id].bc_assign = constrain(configuration.dexed[instance_id].bc_assign, BC_ASSIGN_MIN, BC_ASSIGN_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_BREATHCTRL_ASSIGN] = configuration.dexed[instance_id].bc_assign; + configuration.dexed[instance_id].at_range = constrain(configuration.dexed[instance_id].at_range, AT_RANGE_MIN, AT_RANGE_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_AT_RANGE] = configuration.dexed[instance_id].at_range; + configuration.dexed[instance_id].at_assign = constrain(configuration.dexed[instance_id].at_assign, AT_ASSIGN_MIN, AT_ASSIGN_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_AT_ASSIGN] = configuration.dexed[instance_id].at_assign; + configuration.dexed[instance_id].portamento_mode = constrain(configuration.dexed[instance_id].portamento_mode, PORTAMENTO_MODE_MIN, PORTAMENTO_MODE_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_MODE] = configuration.dexed[instance_id].portamento_mode; + configuration.dexed[instance_id].portamento_glissando = constrain(configuration.dexed[instance_id].portamento_glissando, PORTAMENTO_GLISSANDO_MIN, PORTAMENTO_GLISSANDO_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_GLISSANDO] = configuration.dexed[instance_id].portamento_glissando; + configuration.dexed[instance_id].portamento_time = constrain(configuration.dexed[instance_id].portamento_time, PORTAMENTO_TIME_MIN, PORTAMENTO_TIME_MAX); MicroDexed[instance_id]->data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_TIME] = configuration.dexed[instance_id].portamento_time; configuration.dexed[instance_id].op_enabled = constrain(configuration.dexed[instance_id].op_enabled, OP_ENABLED_MIN, OP_ENABLED_MAX); } } @@ -1246,6 +1257,9 @@ void init_configuration(void) configuration.dexed[instance_id].bc_assign = BC_ASSIGN_DEFAULT; configuration.dexed[instance_id].at_range = AT_RANGE_DEFAULT; configuration.dexed[instance_id].at_assign = AT_ASSIGN_DEFAULT; + configuration.dexed[instance_id].portamento_mode = PORTAMENTO_MODE_DEFAULT; + configuration.dexed[instance_id].portamento_glissando = PORTAMENTO_GLISSANDO_DEFAULT; + configuration.dexed[instance_id].portamento_time = PORTAMENTO_TIME_DEFAULT; configuration.dexed[instance_id].op_enabled = OP_ENABLED_DEFAULT; } eeprom_update(); @@ -1359,31 +1373,33 @@ void show_configuration(void) Serial.print(F("=== DEXED INSTANCE ")); Serial.print(instance_id, DEC); Serial.println(" ==="); - Serial.print(F(" MIDI-Channel ")); Serial.println(configuration.dexed[instance_id].midi_channel, DEC); - Serial.print(F(" Bank ")); Serial.println(configuration.dexed[instance_id].bank, DEC); - Serial.print(F(" Voice ")); Serial.println(configuration.dexed[instance_id].voice, DEC); - Serial.print(F(" Reverb Send ")); Serial.println(configuration.dexed[instance_id].reverb_send, DEC); - Serial.print(F(" Chorus Send ")); Serial.println(configuration.dexed[instance_id].chorus_send, DEC); - Serial.print(F(" Delay Send ")); Serial.println(configuration.dexed[instance_id].delay_send, DEC); - Serial.print(F(" Filter Cutoff ")); Serial.println(configuration.dexed[instance_id].filter_cutoff, DEC); - Serial.print(F(" Filter Resonance ")); Serial.println(configuration.dexed[instance_id].filter_resonance, DEC); - Serial.print(F(" Loudness ")); Serial.println(configuration.dexed[instance_id].loudness, DEC); - Serial.print(F(" Transpose ")); Serial.println(configuration.dexed[instance_id].transpose, DEC); - Serial.print(F(" Tune ")); Serial.println(configuration.dexed[instance_id].tune, DEC); - Serial.print(F(" Polyphony ")); Serial.println(configuration.dexed[instance_id].polyphony, DEC); - Serial.print(F(" Engine ")); Serial.println(configuration.dexed[instance_id].engine, DEC); - Serial.print(F(" Mono/Poly ")); Serial.println(configuration.dexed[instance_id].monopoly, DEC); - Serial.print(F(" Pitchbend Range ")); Serial.println(configuration.dexed[instance_id].pb_range, DEC); - Serial.print(F(" Pitchbend Step ")); Serial.println(configuration.dexed[instance_id].pb_step, DEC); - Serial.print(F(" Modwheel Range ")); Serial.println(configuration.dexed[instance_id].mw_range, DEC); - Serial.print(F(" Modwheel Assign ")); Serial.println(configuration.dexed[instance_id].mw_assign, DEC); - Serial.print(F(" Footctrl Range ")); Serial.println(configuration.dexed[instance_id].fc_range, DEC); - Serial.print(F(" Footctrl Assign ")); Serial.println(configuration.dexed[instance_id].fc_assign, DEC); - Serial.print(F(" BreathCtrl Range ")); Serial.println(configuration.dexed[instance_id].bc_range, DEC); - Serial.print(F(" Breathctrl Assign ")); Serial.println(configuration.dexed[instance_id].bc_assign, DEC); - Serial.print(F(" Aftertouch Range ")); Serial.println(configuration.dexed[instance_id].at_range, DEC); - Serial.print(F(" Aftertouch Assign ")); Serial.println(configuration.dexed[instance_id].at_assign, DEC); - Serial.print(F(" OP Enabled ")); Serial.println(configuration.dexed[instance_id].op_enabled, DEC); + Serial.print(F(" MIDI-Channel ")); Serial.println(configuration.dexed[instance_id].midi_channel, DEC); + Serial.print(F(" Bank ")); Serial.println(configuration.dexed[instance_id].bank, DEC); + Serial.print(F(" Voice ")); Serial.println(configuration.dexed[instance_id].voice, DEC); + Serial.print(F(" Reverb Send ")); Serial.println(configuration.dexed[instance_id].reverb_send, DEC); + Serial.print(F(" Chorus Send ")); Serial.println(configuration.dexed[instance_id].chorus_send, DEC); + Serial.print(F(" Delay Send ")); Serial.println(configuration.dexed[instance_id].delay_send, DEC); + Serial.print(F(" Filter Cutoff ")); Serial.println(configuration.dexed[instance_id].filter_cutoff, DEC); + Serial.print(F(" Filter Resonance ")); Serial.println(configuration.dexed[instance_id].filter_resonance, DEC); + Serial.print(F(" Loudness ")); Serial.println(configuration.dexed[instance_id].loudness, DEC); + Serial.print(F(" Transpose ")); Serial.println(configuration.dexed[instance_id].transpose, DEC); + Serial.print(F(" Tune ")); Serial.println(configuration.dexed[instance_id].tune, DEC); + Serial.print(F(" Polyphony ")); Serial.println(configuration.dexed[instance_id].polyphony, DEC); + Serial.print(F(" Engine ")); Serial.println(configuration.dexed[instance_id].engine, DEC); + Serial.print(F(" Mono/Poly ")); Serial.println(configuration.dexed[instance_id].monopoly, DEC); + Serial.print(F(" Pitchbend Range ")); Serial.println(configuration.dexed[instance_id].pb_range, DEC); + Serial.print(F(" Pitchbend Step ")); Serial.println(configuration.dexed[instance_id].pb_step, DEC); + Serial.print(F(" Modwheel Range ")); Serial.println(configuration.dexed[instance_id].mw_range, DEC); + Serial.print(F(" Modwheel Assign ")); Serial.println(configuration.dexed[instance_id].mw_assign, DEC); + Serial.print(F(" Footctrl Range ")); Serial.println(configuration.dexed[instance_id].fc_range, DEC); + Serial.print(F(" Footctrl Assign ")); Serial.println(configuration.dexed[instance_id].fc_assign, DEC); + Serial.print(F(" BreathCtrl Range ")); Serial.println(configuration.dexed[instance_id].bc_range, DEC); + Serial.print(F(" Breathctrl Assign ")); Serial.println(configuration.dexed[instance_id].bc_assign, DEC); + Serial.print(F(" Aftertouch Range ")); Serial.println(configuration.dexed[instance_id].at_range, DEC); + Serial.print(F(" Portamento Mode ")); Serial.println(configuration.dexed[instance_id].portamento_mode, DEC); + Serial.print(F(" Portamento Glissando ")); Serial.println(configuration.dexed[instance_id].portamento_glissando, DEC); + Serial.print(F(" Portamento Time ")); Serial.println(configuration.dexed[instance_id].portamento_time, DEC); + Serial.print(F(" OP Enabled ")); Serial.println(configuration.dexed[instance_id].op_enabled, DEC); Serial.flush(); } Serial.println(); @@ -1484,7 +1500,7 @@ void show_patch(uint8_t instance_id) Serial.print(voicename); Serial.println(F("]")); Serial.flush(); - for (i = DEXED_GLOBAL_PARAMETER_OFFSET; i <= DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_MAX_NOTES; i++) + for (i = DEXED_GLOBAL_PARAMETER_OFFSET; i <= DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_TIME; i++) { Serial.print(i, DEC); Serial.print(F(": ")); diff --git a/UI.hpp b/UI.hpp index 917d42e..6aa350c 100644 --- a/UI.hpp +++ b/UI.hpp @@ -158,6 +158,9 @@ void UI_func_bc_range(uint8_t param); void UI_func_bc_assign(uint8_t param); void UI_func_at_range(uint8_t param); void UI_func_at_assign(uint8_t param); +void UI_func_portamento_mode(uint8_t param); +void UI_func_portamento_glissando(uint8_t param); +void UI_func_portamento_time(uint8_t param); void UI_func_OP1(uint8_t param); void UI_func_OP2(uint8_t param); void UI_func_OP3(uint8_t param); @@ -211,9 +214,9 @@ LCDML_add(28, LCDML_0_1_1, 17, "Aftertouch 1", NULL); LCDML_add(29, LCDML_0_1_1_17, 1, "AT Range 1", UI_func_at_range); LCDML_add(30, LCDML_0_1_1_17, 2, "AT Assign 1", UI_func_at_assign); LCDML_add(31, LCDML_0_1_1, 18, "Portamento 1", NULL); -LCDML_add(32, LCDML_0_1_1_18, 1, "Port. Mode 1", UI_function_not_enabled); -LCDML_add(33, LCDML_0_1_1_18, 2, "Port. Gliss 1", UI_function_not_enabled); -LCDML_add(34, LCDML_0_1_1_18, 3, "Port. Time 1", UI_function_not_enabled); +LCDML_add(32, LCDML_0_1_1_18, 1, "Port. Mode 1", UI_func_portamento_mode); +LCDML_add(33, LCDML_0_1_1_18, 2, "Port. Gliss 1", UI_func_portamento_glissando); +LCDML_add(34, LCDML_0_1_1_18, 3, "Port. Time 1", UI_func_portamento_time); LCDML_add(35, LCDML_0_1_1, 19, "Operator 1", NULL); LCDML_add(36, LCDML_0_1_1_19, 1, "OP1 1", UI_func_OP1); LCDML_add(37, LCDML_0_1_1_19, 2, "OP2 1", UI_func_OP2); @@ -252,9 +255,9 @@ LCDML_add(69, LCDML_0_1_2, 17, "Aftertouch 2", NULL); LCDML_add(70, LCDML_0_1_2_17, 1, "AT Range 2", UI_func_at_range); LCDML_add(71, LCDML_0_1_2_17, 2, "AT Assign 2", UI_func_at_assign); LCDML_add(72, LCDML_0_1_2, 18, "Portamento 2", NULL); -LCDML_add(73, LCDML_0_1_2_18, 1, "Port. Mode 2", UI_function_not_enabled); -LCDML_add(74, LCDML_0_1_2_18, 2, "Port. Gliss 2", UI_function_not_enabled); -LCDML_add(75, LCDML_0_1_2_18, 3, "Port. Time 2", UI_function_not_enabled); +LCDML_add(73, LCDML_0_1_2_18, 1, "Port. Mode 2", UI_func_portamento_mode); +LCDML_add(74, LCDML_0_1_2_18, 2, "Port. Gliss 2", UI_func_portamento_glissando); +LCDML_add(75, LCDML_0_1_2_18, 3, "Port. Time 2", UI_func_portamento_time); LCDML_add(76, LCDML_0_1_2, 19, "Operator 2", NULL); LCDML_add(77, LCDML_0_1_2_19, 1, "OP1 2", UI_func_OP1); LCDML_add(78, LCDML_0_1_2_19, 2, "OP2 2", UI_func_OP2); @@ -311,9 +314,9 @@ LCDML_add(27, LCDML_0_1, 17, "Aftertouch", NULL); LCDML_add(28, LCDML_0_1_17, 1, "AT Range", UI_func_at_range); LCDML_add(29, LCDML_0_1_17, 2, "AT Assign", UI_func_at_assign); LCDML_add(30, LCDML_0_1, 18, "Portamento", NULL); -LCDML_add(31, LCDML_0_1_18, 1, "Port. Mode", UI_function_not_implemented); -LCDML_add(32, LCDML_0_1_18, 2, "Port. Gliss", UI_function_not_implemented); -LCDML_add(33, LCDML_0_1_18, 3, "Port. Time", UI_function_not_implemented); +LCDML_add(31, LCDML_0_1_18, 1, "Port. Mode", UI_func_portamento_mode); +LCDML_add(32, LCDML_0_1_18, 2, "Port. Gliss", UI_func_portamento_glissando); +LCDML_add(33, LCDML_0_1_18, 3, "Port. Time", UI_func_portamento_time); LCDML_add(34, LCDML_0_1, 19, "Operator", NULL); LCDML_add(35, LCDML_0_1_19, 1, "OP1", UI_func_OP1); LCDML_add(36, LCDML_0_1_19, 2, "OP2", UI_func_OP2); @@ -2663,7 +2666,175 @@ void UI_func_at_assign(uint8_t param) lcd.print(F("[PTCH AMP EG-BS]")); break; } + } + + if (LCDML.FUNC_close()) // ****** STABLE END ********* + { + // you can here reset some global vars or do nothing + eeprom_write(); + } +} + +void UI_func_portamento_mode(uint8_t param) +{ + uint8_t instance_id = 0; + + if (LCDML.FUNC_getID() > MENU_ID_OF_INSTANCE_2) + instance_id = 1; + + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + // setup function + lcd.setCursor(0, 0); + lcd.print(F("Portamento Mode")); + } + + if (LCDML.FUNC_loop()) // ****** LOOP ********* + { + if (LCDML.BT_checkEnter()) + { + LCDML.FUNC_goBackToMenu(); + } + else if (LCDML.BT_checkDown() || LCDML.BT_checkUp()) + { + if (LCDML.BT_checkDown()) + { + if (configuration.dexed[instance_id].portamento_mode < PORTAMENTO_MODE_MAX) + { + configuration.dexed[instance_id].portamento_mode++; + } + } + else if (LCDML.BT_checkUp()) + { + if (configuration.dexed[instance_id].portamento_mode > PORTAMENTO_MODE_MIN) + { + configuration.dexed[instance_id].portamento_mode--; + } + } + + MicroDexed[instance_id]->setPortamentoMode(configuration.dexed[instance_id].portamento_mode, configuration.dexed[instance_id].portamento_glissando, configuration.dexed[instance_id].portamento_time); + } + + lcd.setCursor(0, 1); + switch (configuration.dexed[instance_id].portamento_mode) + { + case 0: + lcd.print(F("[RETAIN ]")); + break; + case 1: + lcd.print(F("[FOLLOW ]")); + break; + } + } + + if (LCDML.FUNC_close()) // ****** STABLE END ********* + { + // you can here reset some global vars or do nothing + eeprom_write(); + } +} + +void UI_func_portamento_glissando(uint8_t param) +{ + uint8_t instance_id = 0; + + if (LCDML.FUNC_getID() > MENU_ID_OF_INSTANCE_2) + instance_id = 1; + + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + // setup function + lcd.setCursor(0, 0); + lcd.print(F("Portam. Gliss.")); + } + + if (LCDML.FUNC_loop()) // ****** LOOP ********* + { + if (LCDML.BT_checkEnter()) + { + LCDML.FUNC_goBackToMenu(); + } + else if (LCDML.BT_checkDown() || LCDML.BT_checkUp()) + { + if (LCDML.BT_checkDown()) + { + if (configuration.dexed[instance_id].portamento_glissando < PORTAMENTO_GLISSANDO_MAX) + { + configuration.dexed[instance_id].portamento_glissando++; + } + } + else if (LCDML.BT_checkUp()) + { + if (configuration.dexed[instance_id].portamento_glissando > PORTAMENTO_GLISSANDO_MIN) + { + configuration.dexed[instance_id].portamento_glissando--; + } + } + + MicroDexed[instance_id]->setPortamentoMode(configuration.dexed[instance_id].portamento_mode, configuration.dexed[instance_id].portamento_glissando, configuration.dexed[instance_id].portamento_time); + } + + lcd.setCursor(0, 1); + switch (configuration.dexed[instance_id].portamento_glissando) + { + case 0: + lcd.print(F("[OFF ]")); + break; + case 1: + lcd.print(F("[ON ]")); + break; + } + } + + if (LCDML.FUNC_close()) // ****** STABLE END ********* + { + // you can here reset some global vars or do nothing + eeprom_write(); + } +} + +void UI_func_portamento_time(uint8_t param) +{ + uint8_t instance_id = 0; + + if (LCDML.FUNC_getID() > MENU_ID_OF_INSTANCE_2) + instance_id = 1; + + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + // setup function + lcd.setCursor(0, 0); + lcd.print(F("Portam. Time")); + } + if (LCDML.FUNC_loop()) // ****** LOOP ********* + { + if (LCDML.BT_checkEnter()) + { + LCDML.FUNC_goBackToMenu(); + } + else if (LCDML.BT_checkDown() || LCDML.BT_checkUp()) + { + if (LCDML.BT_checkDown()) + { + if (configuration.dexed[instance_id].portamento_time < PORTAMENTO_TIME_MAX) + { + configuration.dexed[instance_id].portamento_time++; + } + } + else if (LCDML.BT_checkUp()) + { + if (configuration.dexed[instance_id].portamento_time > PORTAMENTO_TIME_MIN) + { + configuration.dexed[instance_id].portamento_time--; + } + } + + MicroDexed[instance_id]->setPortamentoMode(configuration.dexed[instance_id].portamento_mode, configuration.dexed[instance_id].portamento_glissando, configuration.dexed[instance_id].portamento_time); + } + + lcd.setCursor(0, 1); + lcd_display_int(configuration.dexed[instance_id].portamento_time, 2, false, true, false); } if (LCDML.FUNC_close()) // ****** STABLE END ********* diff --git a/config.h b/config.h index f1f45e3..4de4cdd 100644 --- a/config.h +++ b/config.h @@ -403,13 +403,13 @@ enum { DEXED, REVERB, DELAY, CHORUS }; #define PORTAMENTO_MODE_MAX 1 #define PORTAMENTO_MODE_DEFAULT 0 // 0: Retain, 1: Follow -#define PORTAMENTO_GLISS_MIN 0 -#define PORTAMENTO_GLISS_MAX 1 -#define PORTAMENTO_GLISS_DEFAULT 0 +#define PORTAMENTO_GLISSANDO_MIN 0 +#define PORTAMENTO_GLISSANDO_MAX 1 +#define PORTAMENTO_GLISSANDO_DEFAULT 0 #define PORTAMENTO_TIME_MIN 0 #define PORTAMENTO_TIME_MAX 99 -#define PORTAMENTO_TIME_DEFAULT 50 +#define PORTAMENTO_TIME_DEFAULT 0 #define INSTANCES_MIN 1 #define INSTANCES_MAX NUM_DEXED @@ -449,6 +449,9 @@ typedef struct { uint8_t bc_assign; uint8_t at_range; uint8_t at_assign; + uint8_t portamento_mode; + uint8_t portamento_glissando; + uint8_t portamento_time; uint8_t op_enabled; } dexed_t; diff --git a/controllers.h b/controllers.h index a2e83be..a20c930 100644 --- a/controllers.h +++ b/controllers.h @@ -21,6 +21,7 @@ #include "synth.h" #include #include +#include // State of MIDI controllers const int kControllerPitch = 0; @@ -79,6 +80,8 @@ class Controllers { uint8_t breath_cc; uint8_t foot_cc; uint8_t modwheel_cc; + bool portamento_enable_cc; + int portamento_cc; int masterTune; diff --git a/dexed.cpp b/dexed.cpp index 670df5d..2f837d7 100644 --- a/dexed.cpp +++ b/dexed.cpp @@ -36,6 +36,7 @@ #include "PluginFx.h" #include #include +#include "porta.h" #ifdef USE_TEENSY_DSP #include #endif @@ -52,6 +53,7 @@ Dexed::Dexed(int rate) Lfo::init(rate); PitchEnv::init(rate); Env::init_sr(rate); + Porta::init_sr(rate); fx.init(rate); engineMkI = new EngineMkI; @@ -71,6 +73,7 @@ Dexed::Dexed(int rate) controllers.masterTune = 0; controllers.opSwitch = 0x3f; // enable all operators //controllers.opSwitch=0x00; + lastKeyDown = -1; lfo.reset(data + 137); @@ -188,6 +191,13 @@ void Dexed::keydown(uint8_t pitch, uint8_t velo) { pitch += data[144] - TRANSPOSE_FIX; + int previousKeyDown = lastKeyDown; + lastKeyDown = pitch; + + int porta = -1; + if ( controllers.portamento_enable_cc && previousKeyDown >= 0 ) + porta = controllers.portamento_cc; + uint8_t note = currentNote; uint8_t keydown_counter = 0; @@ -198,7 +208,7 @@ void Dexed::keydown(uint8_t pitch, uint8_t velo) { voices[note].velocity = velo; voices[note].sustained = sustain; voices[note].keydown = true; - voices[note].dx7_note->init(data, (int)pitch, (int)velo); + voices[note].dx7_note->init(data, pitch, velo, previousKeyDown, porta); if ( data[136] ) voices[note].dx7_note->oscSync(); break; @@ -355,6 +365,8 @@ void Dexed::resetControllers(void) controllers.foot_cc = 0; controllers.breath_cc = 0; controllers.aftertouch_cc = 0; + controllers.portamento_enable_cc = false; + controllers.portamento_cc = 0; controllers.refresh(); } @@ -718,3 +730,18 @@ void Dexed::setATController(uint8_t at_range, uint8_t at_assign) controllers.refresh(); } + +void Dexed::setPortamentoMode(uint8_t portamento_mode, uint8_t portamento_glissando, uint8_t portamento_time) +{ + data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_MODE] = portamento_mode; + data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_GLISSANDO] = portamento_glissando; + data[DEXED_GLOBAL_PARAMETER_OFFSET + DEXED_PORTAMENTO_TIME] = portamento_time; + controllers.portamento_cc = portamento_time; + + if (portamento_time > 0) + controllers.portamento_enable_cc = true; + else + controllers.portamento_enable_cc = false; + + controllers.refresh(); +} diff --git a/dexed.h b/dexed.h index 5174f69..3cbac74 100644 --- a/dexed.h +++ b/dexed.h @@ -56,8 +56,8 @@ struct ProcessorVoice { enum DexedEngineResolution { DEXED_ENGINE_MODERN, // 0 - DEXED_ENGINE_MARKI, // 1 - DEXED_ENGINE_OPL // 2 + DEXED_ENGINE_MARKI, // 1 + DEXED_ENGINE_OPL // 2 }; enum DexedVoiceOPParameters { @@ -128,7 +128,9 @@ enum DexedGlobalParameters { DEXED_OP5_ENABLE, // 15 DEXED_OP6_ENABLE, // 16 DEXED_MAX_NOTES, // 17 - DEXED_VOICE_VOLUME // 18 + DEXED_PORTAMENTO_MODE, // 18 + DEXED_PORTAMENTO_GLISSANDO, // 19 + DEXED_PORTAMENTO_TIME, // 20 }; // GLOBALS @@ -168,12 +170,13 @@ class Dexed void setFCController(uint8_t fc_range, uint8_t fc_assign); void setBCController(uint8_t bc_range, uint8_t bc_assign); void setATController(uint8_t at_range, uint8_t pb_assign); + void setPortamentoMode(uint8_t portamento_mode, uint8_t portamento_glissando, uint8_t portamento_time); ProcessorVoice voices[MAX_NOTES]; Controllers controllers; PluginFx fx; - uint8_t data[173] = { + uint8_t data[176] = { 95, 29, 20, 50, 99, 95, 00, 00, 41, 00, 19, 00, 00, 03, 00, 06, 79, 00, 01, 00, 14, // OP6 eg_rate_1-4, level_1-4, kbd_lev_scl_brk_pt, kbd_lev_scl_lft_depth, kbd_lev_scl_rht_depth, kbd_lev_scl_lft_curve, kbd_lev_scl_rht_curve, kbd_rate_scaling, amp_mod_sensitivity, key_vel_sensitivity, operator_output_level, osc_mode, osc_freq_coarse, osc_freq_fine, osc_detune 95, 20, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 00, 99, 00, 01, 00, 00, // OP5 95, 29, 20, 50, 99, 95, 00, 00, 00, 00, 00, 00, 00, 03, 00, 06, 89, 00, 01, 00, 07, // OP4 @@ -188,10 +191,13 @@ class Dexed 01, 00, 99, 00, 99, 00, 99, 00, 99, 00, // pitch_bend_range, pitch_bend_step, mod_wheel_range, mod_wheel_assign, foot_ctrl_range, foot_ctrl_assign, breath_ctrl_range, breath_ctrl_assign, aftertouch_range, aftertouch_assign 00, // master tune 01, 01, 01, 01, 01, 01, // OP1-6 enable + 00, 00, 00, // portamento_mode, portamento_glissando, portamento_time MAX_NOTES // number of voices }; // FM-Piano uint32_t overload = 0; + int lastKeyDown; + protected: static const uint8_t MAX_ACTIVE_NOTES = MAX_NOTES; uint8_t max_notes = MAX_ACTIVE_NOTES; diff --git a/dx7note.cpp b/dx7note.cpp index 49d6114..0124ee5 100644 --- a/dx7note.cpp +++ b/dx7note.cpp @@ -19,6 +19,7 @@ #include #include "synth.h" #include "freqlut.h" +#include "porta.h" #include "exp2.h" #include "controllers.h" #include "dx7note.h" @@ -147,7 +148,7 @@ Dx7Note::Dx7Note() { } //void Dx7Note::init(const uint8_t patch[156], int midinote, int velocity) { -void Dx7Note::init(const uint8_t patch[173], int midinote, int velocity) { +void Dx7Note::init(const uint8_t patch[173], int midinote, int velocity, int srcnote, int porta) { int rates[4]; int levels[4]; for (int op = 0; op < 6; op++) { @@ -176,6 +177,9 @@ void Dx7Note::init(const uint8_t patch[173], int midinote, int velocity) { opMode[op] = mode; basepitch_[op] = freq; ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3]; + + if (porta >= 0) + porta_curpitch_[op] = osc_freq(srcnote, mode, coarse, fine, detune); } for (int i = 0; i < 4; i++) { rates[i] = patch[126 + i]; @@ -188,6 +192,7 @@ void Dx7Note::init(const uint8_t patch[173], int midinote, int velocity) { pitchmoddepth_ = (patch[139] * 165) >> 6; pitchmodsens_ = pitchmodsenstab[patch[143] & 7]; ampmoddepth_ = (patch[140] * 165) >> 6; + porta_rateindex_ = (porta < 128) ? porta : 127; } void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Controllers *ctrls) { @@ -236,10 +241,15 @@ void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Co } else { //int32_t gain = pow(2, 10 + level * (1.0 / (1 << 24))); + int32_t basepitch = basepitch_[op]; + if ( opMode[op] ) - params_[op].freq = Freqlut::lookup(basepitch_[op] + pitch_base); - else - params_[op].freq = Freqlut::lookup(basepitch_[op] + pitch_mod); + params_[op].freq = Freqlut::lookup(basepitch + pitch_base); + else { + if ( porta_rateindex_ >= 0 ) + basepitch = porta_curpitch_[op]; + params_[op].freq = Freqlut::lookup(basepitch + pitch_mod); + } int32_t level = env_[op].getsample(); if (ampmodsens_[op] != 0) { @@ -254,6 +264,25 @@ void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Co params_[op].level_in = level; } } + + // ==== PORTAMENTO ==== + int porta = porta_rateindex_; + if ( porta >= 0 ) { + int32_t rate = Porta::rates[porta]; + for (int op = 0; op < 6; op++) { + int32_t cur = porta_curpitch_[op]; + int32_t dst = basepitch_[op]; + + bool going_up = cur < dst; + int32_t newpitch = cur + (going_up ? +rate : -rate); + + if ( going_up ? (cur > dst) : (cur < dst) ) + newpitch = dst; + + porta_curpitch_[op] = newpitch; + } + } + ctrls->core->render(buf, params_, algorithm_, fb_buf_, fb_shift_); } diff --git a/dx7note.h b/dx7note.h index e221d40..508b418 100644 --- a/dx7note.h +++ b/dx7note.h @@ -1,19 +1,19 @@ /* - * Copyright 2016-2017 Pascal Gauthier. - * Copyright 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ + Copyright 2016-2017 Pascal Gauthier. + Copyright 2012 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ #ifndef SYNTH_DX7NOTE_H_ #define SYNTH_DX7NOTE_H_ @@ -30,36 +30,36 @@ #include "fm_core.h" struct VoiceStatus { - uint32_t amp[6]; - char ampStep[6]; - char pitchStep; + uint32_t amp[6]; + char ampStep[6]; + char pitchStep; }; class Dx7Note { -public: + public: Dx7Note(); - void init(const uint8_t patch[156], int midinote, int velocity); - + void init(const uint8_t patch[156], int midinote, int velocity, int srcnote, int porta); + // Note: this _adds_ to the buffer. Interesting question whether it's // worth it... void compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Controllers *ctrls); - + void keyup(); - + // TODO: some way of indicating end-of-note. Maybe should be a return // value from the compute method? (Having a count return from keyup // is also tempting, but if there's a dynamic parameter change after // keyup, that won't work. - + // PG:add the update void update(const uint8_t patch[156], int midinote, int velocity); void peekVoiceStatus(VoiceStatus &status); void transferState(Dx7Note& src); void transferSignal(Dx7Note &src); void oscSync(); - -private: + + private: Env env_[6]; FmOpParams params_[6]; PitchEnv pitchenv_; @@ -68,11 +68,14 @@ private: int32_t fb_shift_; int32_t ampmodsens_[6]; int32_t opMode[6]; - + int ampmoddepth_; int algorithm_; int pitchmoddepth_; int pitchmodsens_; + + int porta_rateindex_; + int32_t porta_curpitch_[6]; }; #endif // SYNTH_DX7NOTE_H_ diff --git a/porta.cpp b/porta.cpp new file mode 100644 index 0000000..059c06b --- /dev/null +++ b/porta.cpp @@ -0,0 +1,35 @@ +/* + Copyright 2019 Jean Pierre Cimalando. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include +#include "porta.h" +#include "synth.h" + +void Porta::init_sr(double sampleRate) +{ + // compute portamento for CC 7-bit range + + for (unsigned int i = 0; i < 128; ++i) { + // number of semitones travelled + double sps = 350.0 * pow(2.0, -0.062 * i); // per second + double spf = sps / sampleRate; // per frame + double spp = spf * _N_; // per period + const int step = (1 << 24) / 12; + rates[i] = (int32_t)(0.5f + step * spp); // to pitch units + } +} + +int32_t Porta::rates[128]; diff --git a/porta.h b/porta.h new file mode 100644 index 0000000..d8a6b8c --- /dev/null +++ b/porta.h @@ -0,0 +1,28 @@ +/* + Copyright 2019 Jean Pierre Cimalando. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef SYNTH_PORTA_H_ +#define SYNTH_PORTA_H_ + +#include + +struct Porta { + public: + static void init_sr(double sampleRate); + static int32_t rates[128]; +}; + +#endif // SYNTH_PORTA_H_