/* MicroDexed MicroDexed is a port of the Dexed sound engine (https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6 with audio shield. Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android (c)2018,2019 H. Wirtz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef ENABLE_LCD_UI #ifndef _UI_HPP_ #define _UI_HPP_ #include "config.h" #include "LiquidCrystalPlus_I2C.h" #include #include #define _LCDML_DISP_cols LCD_cols #define _LCDML_DISP_rows LCD_rows #define _LCDML_DISP_cfg_cursor 0x7E // cursor Symbol #define _LCDML_DISP_cfg_scrollbar 1 // enable a scrollbar extern config_t configuration; //void set_volume(float v, float p); extern char bank_names[MAX_BANKS][BANK_NAME_LEN]; extern char bank_name[BANK_NAME_LEN]; extern char voice_name[VOICE_NAME_LEN]; extern char voice_names[MAX_VOICES][VOICE_NAME_LEN]; extern void strip_extension(char* s, char *target); extern void eeprom_write(void); extern bool get_voice_names_from_bank(uint8_t b); extern bool load_sysex(uint8_t b, uint8_t v); extern value_change_t soften_volume; extern value_change_t soften_filter_res; extern value_change_t soften_filter_cut; /*********************************************************************** GLOBAL ************************************************************************/ elapsedMillis back_from_volume; LiquidCrystalPlus_I2C lcd(LCD_I2C_ADDRESS, _LCDML_DISP_cols, _LCDML_DISP_rows); const uint8_t scroll_bar[5][8] = { {B10001, B10001, B10001, B10001, B10001, B10001, B10001, B10001}, // scrollbar top {B11111, B11111, B10001, B10001, B10001, B10001, B10001, B10001}, // scroll state 1 {B10001, B10001, B11111, B11111, B10001, B10001, B10001, B10001}, // scroll state 2 {B10001, B10001, B10001, B10001, B11111, B11111, B10001, B10001}, // scroll state 3 {B10001, B10001, B10001, B10001, B10001, B10001, B11111, B11111} // scrollbar bottom }; enum { ENC_R, ENC_L }; enum { MENU_START, MENU_VOICE, MENU_EDIT, MENU_VOLUME }; enum {MENU_VOICE_BANK, MENU_VOICE_SOUND}; uint8_t menu_state = MENU_START; uint8_t menu_voice = MENU_VOICE_SOUND; void lcdml_menu_display(void); void lcdml_voice_menu_display(void); void lcdml_menu_clear(void); void lcdml_menu_control(void); void encoder_right_up(void); void encoder_right_down(void); void encoder_right_button_long(void); void encoder_right_button_short(void); void encoder_left_up(void); void encoder_left_down(void); void encoder_left_button_long(void); void encoder_left_button_short(void); void lcdml_voice_menu_control(void); void UI_func_sound(uint8_t param); void UI_func_reverb_roomsize(uint8_t param); void UI_func_reverb_damping(uint8_t param); void UI_func_reverb_level(uint8_t param); void UI_func_chorus_frequency(uint8_t param); void UI_func_chorus_depth(uint8_t param); void UI_func_chorus_level(uint8_t param); void UI_func_delay_time(uint8_t param); void UI_func_delay_feedback(uint8_t param); void UI_func_delay_level(uint8_t param); void UI_func_filter_cutoff(uint8_t param); void UI_func_filter_resonance(uint8_t param); void UI_func_midi_channel(uint8_t param); void UI_func_loudness(uint8_t param); void UI_func_panorama(uint8_t param); void UI_func_stereo_mono(uint8_t param); void UI_func_polyphony(uint8_t param); void UI_func_engine(uint8_t param); void UI_func_information(uint8_t param); void UI_func_voice_selection(uint8_t param); void UI_func_volume(uint8_t param); void UI_func_back(uint8_t param); void UI_func_goToRootMenu(uint8_t param); // normal menu LCDMenuLib2_menu LCDML_0(255, 0, 0, NULL, NULL); // normal root menu element (do not change) LCDMenuLib2 LCDML(LCDML_0, _LCDML_DISP_rows, _LCDML_DISP_cols, lcdml_menu_display, lcdml_menu_clear, lcdml_menu_control); // LCDML_add(id, prev_layer, new_num, lang_char_array, callback_function) LCDML_add(0, LCDML_0, 1, "Sound", UI_func_sound); LCDML_add(1, LCDML_0, 2, "Effect", NULL); LCDML_add(2, LCDML_0_2, 1, "Reverb", NULL); LCDML_add(3, LCDML_0_2_1, 1, "Roomsize", UI_func_reverb_roomsize); LCDML_add(4, LCDML_0_2_1, 2, "Damping", UI_func_reverb_damping); LCDML_add(5, LCDML_0_2_1, 3, "Level", UI_func_reverb_level); LCDML_add(6, LCDML_0_2, 2, "Chorus", NULL); LCDML_add(7, LCDML_0_2_2, 1, "Frequency", UI_func_chorus_frequency); LCDML_add(8, LCDML_0_2_2, 2, "Depth", UI_func_chorus_depth); LCDML_add(9, LCDML_0_2_2, 3, "Level", UI_func_chorus_level); LCDML_add(10, LCDML_0_2, 3, "Delay", NULL); LCDML_add(11, LCDML_0_2_3, 1, "Time", UI_func_delay_time); LCDML_add(12, LCDML_0_2_3, 2, "Feedback", UI_func_delay_feedback); LCDML_add(13, LCDML_0_2_3, 3, "Level", UI_func_delay_level); LCDML_add(14, LCDML_0_2, 4, "Filter", NULL); LCDML_add(15, LCDML_0_2_4, 1, "Cutoff", UI_func_filter_cutoff); LCDML_add(16, LCDML_0_2_4, 2, "Resonance", UI_func_filter_resonance); LCDML_add(17, LCDML_0, 3, "Store", NULL); LCDML_add(18, LCDML_0, 4, "System", NULL); LCDML_add(19, LCDML_0_4, 1, "MIDI Channel", UI_func_midi_channel); LCDML_add(20, LCDML_0_4, 2, "Loudness", UI_func_loudness); LCDML_add(21, LCDML_0_4, 3, "Panorama", UI_func_panorama); LCDML_add(22, LCDML_0_4, 4, "Stereo/Mono", UI_func_stereo_mono); LCDML_add(23, LCDML_0_4, 5, "Polyphony", UI_func_polyphony); LCDML_add(24, LCDML_0_4, 6, "Engine", UI_func_engine); LCDML_add(25, LCDML_0, 5, "Info", UI_func_information); #define _LCDML_DISP_cnt 25 // create menu LCDML_createMenu(_LCDML_DISP_cnt); /*********************************************************************** CONTROL ***********************************************************************/ #define g_LCDML_CONTROL_button_long_press LONG_BUTTON_PRESS #define g_LCDML_CONTROL_button_short_press BUT_DEBOUNCE_MS //#define ENCODER_OPTIMIZE_INTERRUPTS //Only when using pin2/3 (or 20/21 on mega) Encoder ENCODER[NUM_ENCODER] = {Encoder(ENC_R_PIN_B, ENC_R_PIN_A), Encoder(ENC_L_PIN_B, ENC_L_PIN_A)}; long g_LCDML_CONTROL_button_press_time[NUM_ENCODER] = {0, 0}; bool g_LCDML_CONTROL_button_prev[NUM_ENCODER] = {HIGH, HIGH}; uint8_t g_LCDML_CONTROL_prev[NUM_ENCODER] = {0, 0}; void lcdml_menu_control(void) { // If something must init, put in in the setup condition if (LCDML.BT_setup()) { pinMode(BUT_R_PIN, INPUT_PULLUP); pinMode(BUT_L_PIN, INPUT_PULLUP); } if (back_from_volume > BACK_FROM_VOLUME_MS && menu_state == MENU_VOLUME) { UI_func_voice_selection(0); return; } //Volatile Variable long g_LCDML_CONTROL_Encoder_position[NUM_ENCODER] = {ENCODER[ENC_R].read(), ENCODER[ENC_L].read()}; bool button[NUM_ENCODER] = {digitalRead(BUT_R_PIN), digitalRead(BUT_L_PIN)}; /************************************************************************************ Basic encoder handlying (from LCDMenuLib2 ************************************************************************************/ // RIGHT if (g_LCDML_CONTROL_Encoder_position[ENC_R] <= -3) { if (!button[ENC_R]) { LCDML.BT_left(); g_LCDML_CONTROL_button_prev[ENC_R] = LOW; g_LCDML_CONTROL_button_press_time[ENC_R] = -1; } else { encoder_right_up(); } ENCODER[ENC_R].write(g_LCDML_CONTROL_Encoder_position[ENC_R] + 4); } else if (g_LCDML_CONTROL_Encoder_position[ENC_R] >= 3) { if (!button[ENC_R]) { LCDML.BT_right(); g_LCDML_CONTROL_button_prev[ENC_R] = LOW; g_LCDML_CONTROL_button_press_time[ENC_R] = -1; } else { encoder_right_down(); } ENCODER[ENC_R].write(g_LCDML_CONTROL_Encoder_position[ENC_R] - 4); } else { if (!button[ENC_R] && g_LCDML_CONTROL_button_prev[ENC_R]) //falling edge, button[ENC_R] pressed { g_LCDML_CONTROL_button_prev[ENC_R] = LOW; g_LCDML_CONTROL_button_press_time[ENC_R] = millis(); } else if (button[ENC_R] && !g_LCDML_CONTROL_button_prev[ENC_R]) //rising edge, button[ENC_R] not active { g_LCDML_CONTROL_button_prev[ENC_R] = HIGH; if (g_LCDML_CONTROL_button_press_time[ENC_R] < 0) { g_LCDML_CONTROL_button_press_time[ENC_R] = millis(); //Reset for left right action } else if ((millis() - g_LCDML_CONTROL_button_press_time[ENC_R]) >= g_LCDML_CONTROL_button_long_press) { LCDML.BT_quit(); if (menu_state == MENU_EDIT) { LCDML.BT_quit(); } else if (menu_state == MENU_VOICE) { encoder_right_button_long(); } } else if ((millis() - g_LCDML_CONTROL_button_press_time[ENC_R]) >= g_LCDML_CONTROL_button_short_press) { encoder_right_button_short(); } } } // LEFT if (g_LCDML_CONTROL_Encoder_position[ENC_L] <= -3) { if (!button[ENC_L]) { //LCDML.BT_left(); g_LCDML_CONTROL_button_prev[ENC_L] = LOW; g_LCDML_CONTROL_button_press_time[ENC_L] = -1; } else { encoder_left_up(); } ENCODER[ENC_L].write(g_LCDML_CONTROL_Encoder_position[ENC_L] + 4); } else if (g_LCDML_CONTROL_Encoder_position[ENC_L] >= 3) { if (!button[ENC_L]) { //LCDML.BT_right(); g_LCDML_CONTROL_button_prev[ENC_L] = LOW; g_LCDML_CONTROL_button_press_time[ENC_L] = -1; } else { encoder_left_down(); } ENCODER[ENC_L].write(g_LCDML_CONTROL_Encoder_position[ENC_L] - 4); } else { if (!button[ENC_L] && g_LCDML_CONTROL_button_prev[ENC_L]) //falling edge, button[ENC_L] pressed { g_LCDML_CONTROL_button_prev[ENC_L] = LOW; g_LCDML_CONTROL_button_press_time[ENC_L] = millis(); } else if (button[ENC_L] && !g_LCDML_CONTROL_button_prev[ENC_L]) //rising edge, button[ENC_L] not active { g_LCDML_CONTROL_button_prev[ENC_L] = HIGH; if (g_LCDML_CONTROL_button_press_time[ENC_L] < 0) { g_LCDML_CONTROL_button_press_time[ENC_L] = millis(); //Reset for left right action } else if ((millis() - g_LCDML_CONTROL_button_press_time[ENC_L]) >= g_LCDML_CONTROL_button_long_press) { //LCDML.BT_quit(); encoder_left_button_long(); } else if ((millis() - g_LCDML_CONTROL_button_press_time[ENC_L]) >= g_LCDML_CONTROL_button_short_press) { //LCDML.BT_enter(); encoder_left_button_short(); } } } } /************************************************************************************ RIGHT Encoder functions ************************************************************************************/ void encoder_right_up(void) { switch (menu_state) { case MENU_EDIT: case MENU_VOLUME: menu_state = MENU_EDIT; LCDML.BT_down(); break; case MENU_VOICE: #ifdef DEBUG Serial.println(F("State: MENU_VOICE, Encoder left up")); #endif switch (menu_voice) { case MENU_VOICE_BANK: if (configuration.bank < MAX_BANKS) { configuration.bank++; load_sysex(configuration.bank, configuration.voice); get_voice_names_from_bank(configuration.bank); } break; case MENU_VOICE_SOUND: if (configuration.voice < MAX_VOICES-1) configuration.voice++; else { if (configuration.bank < MAX_BANKS) { configuration.bank++; configuration.voice = 0; } } load_sysex(configuration.bank, configuration.voice); get_voice_names_from_bank(configuration.bank); eeprom_write(); } UI_func_voice_selection(0); break; } } void encoder_right_down(void) { switch (menu_state) { case MENU_EDIT: case MENU_VOLUME: menu_state = MENU_EDIT; LCDML.BT_up(); break; case MENU_VOICE: #ifdef DEBUG Serial.println(F("State: MENU_VOICE, Encoder left down")); #endif switch (menu_voice) { case MENU_VOICE_BANK: if (configuration.bank > 0) { configuration.bank--; load_sysex(configuration.bank, configuration.voice); get_voice_names_from_bank(configuration.bank); } break; case MENU_VOICE_SOUND: if (configuration.voice > 0) configuration.voice--; else { if (configuration.bank > 0) { configuration.bank--; configuration.voice = 31; } } load_sysex(configuration.bank, configuration.voice); get_voice_names_from_bank(configuration.bank); eeprom_write(); } UI_func_voice_selection(0); break; } } void encoder_right_button_long(void) { #ifdef DEBUG Serial.println(F("State: MENU_VOICE, button long press")); #else ; #endif } void encoder_right_button_short(void) { #ifdef DEBUG Serial.println(F("Encoder right short press")); #endif if (menu_state == MENU_EDIT) { LCDML.BT_enter(); } else if (menu_state == MENU_VOICE) { #ifdef DEBUG Serial.println(F("State: MENU_VOICE, button short press")); #endif if (menu_voice == MENU_VOICE_BANK) menu_voice = MENU_VOICE_SOUND; else menu_voice = MENU_VOICE_BANK; UI_func_voice_selection(0); } } /************************************************************************************ LEFT Encoder functions ************************************************************************************/ void encoder_left_up(void) { #ifdef DEBUG Serial.println(F("Volume +")); #endif if (configuration.vol < 0.96) { soften_volume.diff = 0.05 / SOFTEN_VALUE_CHANGE_STEPS; soften_volume.steps = SOFTEN_VALUE_CHANGE_STEPS; eeprom_write(); } UI_func_volume(0); } void encoder_left_down(void) { #ifdef DEBUG Serial.println(F("Volume -")); #endif if (configuration.vol > 0.04) { soften_volume.diff = -0.05 / SOFTEN_VALUE_CHANGE_STEPS; soften_volume.steps = SOFTEN_VALUE_CHANGE_STEPS; eeprom_write(); } UI_func_volume(0); } void encoder_left_button_long(void) { #ifdef DEBUG Serial.println(F("Encoder left long press")); #else ; #endif } void encoder_left_button_short(void) { #ifdef DEBUG Serial.println(F("Encoder left short press")); #endif if (menu_state == MENU_EDIT) { menu_state = MENU_VOICE; UI_func_voice_selection(0); } else if (menu_state == MENU_VOICE) { menu_state = MENU_EDIT; LCDML.MENU_goRoot(); } } /*********************************************************************** DISPLAY ***********************************************************************/ void lcdml_menu_clear(void) { lcd.clear(); lcd.setCursor(0, 0); } void lcdml_menu_display(void) { // update content // *************** if (LCDML.DISP_checkMenuUpdate()) { // clear menu // *************** LCDML.DISP_clear(); // declaration of some variables // *************** // content variable char content_text[_LCDML_DISP_cols]; // save the content text of every menu element // menu element object LCDMenuLib2_menu *tmp; // some limit values uint8_t i = LCDML.MENU_getScroll(); uint8_t maxi = _LCDML_DISP_rows + i; uint8_t n = 0; // check if this element has children if ((tmp = LCDML.MENU_getDisplayedObj()) != NULL) { // loop to display lines do { // check if a menu element has a condition and if the condition be true if (tmp->checkCondition()) { // check the type off a menu element if (tmp->checkType_menu() == true) { // display normal content LCDML_getContent(content_text, tmp->getID()); lcd.setCursor(1, n); lcd.print(content_text); } else { if (tmp->checkType_dynParam()) { tmp->callback(n); } } // increment some values i++; n++; } // try to go to the next sibling and check the number of displayed rows } while (((tmp = tmp->getSibling(1)) != NULL) && (i < maxi)); } } if (LCDML.DISP_checkMenuCursorUpdate()) { // init vars uint8_t n_max = (LCDML.MENU_getChilds() >= _LCDML_DISP_rows) ? _LCDML_DISP_rows : (LCDML.MENU_getChilds()); uint8_t scrollbar_min = 0; uint8_t scrollbar_max = LCDML.MENU_getChilds(); uint8_t scrollbar_cur_pos = LCDML.MENU_getCursorPosAbs(); uint8_t scroll_pos = ((1.*n_max * _LCDML_DISP_rows) / (scrollbar_max - 1) * scrollbar_cur_pos); // display rows for (uint8_t n = 0; n < n_max; n++) { //set cursor lcd.setCursor(0, n); //set cursor char if (n == LCDML.MENU_getCursorPos()) { lcd.write(_LCDML_DISP_cfg_cursor); } else { lcd.write(' '); } // delete or reset scrollbar if (_LCDML_DISP_cfg_scrollbar == 1) { if (scrollbar_max > n_max) { lcd.setCursor((_LCDML_DISP_cols - 1), n); lcd.write((uint8_t)0); } else { lcd.setCursor((_LCDML_DISP_cols - 1), n); lcd.print(' '); } } } // display scrollbar if (_LCDML_DISP_cfg_scrollbar == 1) { if (scrollbar_max > n_max) { //set scroll position if (scrollbar_cur_pos == scrollbar_min) { // min pos lcd.setCursor((_LCDML_DISP_cols - 1), 0); lcd.write((uint8_t)1); } else if (scrollbar_cur_pos == (scrollbar_max - 1)) { // max pos lcd.setCursor((_LCDML_DISP_cols - 1), (n_max - 1)); lcd.write((uint8_t)4); } else { // between lcd.setCursor((_LCDML_DISP_cols - 1), scroll_pos / n_max); lcd.write((uint8_t)(scroll_pos % n_max) + 1); } } } } } /*********************************************************************** MENU ***********************************************************************/ void UI_func_sound(uint8_t param) { if (LCDML.FUNC_setup()) // ****** SETUP ********* { // setup function lcd.setCursor(0, 0); lcd.print(F("Filter Res.")); lcd.setCursor(0, 1); lcd.print(F("")); } if (LCDML.FUNC_loop()) // ****** LOOP ********* { if (LCDML.BT_checkEnter()) { // check if any button is pressed (enter, up, down, left, right) // LCDML_goToMenu stops a running menu function and goes to the menu LCDML.FUNC_goBackToMenu(); } } if (LCDML.FUNC_close()) // ****** STABLE END ********* { // you can here reset some global vars or do nothing } } void UI_func_reverb_roomsize(uint8_t param) { ; } void UI_func_reverb_damping(uint8_t param) { ; } void UI_func_reverb_level(uint8_t param) { ; } void UI_func_chorus_frequency(uint8_t param) { ; } void UI_func_chorus_depth(uint8_t param) { ; } void UI_func_chorus_level(uint8_t param) { ; } void UI_func_delay_time(uint8_t param) { ; } void UI_func_delay_feedback(uint8_t param) { ; } void UI_func_delay_level(uint8_t param) { ; } void UI_func_filter_cutoff(uint8_t param) { ; } void UI_func_filter_resonance(uint8_t param) { ; } void UI_func_midi_channel(uint8_t param) { ; } void UI_func_loudness(uint8_t param) { ; } void UI_func_panorama(uint8_t param) { ; } void UI_func_stereo_mono(uint8_t param) { ; } void UI_func_polyphony(uint8_t param) { ; } void UI_func_engine(uint8_t param) { ; } void UI_func_information(uint8_t param) { if (LCDML.FUNC_setup()) // ****** SETUP ********* { // setup function lcd.setCursor(0, 0); lcd.print(F("MicroDexed")); lcd.setCursor(0, 1); lcd.print(VERSION); } if (LCDML.FUNC_loop()) // ****** LOOP ********* { // loop function, can be run in a loop when LCDML_DISP_triggerMenu(xx) is set // the quit button works in every DISP function without any checks; it starts the loop_end function if (LCDML.BT_checkEnter()) { // check if any button is pressed (enter, up, down, left, right) // LCDML_goToMenu stops a running menu function and goes to the menu LCDML.FUNC_goBackToMenu(); } } if (LCDML.FUNC_close()) // ****** STABLE END ********* { // you can here reset some global vars or do nothing } } void UI_func_voice_selection(uint8_t param) { #ifdef DEBUG Serial.println(F("UI_func_voice_selection()")); #endif menu_state = MENU_VOICE; strip_extension(bank_names[configuration.bank], bank_name); //LCDML.DISP_clear(); // display bank and voice number lcd.show(0, 0, 2, configuration.bank); lcd.show(1, 0, 2, configuration.voice + 1); // display names lcd.show(0, 4, 10, bank_name); lcd.show(1, 4, 10, voice_names[configuration.voice]); // display selections switch (menu_voice) { case MENU_VOICE_BANK: lcd.show(0, 2, 2, " ["); lcd.show(0, 14, 2, "] "); lcd.show(1, 2, 2, " "); lcd.show(1, 14, 2, " "); break; case MENU_VOICE_SOUND: lcd.show(0, 2, 2, " "); lcd.show(0, 14, 2, " "); lcd.show(1, 2, 2, " ["); lcd.show(1, 14, 2, "] "); break; } } void UI_func_volume(uint8_t param) { #ifdef DEBUG Serial.println(F("UI_func_volume()")); #endif menu_state = MENU_VOLUME; back_from_volume = 0; // update LCD content LCDML.DISP_clear(); lcd.show(0, 0, 8, "Volume: "); lcd.show(0, 9, 3, configuration.vol * 100.0 + 0.5); lcd.setCursor(1, 1); for (uint8_t i = 0; i < LCD_cols; i++) { if (i < int((LCD_cols - 2) * configuration.vol + 0.5)) lcd.print("*"); else lcd.print(" "); } lcd.show(1, 0, 1, "["); lcd.show(1, 15, 1, "]"); } #endif #endif