diff --git a/MicroDexed.ino b/MicroDexed.ino index 02b6610..5dfccdb 100644 --- a/MicroDexed.ino +++ b/MicroDexed.ino @@ -23,18 +23,21 @@ */ #include "config.h" +#include #include #include #include #include #include #include +#include +#include #include "EEPROMAnything.h" #include "midi_devices.hpp" -#include #include "dexed.h" #include "dexed_sysex.h" #include "PluginFx.h" +#include "UI.hpp" AudioPlayQueue queue1; AudioAnalyzePeak peak1; @@ -105,13 +108,19 @@ value_change_t soften_volume = {0.0, 0}; value_change_t soften_filter_res = {0.0, 0}; value_change_t soften_filter_cut = {0.0, 0}; +/*********************************************************************** + LCDMenuLib2 + ***********************************************************************/ +extern LiquidCrystal_I2C lcd; +extern LCDMenuLib2 LCDML; + void setup() { //while (!Serial) ; // wait for Arduino Serial Monitor Serial.begin(SERIAL_SPEED); delay(220); - + Serial.println(F("MicroDexed based on https://github.com/asb2m10/dexed")); Serial.println(F("(c)2018,2019 H. Wirtz ")); Serial.println(F("https://codeberg.org/dcoredump/MicroDexed")); @@ -247,6 +256,34 @@ void setup() #endif AudioInterrupts(); + + // LCD Begin + lcd.init(); + lcd.backlight(); + lcd.clear(); + lcd.blink_off(); + lcd.cursor_off(); + lcd.backlight(); + lcd.setCursor(1, 0); + lcd.print("MicroDexed"); + lcd.setCursor(0, 1); + lcd.print("(c)parasiTstudio"); + delay(1000); + + // set special chars for scrollbar + lcd.createChar(0, (uint8_t*)scroll_bar[0]); + lcd.createChar(1, (uint8_t*)scroll_bar[1]); + lcd.createChar(2, (uint8_t*)scroll_bar[2]); + lcd.createChar(3, (uint8_t*)scroll_bar[3]); + lcd.createChar(4, (uint8_t*)scroll_bar[4]); + + // LCDMenuLib Setup + LCDML_setup(_LCDML_DISP_cnt); + // Enable Menu Rollover + LCDML.MENU_enRollover(); + // Enable Screensaver (screensaver menu function, time to activate in ms) + LCDML.SCREEN_enable(UI_func_screensaver, 10000); // set to 10 seconds + Serial.println(F("")); } @@ -255,101 +292,107 @@ void loop() int16_t* audio_buffer; // pointer to AUDIO_BLOCK_SAMPLES * int16_t const uint16_t audio_block_time_us = 1000000 / (SAMPLE_RATE / AUDIO_BLOCK_SAMPLES); - // Main sound calculation - if (queue1.available() && fill_audio_buffer > audio_block_time_us - 10) + while (42 == 42) { - fill_audio_buffer = 0; - - audio_buffer = queue1.getBuffer(); - - elapsedMicros t1; - dexed->getSamples(AUDIO_BLOCK_SAMPLES, audio_buffer); - if (t1 > audio_block_time_us) // everything greater 2.9ms is a buffer underrun! - xrun++; - if (t1 > render_time_max) - render_time_max = t1; - if (peak1.available()) + // Main sound calculation + if (queue1.available() && fill_audio_buffer > audio_block_time_us - 10) { - if (peak1.read() > 0.99) - peak++; - } + fill_audio_buffer = 0; + + audio_buffer = queue1.getBuffer(); + + elapsedMicros t1; + dexed->getSamples(AUDIO_BLOCK_SAMPLES, audio_buffer); + if (t1 > audio_block_time_us) // everything greater 2.9ms is a buffer underrun! + xrun++; + if (t1 > render_time_max) + render_time_max = t1; + if (peak1.available()) + { + if (peak1.read() > 0.99) + peak++; + } #ifndef TEENSY_AUDIO_BOARD - for (uint8_t i = 0; i < AUDIO_BLOCK_SAMPLES; i++) - audio_buffer[i] *= configuration.vol; + for (uint8_t i = 0; i < AUDIO_BLOCK_SAMPLES; i++) + audio_buffer[i] *= configuration.vol; #endif - queue1.playBuffer(); - } + queue1.playBuffer(); + } - // EEPROM update handling - if (autostore >= AUTOSTORE_MS && active_voices == 0 && eeprom_update_flag == true) - { - // only store configuration data to EEPROM when AUTOSTORE_MS is reached and no voices are activated anymore - eeprom_update(); - } + // EEPROM update handling + if (autostore >= AUTOSTORE_MS && active_voices == 0 && eeprom_update_flag == true) + { + // only store configuration data to EEPROM when AUTOSTORE_MS is reached and no voices are activated anymore + eeprom_update(); + } - // MIDI input handling - check_midi_devices(); + // MIDI input handling + check_midi_devices(); - // CONTROL-RATE-EVENT-HANDLING - if (control_rate > CONTROL_RATE_MS) - { - control_rate = 0; + // CONTROL-RATE-EVENT-HANDLING + if (control_rate > CONTROL_RATE_MS) + { + // LCD Menu + LCDML.loop(); - // Shutdown unused voices - active_voices = dexed->getNumNotesPlaying(); + control_rate = 0; - // check for value changes - if (soften_volume.steps > 0) - { - // soften volume value - soften_volume.steps--; - set_volume(configuration.vol + soften_volume.diff, configuration.pan); + // Shutdown unused voices + active_voices = dexed->getNumNotesPlaying(); + + // check for value changes + if (soften_volume.steps > 0) + { + // soften volume value + soften_volume.steps--; + set_volume(configuration.vol + soften_volume.diff, configuration.pan); #ifdef DEBUG - Serial.print(F("Volume: ")); - Serial.print(configuration.vol, 5); - Serial.print(F(" Volume step: ")); - Serial.print(soften_volume.steps); - Serial.print(F(" Volume diff: ")); - Serial.println(soften_volume.diff, 5); + Serial.print(F("Volume: ")); + Serial.print(configuration.vol, 5); + Serial.print(F(" Volume step: ")); + Serial.print(soften_volume.steps); + Serial.print(F(" Volume diff: ")); + Serial.println(soften_volume.diff, 5); #endif - } - if (soften_filter_res.steps > 0) - { - // soften filter resonance value - soften_filter_res.steps--; - dexed->fx.Reso = dexed->fx.Reso + soften_filter_res.diff; + } + if (soften_filter_res.steps > 0) + { + // soften filter resonance value + soften_filter_res.steps--; + dexed->fx.Reso = dexed->fx.Reso + soften_filter_res.diff; #ifdef DEBUG - Serial.print(F("Filter-Resonance: ")); - Serial.print(dexed->fx.Reso, 5); - Serial.print(F(" Filter-Resonance step: ")); - Serial.print(soften_filter_res.steps); - Serial.print(F(" Filter-Resonance diff: ")); - Serial.println(soften_filter_res.diff, 5); + Serial.print(F("Filter-Resonance: ")); + Serial.print(dexed->fx.Reso, 5); + Serial.print(F(" Filter-Resonance step: ")); + Serial.print(soften_filter_res.steps); + Serial.print(F(" Filter-Resonance diff: ")); + Serial.println(soften_filter_res.diff, 5); #endif - } - if (soften_filter_cut.steps > 0) - { - // soften filter cutoff value - soften_filter_cut.steps--; - dexed->fx.Cutoff = dexed->fx.Cutoff + soften_filter_cut.diff; + } + if (soften_filter_cut.steps > 0) + { + // soften filter cutoff value + soften_filter_cut.steps--; + dexed->fx.Cutoff = dexed->fx.Cutoff + soften_filter_cut.diff; #ifdef DEBUG - Serial.print(F("Filter-Cutoff: ")); - Serial.print(dexed->fx.Cutoff, 5); - Serial.print(F(" Filter-Cutoff step: ")); - Serial.print(soften_filter_cut.steps); - Serial.print(F(" Filter-Cutoff diff: ")); - Serial.println(soften_filter_cut.diff, 5); + Serial.print(F("Filter-Cutoff: ")); + Serial.print(dexed->fx.Cutoff, 5); + Serial.print(F(" Filter-Cutoff step: ")); + Serial.print(soften_filter_cut.steps); + Serial.print(F(" Filter-Cutoff diff: ")); + Serial.println(soften_filter_cut.diff, 5); #endif + } } - } #if defined (DEBUG) && defined (SHOW_CPU_LOAD_MSEC) - if (cpu_mem_millis >= SHOW_CPU_LOAD_MSEC) - { - cpu_mem_millis -= SHOW_CPU_LOAD_MSEC; - show_cpu_and_mem_usage(); - } + if (cpu_mem_millis >= SHOW_CPU_LOAD_MSEC) + { + cpu_mem_millis -= SHOW_CPU_LOAD_MSEC; + show_cpu_and_mem_usage(); + } #endif + } } /****************************************************************************** diff --git a/UI.hpp b/UI.hpp new file mode 100644 index 0000000..22fa64a --- /dev/null +++ b/UI.hpp @@ -0,0 +1,378 @@ +/* + 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 +*/ + +#ifndef _UI_HPP_ +#define _UI_HPP_ + +#include +#include +#include "Arduino.h" + +#define _LCDML_DISP_cols 16 +#define _LCDML_DISP_rows 2 + +#define _LCDML_DISP_cfg_cursor 0x7E // cursor Symbol +#define _LCDML_DISP_cfg_scrollbar 1 // enable a scrollbar + +/*********************************************************************** + GLOBAL + ***********************************************************************/ +LiquidCrystal_I2C lcd(0x27, _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 +}; + +void lcdml_menu_display(void); +void lcdml_menu_clear(void); +void lcdml_menu_control(void); +void UI_func_information(uint8_t param); +void UI_func_screensaver(uint8_t param); +void UI_func_test(uint8_t param); +void UI_func_back(uint8_t param); +void UI_func_goToRootMenu(uint8_t param); + +LCDMenuLib2_menu LCDML_0(255, 0, 0, NULL, NULL); // 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, "Information", UI_func_information); +LCDML_add(1, LCDML_0, 2, "Test", UI_func_test); +LCDML_add(2, LCDML_0, 3, "Screensaver", UI_func_screensaver); +#define _LCDML_DISP_cnt 2 + +// create menu +LCDML_createMenu(_LCDML_DISP_cnt); + +/*********************************************************************** + CONTROL + ***********************************************************************/ +#define g_LCDML_CONTROL_button_long_press 800 // ms +#define g_LCDML_CONTROL_button_short_press 120 // ms + +#define ENCODER_OPTIMIZE_INTERRUPTS //Only when using pin2/3 (or 20/21 on mega) +#include + +Encoder ENCODER(ENC_R_PIN_B, ENC_R_PIN_A); + +long g_LCDML_CONTROL_button_press_time = 0; +bool g_LCDML_CONTROL_button_prev = HIGH; + +void lcdml_menu_control(void) +{ + // If something must init, put in in the setup condition + if (LCDML.BT_setup()) + { + //pinMode(ENC_R_PIN_A, INPUT_PULLUP); + //pinMode(ENC_R_PIN_B, INPUT_PULLUP); + pinMode(BUT_R_PIN, INPUT_PULLUP); + } + + //Volatile Variable + long g_LCDML_CONTROL_Encoder_position = ENCODER.read(); + bool button = digitalRead(BUT_R_PIN); + + if (g_LCDML_CONTROL_Encoder_position <= -3) { + + if (!button) + { + LCDML.BT_left(); + g_LCDML_CONTROL_button_prev = LOW; + g_LCDML_CONTROL_button_press_time = -1; + } + else + { + LCDML.BT_down(); + } + ENCODER.write(g_LCDML_CONTROL_Encoder_position + 4); + } + else if (g_LCDML_CONTROL_Encoder_position >= 3) + { + + if (!button) + { + LCDML.BT_right(); + g_LCDML_CONTROL_button_prev = LOW; + g_LCDML_CONTROL_button_press_time = -1; + } + else + { + LCDML.BT_up(); + } + ENCODER.write(g_LCDML_CONTROL_Encoder_position - 4); + } + else + { + if (!button && g_LCDML_CONTROL_button_prev) //falling edge, button pressed + { + g_LCDML_CONTROL_button_prev = LOW; + g_LCDML_CONTROL_button_press_time = millis(); + } + else if (button && !g_LCDML_CONTROL_button_prev) //rising edge, button not active + { + g_LCDML_CONTROL_button_prev = HIGH; + + if (g_LCDML_CONTROL_button_press_time < 0) + { + g_LCDML_CONTROL_button_press_time = millis(); + //Reset for left right action + } + else if ((millis() - g_LCDML_CONTROL_button_press_time) >= g_LCDML_CONTROL_button_long_press) + { + LCDML.BT_quit(); + } + else if ((millis() - g_LCDML_CONTROL_button_press_time) >= g_LCDML_CONTROL_button_short_press) + { + LCDML.BT_enter(); + } + } + } +} + +/*********************************************************************** + 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_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(F("LCDMenuLib2")); + } + + 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_checkAny()) { // 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_screensaver(uint8_t param) +{ + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + // update LCD content + lcd.setCursor(0, 0); // set cursor + lcd.print("screensaver"); // print change content + lcd.setCursor(0, 1); // set cursor + lcd.print("press any key"); + LCDML.FUNC_setLoopInterval(100); // starts a trigger event for the loop function every 100 milliseconds + } + + if (LCDML.FUNC_loop()) + { + if (LCDML.BT_checkAny()) // check if any button is pressed (enter, up, down, left, right) + { + LCDML.FUNC_goBackToMenu(); // leave this function + } + } + + if (LCDML.FUNC_close()) + { + // The screensaver go to the root menu + LCDML.MENU_goRoot(); + } +} + +void UI_func_test(uint8_t param) +{ + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + // update LCD content + lcd.setCursor(0, 0); // set cursor + lcd.print("TEST"); // print change content + lcd.setCursor(0, 1); // set cursor + lcd.print("TEST123"); + LCDML.FUNC_setLoopInterval(100); // starts a trigger event for the loop function every 100 milliseconds + } + + if (LCDML.FUNC_loop()) + { + if (LCDML.BT_checkAny()) // check if any button is pressed (enter, up, down, left, right) + { + LCDML.FUNC_goBackToMenu(); // leave this function + } + } + + if (LCDML.FUNC_close()) + { + // The screensaver go to the root menu + LCDML.MENU_goRoot(); + } +} + +/*void UI_func_back(uint8_t param) +{ + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + // end function and go an layer back + LCDML.FUNC_goBackToMenu(1); // leave this function and go a layer back + } +}*/ + +void UI_func_goToRootMenu(uint8_t param) +{ + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + // go to root and display menu + LCDML.MENU_goRoot(); + } +} + +#endif diff --git a/config.h b/config.h index aa811d7..750cff9 100644 --- a/config.h +++ b/config.h @@ -38,7 +38,7 @@ // $ aplaymidi -p 20:0 # e.g. test.mid // $ arecord -f cd -Dhw:1,0 /tmp/bla.wav -#define VERSION "0.9.5" +#define VERSION "0.9.6" //************************************************************************************************* //* DEVICE SETTINGS @@ -101,8 +101,7 @@ //************************************************************************************************* //* UI AND DATA-STORE SETTINGS //************************************************************************************************* -#define CONTROL_RATE_MS 50 -#define TIMER_UI_HANDLING_MS 100 +#define CONTROL_RATE_MS 10 //************************************************************************************************* //* DEBUG OUTPUT SETTINGS