From 2365394e7b7c710d8ecca4b5349a12dcb73346eb Mon Sep 17 00:00:00 2001 From: positionhigh Date: Fri, 13 Aug 2021 18:39:45 +0200 Subject: [PATCH 1/5] =?UTF-8?q?Dateien=20hochladen=20nach=20=E2=80=9E?= =?UTF-8?q?=E2=80=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UI_FX.h | 33 +++++++++++----------- UI_NO_FX.h | 33 +++++++++++----------- sequencer.cpp | 78 ++++++++++++++++++++++++++++++--------------------- sequencer.h | 32 ++++++++++++++++----- 4 files changed, 105 insertions(+), 71 deletions(-) diff --git a/UI_FX.h b/UI_FX.h index 534f0de..9be0d1f 100644 --- a/UI_FX.h +++ b/UI_FX.h @@ -121,20 +121,21 @@ LCDML_add(87, LCDML_0, 5, "Sequencer", NULL); LCDML_add(88, LCDML_0_5, 1, "Sequencer", UI_func_sequencer); LCDML_add(89, LCDML_0_5, 2, "Vel./Chrd Edit", UI_func_seq_vel_editor); LCDML_add(90, LCDML_0_5, 3, "Pattern Chain", UI_func_seq_pat_chain); -LCDML_add(91, LCDML_0_5, 4, "Seq. Length", UI_func_seq_lenght); -LCDML_add(92, LCDML_0_5, 5, "Tempo", UI_func_seq_tempo); -LCDML_add(93, LCDML_0_5, 6, "L.Transp.Key", UI_func_seq_live_transpose_oct); -LCDML_add(94, LCDML_0_5, 7, "L.Trp.Offset", NULL); -LCDML_add(95, LCDML_0_5, 8, "Track Setup", UI_func_seq_track_setup); -LCDML_add(96, LCDML_0_5, 9, "Seq.Disp.Style", UI_func_seq_display_style); -LCDML_add(97, LCDML_0_5, 10, "LOAD Patterns", UI_func_seq_pattern_load); -LCDML_add(98, LCDML_0_5, 11, "SAVE Patterns", UI_func_seq_pattern_save); -LCDML_add(99, LCDML_0, 6, "System", NULL); -LCDML_add(100, LCDML_0_6, 1, "Stereo/Mono", UI_func_stereo_mono); -LCDML_add(101, LCDML_0_6, 2, "MIDI Soft THRU", UI_func_midi_soft_thru); -LCDML_add(102, LCDML_0_6, 3, "Favorites", UI_func_favorites); -LCDML_add(103, LCDML_0_6, 4, "EEPROM Reset", UI_func_eeprom_reset); -LCDML_add(104, LCDML_0, 7, "Info", UI_func_information); -LCDML_addAdvanced(105, LCDML_0, 8, COND_hide, "Volume", UI_func_volume, 0, _LCDML_TYPE_default); -#define _LCDML_DISP_cnt 105 +LCDML_add(91, LCDML_0_5, 4, "Arpeggio", UI_func_arpeggio); +LCDML_add(92, LCDML_0_5, 5, "Seq. Length", UI_func_seq_lenght); +LCDML_add(93, LCDML_0_5, 6, "Tempo", UI_func_seq_tempo); +LCDML_add(94, LCDML_0_5, 7, "L.Transp.Key", UI_func_seq_live_transpose_oct); +LCDML_add(95, LCDML_0_5, 8, "L.Trp.Offset", NULL); +LCDML_add(96, LCDML_0_5, 9, "Track Setup", UI_func_seq_track_setup); +LCDML_add(97, LCDML_0_5, 10, "Seq.Disp.Style", UI_func_seq_display_style); +LCDML_add(98, LCDML_0_5, 11, "LOAD Patterns", UI_func_seq_pattern_load); +LCDML_add(99, LCDML_0_5, 12, "SAVE Patterns", UI_func_seq_pattern_save); +LCDML_add(100, LCDML_0, 6, "System", NULL); +LCDML_add(101, LCDML_0_6, 1, "Stereo/Mono", UI_func_stereo_mono); +LCDML_add(102, LCDML_0_6, 2, "MIDI Soft THRU", UI_func_midi_soft_thru); +LCDML_add(103, LCDML_0_6, 3, "Favorites", UI_func_favorites); +LCDML_add(104, LCDML_0_6, 4, "EEPROM Reset", UI_func_eeprom_reset); +LCDML_add(105, LCDML_0, 7, "Info", UI_func_information); +LCDML_addAdvanced(106, LCDML_0, 8, COND_hide, "Volume", UI_func_volume, 0, _LCDML_TYPE_default); +#define _LCDML_DISP_cnt 106 #endif diff --git a/UI_NO_FX.h b/UI_NO_FX.h index 8fc7e9d..bd1e467 100644 --- a/UI_NO_FX.h +++ b/UI_NO_FX.h @@ -91,22 +91,23 @@ LCDML_add(57, LCDML_0_3, 2, "Drum Volumes", UI_func_drum_volume); LCDML_add(58, LCDML_0_3, 3, "Drum Pan", UI_func_drum_pan); LCDML_add(59, LCDML_0, 4, "Sequencer", NULL); LCDML_add(60, LCDML_0_4, 1, "Sequencer", UI_func_sequencer); -LCDML_add(61, LCDML_0_5, 2, "Velocity Edit", UI_func_seq_vel_editor); +LCDML_add(61, LCDML_0_5, 2, "Vel./Chrd Edit", UI_func_seq_vel_editor); LCDML_add(62, LCDML_0_4, 3, "Pattern Chain", UI_func_seq_pat_chain); -LCDML_add(63, LCDML_0_4, 4, "Seq. Length", UI_func_seq_lenght); -LCDML_add(64, LCDML_0_4, 5, "Tempo", UI_func_seq_tempo); -LCDML_add(65, LCDML_0_4, 6, "L.Transp.Key", UI_func_seq_live_transpose_oct); -LCDML_add(66, LCDML_0_5, 7, "Track Setup", UI_func_seq_track_setup); -LCDML_add(67, LCDML_0_5, 8, "Seq.Disp.Style", UI_func_seq_display_style); -LCDML_add(68, LCDML_0_4, 9, "LOAD Patterns", UI_func_seq_pattern_load); -LCDML_add(69, LCDML_0_4, 10, "SAVE Patterns", UI_func_seq_pattern_save); -LCDML_add(70, LCDML_0, 5, "System", NULL); -LCDML_add(71, LCDML_0_5, 1, "Stereo/Mono", UI_func_stereo_mono); -LCDML_add(72, LCDML_0_5, 2, "MIDI Soft THRU", UI_func_midi_soft_thru); -LCDML_add(73, LCDML_0_5, 3, "Favorites", UI_func_favorites); -LCDML_add(74, LCDML_0_5, 4, "EEPROM Reset", UI_func_eeprom_reset); -LCDML_add(75, LCDML_0, 6, "Info", UI_func_information); -LCDML_addAdvanced(76, LCDML_0, 5, COND_hide, "Volume", UI_func_volume, 0, _LCDML_TYPE_default); -#define _LCDML_DISP_cnt 76 +LCDML_add(63, LCDML_0_5, 4, "Arpeggio", UI_func_arpeggio); +LCDML_add(64, LCDML_0_4, 5, "Seq. Length", UI_func_seq_lenght); +LCDML_add(65, LCDML_0_4, 6, "Tempo", UI_func_seq_tempo); +LCDML_add(66, LCDML_0_4, 7, "L.Transp.Key", UI_func_seq_live_transpose_oct); +LCDML_add(67, LCDML_0_5, 8, "Track Setup", UI_func_seq_track_setup); +LCDML_add(68, LCDML_0_5, 9, "Seq.Disp.Style", UI_func_seq_display_style); +LCDML_add(69, LCDML_0_4, 10, "LOAD Patterns", UI_func_seq_pattern_load); +LCDML_add(70, LCDML_0_4, 11, "SAVE Patterns", UI_func_seq_pattern_save); +LCDML_add(71, LCDML_0, 5, "System", NULL); +LCDML_add(72, LCDML_0_5, 1, "Stereo/Mono", UI_func_stereo_mono); +LCDML_add(73, LCDML_0_5, 2, "MIDI Soft THRU", UI_func_midi_soft_thru); +LCDML_add(74, LCDML_0_5, 3, "Favorites", UI_func_favorites); +LCDML_add(75, LCDML_0_5, 4, "EEPROM Reset", UI_func_eeprom_reset); +LCDML_add(76, LCDML_0, 6, "Info", UI_func_information); +LCDML_addAdvanced(77, LCDML_0, 5, COND_hide, "Volume", UI_func_volume, 0, _LCDML_TYPE_default); +#define _LCDML_DISP_cnt 77 #endif diff --git a/sequencer.cpp b/sequencer.cpp index da84662..bad6929 100644 --- a/sequencer.cpp +++ b/sequencer.cpp @@ -10,6 +10,7 @@ extern config_t configuration; extern void handleNoteOn(byte , byte , byte ); extern void handleNoteOff(byte , byte , byte ); extern void UI_func_sequencer(uint8_t); +extern void UI_func_arpeggio(uint8_t); extern const char* seq_find_shortname(uint8_t); void sequencer(void) @@ -27,7 +28,7 @@ void sequencer(void) { seq_timer_previous = seq_millis_timer; - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_sequencer)) { + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_sequencer)) { //is in UI of Sequencer //write to sequencer if in sequencer menu if (seq_note_in > 0 && seq_recording == true) { @@ -51,7 +52,13 @@ void sequencer(void) lcd.print(seq_find_shortname(seq_step - 1)[0]); } } - + else if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_arpeggio)) { //is in UI of Arpeggiator + lcd.setCursor(7, 0); + lcd.print( seq_chord_names[arp_chord - 200][0]); + lcd.print( seq_chord_names[arp_chord - 200][1]); + lcd.print( seq_chord_names[arp_chord - 200][2]); + lcd.print( seq_chord_names[arp_chord - 200][3]); + } for (uint8_t d = 0; d < 4; d++) { if ( seq_track_type[d] == 0) { // drum track @@ -78,46 +85,61 @@ void sequencer(void) handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose + seq_chords[seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] - 200][3], seq_chord_velocity); } } - else if (seq_track_type[d] == 3) { //Arp - arp_step = 0; + arp_step = 0; + arp_counter = 0; arp_note = seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose; arp_chord = seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step]; - } - } } - if (seq_track_type[d] == 3) { //Arp - if (arp_step == 0 ) { - handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note+ arp_octave * 12 , seq_chord_velocity); - arp_note_prev = arp_note+arp_octave * 12; - - } - else - { - handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note + seq_chords[arp_chord - 200][arp_step - 1] + arp_octave * 12, seq_chord_velocity); - arp_note_prev = arp_note + seq_chords[arp_chord - 200][arp_step - 1] + arp_octave * 12; - + if (arp_speed == 0 || (arp_speed == 1 && arp_counter == 2) ) { + if (arp_step == 0 ) { + handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note + arp_octave * 12 , seq_chord_velocity); + arp_note_prev = arp_note + arp_octave * 12; + } + else + { if (arp_style == 0) { //arp up + handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note + seq_chords[arp_chord - 200][arp_step - 1] + arp_octave * 12, seq_chord_velocity); + arp_note_prev = arp_note + seq_chords[arp_chord - 200][arp_step - 1] + arp_octave * 12; + } + else if (arp_style == 3) { //arp random + uint8_t rnd1 = random(4); + uint8_t rnd2 = random(arp_oct_usersetting + 1) * 12; + handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note + seq_chords[arp_chord - 200][rnd1] + rnd2, seq_chord_velocity); + arp_note_prev = arp_note + seq_chords[arp_chord - 200][rnd1] + rnd2; + } + } } } - seq_noteoffsent[d] = false; } seq_step++; - arp_step++; - if (arp_step > 3) { + if (arp_speed == 0) // Arp Speed 1/16 + { + arp_step++; + } + else + { + if (arp_speed == 1) // Arp Speed 1/8 + { + if (arp_counter > 1) { + arp_counter = 0; + arp_step++; + } + arp_counter++; + } + } + if (arp_step > 3 || seq_chords[arp_chord - 200][arp_step] == 0 ) { arp_step = 0; arp_octave++; - if (arp_octave > 1) arp_octave = 0; + if (arp_octave > arp_oct_usersetting) arp_octave = 0; } if (seq_step > 15) { - - seq_step = 0; if (seq_chain_lenght > 0) { seq_chain_active_step++; @@ -135,26 +157,18 @@ void sequencer(void) if ( seq_noteoffsent[d] == false) { if ( seq_prev_note[d] > 0 && seq_track_type[d] > 0) //test instrument sequencer Instance=0 { - handleNoteOff(configuration.dexed[seq_inst_dexed[d]].midi_channel, seq_prev_note[d] , 0); - if (seq_track_type[d] == 2) { //Chords if ( seq_prev_vel[d] > 199) { - handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][0], 0); handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][1] , 0); handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][2] , 0); handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][3] , 0); } - } - - else if (seq_track_type[d] == 3) + else if (seq_track_type[d] == 3) { //Arp - - handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note_prev, 0); - } } } diff --git a/sequencer.h b/sequencer.h index 53cfa84..84a5237 100644 --- a/sequencer.h +++ b/sequencer.h @@ -14,6 +14,7 @@ int seq_transpose; uint8_t seq_inst_dexed[4] = { 1, 1, 1, 1 }; uint8_t seq_chord_dexed_inst = 0; uint8_t seq_chord_velocity = 50; +uint8_t arp_style = 0; // up, down, up&down, random uint8_t seq_chords[6][4] = { 4, 7, 12, 0, //major 3, 7, 12, 0, //minor 4, 7, 10, 12, //seventh @@ -21,12 +22,26 @@ uint8_t seq_chords[6][4] = { 4, 7, 12, 0, //major 3, 6, 12, 0, //dim 4, 7, 11 , 0 //maj7, }; +char seq_chord_names[6][4] = { 'M', 'a', 'j', ' ' , //major + 'M', 'i', 'n', ' ' , + 's', 'e', 'v', ' ' , + 'a', 'u', 'g', ' ' , + 'd', 'i', 'm', ' ' , + 'M', 'a', 'j', '7' , + }; + + +char arp_style_names[4][3] = { 'u', 'p', ' ', + 'd', 'w', 'n', + 'u', '&', 'd', + 'R', 'N', 'D', + }; int seq_tempo_ms = 147; int seq_bpm = 102; uint8_t seq_temp_select_menu; uint8_t seq_temp_active_menu = 99; uint8_t seq_chain_active_chainstep; -uint8_t seq_chain_lenght = 3; // 0=16 steps, 1=32 Steps, 2=46 Steps, 3=64 Steps +uint8_t seq_chain_lenght = 3; // 0 = 16 steps, 1 = 32 Steps, 2 = 46 Steps, 3 = 64 Steps uint8_t seq_chain_active_step = 0; uint8_t seq_prev_note[4]; // note_offs for every (instr.) track uint8_t seq_prev_vel[4]; @@ -36,23 +51,26 @@ uint8_t arp_chord; uint8_t arp_note_prev; uint8_t arp_octave; uint8_t arp_prev_oct; +uint8_t arp_speed = 0; +uint8_t arp_counter = 0; +uint8_t arp_oct_usersetting = 1; uint8_t seq_data[10][16] = {72 , 0 , 0 , 0 , 72 , 0 , 0 , 0 , 72 , 0 , 0 , 0 , 72 , 0 , 0 , 0 , 78 , 78 , 78 , 78 , 78 , 78 , 78 , 78 , 78 , 78 , 78 , 78 , 78 , 78 , 78 , 78 , 72 , 0 , 0 , 0 , 72 , 0 , 0 , 0 , 72 , 0 , 0 , 75 , 72 , 0 , 0 , 0 , 60 , 61 , 62 , 63 , 64 , 65 , 66 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , - 55 , 0 , 0 , 0 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , //c1 - 57 , 0 , 0 , 0 , 0 , 0 , 53 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , //C2 + 55 , 0 , 0 , 0 , 0 , 0 , 52 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , + 57 , 0 , 0 , 0 , 0 , 0 , 53 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 74 , 0 , 0 , 72 , 0 , 0 , 74 , 0 , 0 , 0 , 76 , 0 , 0 , 0 , 0 , 0 , 74 , 0 , 0 , 72 , 0 , 0 , 71 , 0 , 0 , 0 , 67 , 0 , 0 , 0 , 0 , 0 , 69 , 0 , 0 , 76 , 0 , 0 , 69 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }; -uint8_t seq_vel[10][16] = {120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, 0, +uint8_t seq_vel[10][16] = {120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, 0, 105, 80, 105, 70, 106, 98, 106, 70, 126, 97, 106, 70, 106, 99, 90, 65, 120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 120, 50, 120, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 200, 200, 200, 200, 200, 200, 201, 0, 0, 0, 0, 0, 0, 0, 0, 200, 201, 200, 200, 200, 200, 200, 200, 200, 0, 0, 0, 0, 0, 0, 0, 0, 98, 120, 0, 88, 120, 0, 127, 120, 0, 0, 125, 120, 0, 0, 0, 0, @@ -62,8 +80,8 @@ uint8_t seq_vel[10][16] = {120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, uint8_t seq_patternchain[4][4] = { 0 , 1 , 6 , 9 , 0 , 1 , 5 , 8 , 0 , 1 , 6 , 9 , 2 , 1 , 5 , 7 }; -uint8_t seq_content_type[10] = { 0, 0, 0, 0 , 1, 2, 2 , 1 , 1 , 1 }; // 0 = track is Drumtrack, 1= Instrumenttrack, 2= Chord, 3= Arpeggio -uint8_t seq_track_type[4] = { 0, 0, 3, 1 }; // 0 = track is Drumtrack, 1= Instrumenttrack, 2= Chord, 3=Arp, 4=Chord+Arp +uint8_t seq_content_type[10] = { 0, 0, 0, 0 , 1, 2, 2 , 1 , 1 , 1 }; // 0 = track is Drumtrack, 1= Instrumenttrack, 2= Chord or Arpeggio +uint8_t seq_track_type[4] = { 0, 0, 3, 1 }; // 0 = track is Drumtrack, 1 = Instrumenttrack, 2 = Chord, 3 = Arp //uint8_t seq_reverb[4][16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, From f707082624a850cb5458b65aeffebf51010e392e Mon Sep 17 00:00:00 2001 From: positionhigh Date: Fri, 13 Aug 2021 18:45:06 +0200 Subject: [PATCH 3/5] =?UTF-8?q?Dateien=20hochladen=20nach=20=E2=80=9E?= =?UTF-8?q?=E2=80=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UI.hpp | 258 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 217 insertions(+), 41 deletions(-) diff --git a/UI.hpp b/UI.hpp index 341957f..56c718b 100644 --- a/UI.hpp +++ b/UI.hpp @@ -89,6 +89,14 @@ extern uint8_t seq_temp_active_menu; extern uint8_t seq_chain_active_chainstep; //for editor extern uint8_t seq_chain_active_step; extern int seq_transpose; +extern uint8_t arp_step; +extern uint8_t arp_note; +extern uint8_t arp_chord; +extern uint8_t arp_octave; +extern uint8_t arp_oct_usersetting; +extern uint8_t arp_style; +extern uint8_t arp_speed; +extern char arp_style_names[4][3]; #endif #ifdef DISPLAY_LCD_SPI @@ -273,6 +281,7 @@ void UI_func_seq_live_transpose_oct(uint8_t param); void UI_func_seq_lenght(uint8_t param); void UI_func_seq_tempo(uint8_t param); void UI_func_seq_pat_chain(uint8_t param); +void UI_func_arpeggio(uint8_t param); void UI_func_seq_track_setup(uint8_t param); void UI_func_seq_display_style(uint8_t param); void UI_func_seq_pattern_load(uint8_t param); @@ -3915,14 +3924,9 @@ void UI_func_seq_display_style(uint8_t param) lcd.setCursor(0, 0); lcd.print("Seq. Disp. Style"); lcd.setCursor(0, 1); - lcd.print("Seq."); + lcd.print("Pat."); lcd.setCursor(8, 1); lcd.print("="); - lcd.setCursor(11, 1); - if (seq_content_type[seq_active_track] == 0) - lcd.print("Drum"); - else - lcd.print("Inst"); } if (LCDML.FUNC_loop()) // ****** LOOP ********* { @@ -3943,21 +3947,17 @@ void UI_func_seq_display_style(uint8_t param) { if (LCDML.BT_checkDown()) { - seq_content_type[seq_active_track] = constrain(seq_content_type[seq_active_track] + ENCODER[ENC_R].speed(), 0, 1); + seq_content_type[seq_active_track] = constrain(seq_content_type[seq_active_track] + ENCODER[ENC_R].speed(), 0, 2); } else if (LCDML.BT_checkUp()) { - seq_content_type[seq_active_track] = constrain(seq_content_type[seq_active_track] - ENCODER[ENC_R].speed(), 0, 1); + seq_content_type[seq_active_track] = constrain(seq_content_type[seq_active_track] - ENCODER[ENC_R].speed(), 0, 2); } } } if (LCDML.BT_checkEnter()) { - if (menu_select_toggle) { - menu_select_toggle = false; - } else - { menu_select_toggle = true; - } + menu_select_toggle = !menu_select_toggle; } if (menu_select_toggle == false) { lcd.setCursor(10, 1); @@ -3968,10 +3968,6 @@ void UI_func_seq_display_style(uint8_t param) lcd.print("["); lcd.print(seq_active_track); lcd.print("]"); - lcd.setCursor(11, 1); - if (seq_content_type[seq_active_track] == 0) lcd.print("Drum"); - else - lcd.print("Inst"); } else { lcd.setCursor(4, 1); lcd.print(" "); @@ -3979,12 +3975,16 @@ void UI_func_seq_display_style(uint8_t param) lcd.print(" "); lcd.setCursor(10, 1); lcd.print("["); - if (seq_content_type[seq_active_track] == 0) - lcd.print("Drum"); - else - lcd.print("Inst"); + lcd.setCursor(15, 1); lcd.print("]"); } + lcd.setCursor(11, 1); + if (seq_content_type[seq_active_track] == 0) + lcd.print("Drum"); + else if (seq_content_type[seq_active_track] == 1) + lcd.print("Inst"); + else + lcd.print("Chrd"); } if (LCDML.FUNC_close()) // ****** STABLE END ********* { @@ -4152,7 +4152,7 @@ void UI_func_seq_vel_editor(uint8_t param) else if (LCDML.BT_checkUp()) seq_vel[seq_active_track][seq_menu - 1] = constrain(seq_vel[seq_active_track][seq_menu - 1] - 1, 0, 127); } - } else if (seq_active_function == 1 && seq_content_type[seq_active_track] > 1 ) { + } else if (seq_active_function == 1 && seq_content_type[seq_active_track] > 1 ) { //is in Chord or Arp Mode if ((LCDML.BT_checkDown() && encoderDir[ENC_R].Down()) || (LCDML.BT_checkUp() && encoderDir[ENC_R].Up())) { if (LCDML.BT_checkDown()) @@ -4179,8 +4179,8 @@ void UI_func_seq_vel_editor(uint8_t param) } //button check end <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - if ( seq_content_type[seq_active_track] > 1 && seq_vel[seq_active_track][seq_menu - 1]<200) seq_vel[seq_active_track][seq_menu - 1]=200; - + if ( seq_content_type[seq_active_track] > 1 && seq_vel[seq_active_track][seq_menu - 1] < 200) seq_vel[seq_active_track][seq_menu - 1] = 200; + if (seq_menu == 0) { lcd.setCursor(4, 0); lcd.print("--- --- "); @@ -4232,7 +4232,7 @@ void UI_func_seq_vel_editor(uint8_t param) lcd.print(" "); } else { - if (seq_vel[seq_active_track][seq_menu - 1] == 200) lcd.print("Maj" ); + if (seq_vel[seq_active_track][seq_menu - 1] == 200) lcd.print("Maj" ); else if (seq_vel[seq_active_track][seq_menu - 1] == 201) lcd.print("Min" ); else if (seq_vel[seq_active_track][seq_menu - 1] == 202) lcd.print("Sev" ); else if (seq_vel[seq_active_track][seq_menu - 1] == 203) lcd.print("Aug" ); @@ -4283,6 +4283,21 @@ void seq_refresh_display_play_status() } } +void arp_refresh_display_play_status() +{ + lcd.setCursor(12, 0); + if (seq_running == false ) + { + lcd.print("PLY"); + } else if (seq_running == true ) + { + seq_note_in = 0; + lcd.print("STP"); + } +} + + + void UI_func_sequencer(uint8_t param) { if (LCDML.FUNC_setup()) // ****** SETUP ********* @@ -4759,9 +4774,173 @@ void UI_func_sequencer(uint8_t param) } } -void UI_func_seq_pat_chain(uint8_t param) +void UI_func_arpeggio(uint8_t param) { + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + encoderDir[ENC_R].reset(); + seq_temp_active_menu = 0; + lcd.setCursor( 0, 0); + lcd.print("Oct"); + lcd.setCursor( 0, 1); + lcd.print("Style"); + lcd.setCursor( 7, 0); + lcd.print("Maj"); + lcd.setCursor( 11, 1); + lcd.print("1/16"); + + arp_refresh_display_play_status(); + } + 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 (seq_temp_active_menu == 0) + { + if (LCDML.BT_checkDown()) + seq_temp_select_menu = constrain(seq_temp_select_menu + ENCODER[ENC_R].speed(), 0, 3); + else if (LCDML.BT_checkUp()) + seq_temp_select_menu = constrain(seq_temp_select_menu - ENCODER[ENC_R].speed(), 0, 3); + } else if (seq_temp_active_menu == 1) // Octave setting + { + if (LCDML.BT_checkDown()) + arp_oct_usersetting = constrain(arp_oct_usersetting + ENCODER[ENC_R].speed(), 0, 3); + else if (LCDML.BT_checkUp()) + arp_oct_usersetting = constrain(arp_oct_usersetting - ENCODER[ENC_R].speed(), 0, 3); + } + else if (seq_temp_active_menu == 2) // Style setting + { + if (LCDML.BT_checkDown()) + arp_style = constrain(arp_style + ENCODER[ENC_R].speed(), 0, 3); + else if (LCDML.BT_checkUp()) + arp_style = constrain(arp_style - ENCODER[ENC_R].speed(), 0, 3); + } + else if (seq_temp_active_menu == 3) // Arp Speed setting + { + if (LCDML.BT_checkDown()) + arp_speed = constrain(arp_speed + ENCODER[ENC_R].speed(), 0, 1); + else if (LCDML.BT_checkUp()) + arp_speed = constrain(arp_speed - ENCODER[ENC_R].speed(), 0, 1); + } + if (LCDML.BT_checkEnter()) //handle button presses during menu >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + { + if ( seq_temp_select_menu == 0 && seq_temp_active_menu == 0 ) + { + seq_temp_active_menu = 1; + } + else if ( seq_temp_select_menu == 0 && seq_temp_active_menu == 1 ) + { + seq_temp_active_menu = 0; + } + if ( seq_temp_select_menu == 1 && seq_temp_active_menu == 0 ) + { + seq_temp_active_menu = 2; + } + else if ( seq_temp_select_menu == 1 && seq_temp_active_menu == 2 ) + { + seq_temp_active_menu = 0; + } + + else if ( seq_temp_select_menu == 2 && seq_temp_active_menu == 0 ) + { + seq_running = !seq_running; + arp_refresh_display_play_status(); + MicroDexed[0]->panic(); + seq_step = 0; + arp_octave = 0; + arp_step = 0; + seq_chain_active_step = 0; + } + else if ( seq_temp_select_menu == 3 && seq_temp_active_menu == 0 ) + { + seq_temp_active_menu = 3; + } + else if ( seq_temp_select_menu == 3 && seq_temp_active_menu == 3 ) + { + seq_temp_active_menu = 0; + } + } + } + lcd.setCursor( 4, 0); + lcd.print(arp_oct_usersetting); + lcd.setCursor( 6, 1); + lcd.print( arp_style_names[arp_style][0] ); + lcd.print( arp_style_names[arp_style][1] ); + lcd.print( arp_style_names[arp_style][2] ); + lcd.setCursor( 11, 1); + if (arp_speed == 0)lcd.print("1/16"); else if (arp_speed == 1)lcd.print("1/8 "); + + if (seq_temp_select_menu == 0) { + lcd.setCursor( 11, 0); + lcd.print(" "); + lcd.setCursor( 15, 0); + lcd.print(" "); + lcd.setCursor( 5, 1); + lcd.print(" "); + lcd.setCursor( 9, 1); + lcd.print(" "); + lcd.setCursor( 3, 0); + lcd.print("["); + lcd.setCursor( 5, 0); + lcd.print("]"); + lcd.setCursor( 10, 1); + lcd.print(" "); + lcd.setCursor( 15, 1); + lcd.print(" "); + } + else if (seq_temp_select_menu == 1) + { + lcd.setCursor( 11, 0); + lcd.print(" "); + lcd.print(" "); + lcd.setCursor( 3, 0); + lcd.print(" "); + lcd.setCursor( 5, 0); + lcd.print(" "); + lcd.setCursor( 5, 1); + lcd.print("["); + lcd.setCursor( 9, 1); + lcd.print("]"); + } + else if (seq_temp_select_menu == 2) + { + lcd.setCursor( 5, 1); + lcd.print(" "); + lcd.setCursor( 9, 1); + lcd.print(" "); + lcd.setCursor( 11, 0); + lcd.print("["); + lcd.setCursor( 15, 0); + lcd.print("]"); + lcd.setCursor( 10, 1); + lcd.print(" "); + lcd.setCursor( 15, 1); + lcd.print(" "); + } + else if (seq_temp_select_menu == 3) + { + lcd.setCursor( 11, 0); + lcd.print(" "); + lcd.setCursor( 15, 0); + lcd.print(" "); + lcd.setCursor( 10, 1); + lcd.print("["); + lcd.setCursor( 15, 1); + lcd.print("]"); + lcd.setCursor( 3, 0); + lcd.print(" "); + lcd.setCursor( 5, 0); + lcd.print(" "); + } + } + if (LCDML.FUNC_close()) // ****** STABLE END ********* + { + encoderDir[ENC_R].reset(); + } +} +void UI_func_seq_pat_chain(uint8_t param) +{ if (LCDML.FUNC_setup()) // ****** SETUP ********* { // setup function @@ -4793,7 +4972,6 @@ void UI_func_seq_pat_chain(uint8_t param) lcd.setCursor(8 , 1); lcd.print( seq_patternchain[seq_chain_active_chainstep][3]); } - if (LCDML.FUNC_loop()) // ****** LOOP ********* { if (seq_temp_active_menu == 99) { @@ -4951,7 +5129,6 @@ void UI_func_seq_pat_chain(uint8_t param) void UI_func_seq_track_setup(uint8_t param) { - if (LCDML.FUNC_setup()) // ****** SETUP ********* { // setup function @@ -4965,13 +5142,13 @@ void UI_func_seq_track_setup(uint8_t param) lcd.setCursor(9 , 1); lcd.print("T4"); lcd.setCursor(3 , 0); - if (seq_track_type[0] == 0 ) lcd.print("Drm"); else lcd.print("Ins"); + if (seq_track_type[0] == 0 ) lcd.print("Drm"); else if (seq_track_type[0] == 1 ) lcd.print("Ins"); else if (seq_track_type[0] == 2 )lcd.print("Chd"); else lcd.print("Arp"); lcd.setCursor(3 , 1); - if (seq_track_type[1] == 0 ) lcd.print("Drm"); else lcd.print("Ins"); + if (seq_track_type[1] == 0 ) lcd.print("Drm"); else if (seq_track_type[1] == 1 ) lcd.print("Ins"); else if (seq_track_type[1] == 2 )lcd.print("Chd"); else lcd.print("Arp"); lcd.setCursor(12 , 0); - if (seq_track_type[2] == 0 ) lcd.print("Drm"); else lcd.print("Ins"); + if (seq_track_type[2] == 0 ) lcd.print("Drm"); else if (seq_track_type[2] == 1 ) lcd.print("Ins"); else if (seq_track_type[2] == 2 )lcd.print("Chd"); else lcd.print("Arp"); lcd.setCursor(12 , 1); - if (seq_track_type[3] == 0 ) lcd.print("Drm"); else lcd.print("Ins"); + if (seq_track_type[3] == 0 ) lcd.print("Drm"); else if (seq_track_type[3] == 1 ) lcd.print("Ins"); else if (seq_track_type[3] == 2 )lcd.print("Chd"); else lcd.print("Arp"); } if (LCDML.FUNC_loop()) // ****** LOOP ********* { @@ -4987,9 +5164,9 @@ void UI_func_seq_track_setup(uint8_t param) if ((LCDML.BT_checkDown() && encoderDir[ENC_R].Down()) || (LCDML.BT_checkUp() && encoderDir[ENC_R].Up())) { if (LCDML.BT_checkDown()) - seq_track_type[seq_temp_active_menu] = constrain(seq_track_type[seq_temp_active_menu] + 1, 0, 1); + seq_track_type[seq_temp_active_menu] = constrain(seq_track_type[seq_temp_active_menu] + 1, 0, 3); else if (LCDML.BT_checkUp()) - seq_track_type[seq_temp_active_menu] = constrain(seq_track_type[seq_temp_active_menu] - 1, 0, 1); + seq_track_type[seq_temp_active_menu] = constrain(seq_track_type[seq_temp_active_menu] - 1, 0, 3); } } if (LCDML.BT_checkEnter()) //handle button presses during menu >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @@ -5012,7 +5189,7 @@ void UI_func_seq_track_setup(uint8_t param) lcd.setCursor(6 , 1); lcd.print(" "); lcd.setCursor(3 , 0); - if (seq_track_type[0] == 0 ) lcd.print("Drm"); else lcd.print("Ins"); + if (seq_track_type[0] == 0 ) lcd.print("Drm"); else if (seq_track_type[0] == 1 ) lcd.print("Ins"); else if (seq_track_type[0] == 2 )lcd.print("Chd"); else lcd.print("Arp"); } else if (seq_temp_select_menu == 1) { lcd.setCursor(2 , 0); @@ -5028,7 +5205,7 @@ void UI_func_seq_track_setup(uint8_t param) lcd.setCursor(15 , 0); lcd.print(" "); lcd.setCursor(3 , 1); - if (seq_track_type[1] == 0 ) lcd.print("Drm"); else lcd.print("Ins"); + if (seq_track_type[1] == 0 ) lcd.print("Drm"); else if (seq_track_type[1] == 1 ) lcd.print("Ins"); else if (seq_track_type[1] == 2 )lcd.print("Chd"); else lcd.print("Arp"); } else if (seq_temp_select_menu == 2) { lcd.setCursor(2 , 1); @@ -5044,7 +5221,7 @@ void UI_func_seq_track_setup(uint8_t param) lcd.setCursor(15 , 1); lcd.print(" "); lcd.setCursor(12 , 0); - if (seq_track_type[2] == 0 ) lcd.print("Drm"); else lcd.print("Ins"); + if (seq_track_type[2] == 0 ) lcd.print("Drm"); else if (seq_track_type[2] == 1 ) lcd.print("Ins"); else if (seq_track_type[2] == 2 )lcd.print("Chd"); else lcd.print("Arp"); } else if (seq_temp_select_menu == 3) { lcd.setCursor(11 , 0); @@ -5056,7 +5233,7 @@ void UI_func_seq_track_setup(uint8_t param) lcd.setCursor(15 , 1); lcd.print("]"); lcd.setCursor(12 , 1); - if (seq_track_type[3] == 0 ) lcd.print("Drm"); else lcd.print("Ins"); + if (seq_track_type[3] == 0 ) lcd.print("Drm"); else if (seq_track_type[3] == 1 ) lcd.print("Ins"); else if (seq_track_type[3] == 2 )lcd.print("Chd"); else lcd.print("Arp"); } } if (LCDML.FUNC_close()) // ****** STABLE END ********* @@ -5065,7 +5242,6 @@ void UI_func_seq_track_setup(uint8_t param) } } - void UI_func_seq_pattern_load(uint8_t param) { static uint8_t mode; @@ -5134,7 +5310,7 @@ void UI_func_seq_pattern_save(uint8_t param) { char tmp[FILENAME_LEN]; yesno = false; - temp_int=0; + temp_int = 0; mode = 0; encoderDir[ENC_R].reset(); lcd.setCursor(0, 0); @@ -7357,7 +7533,7 @@ void UI_func_eq_treble(uint8_t param) } } lcd_display_meter_float("EQ Treble", configuration.fx.eq_treble, 0.1, 0.0, EQ_TREBLE_MIN, EQ_TREBLE_MAX, 1, 1, false, true, true); - sgtl5000_1.eqBand(4,mapfloat(configuration.fx.eq_treble, EQ_TREBLE_MIN, EQ_TREBLE_MAX, -1.0, 1.0)); + sgtl5000_1.eqBand(4, mapfloat(configuration.fx.eq_treble, EQ_TREBLE_MIN, EQ_TREBLE_MAX, -1.0, 1.0)); } if (LCDML.FUNC_close()) // ****** STABLE END ********* From 466e105b394d870e54515d652972307c32fe06c0 Mon Sep 17 00:00:00 2001 From: positionhigh Date: Sat, 14 Aug 2021 11:38:24 +0200 Subject: [PATCH 4/5] =?UTF-8?q?Dateien=20hochladen=20nach=20=E2=80=9E?= =?UTF-8?q?=E2=80=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MicroDexed.ino | 3989 ++++++++++++++++++++++++------------------------ UI.hpp | 87 +- sequencer.cpp | 231 ++- sequencer.h | 11 +- 4 files changed, 2181 insertions(+), 2137 deletions(-) diff --git a/MicroDexed.ino b/MicroDexed.ino index a140ddc..441fb67 100644 --- a/MicroDexed.ino +++ b/MicroDexed.ino @@ -21,6 +21,9 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "TeensyTimerTool.h" +using namespace TeensyTimerTool; + #include #include "config.h" #include @@ -325,6 +328,7 @@ char g_voice_name[NUM_DEXED][VOICE_NAME_LEN]; char g_bank_name[NUM_DEXED][BANK_NAME_LEN]; char receive_bank_filename[FILENAME_LEN]; uint8_t selected_instance_id = 0; +uint8_t seq_UI_last_step = 0; #ifdef TEENSY4 #if NUM_DEXED>1 int8_t midi_decay[NUM_DEXED] = { -1, -1}; @@ -358,6 +362,8 @@ extern LCDMenuLib2 LCDML; #endif extern void getNoteName(char* noteName, uint8_t noteNumber); +PeriodicTimer timer1; +extern char seq_chord_names[6][4]; /*********************************************************************** SETUP @@ -631,2155 +637,2186 @@ void setup() #endif LCDML.OTHER_jumpToFunc(UI_func_voice_select); -} - -void loop() -{ - // MIDI input handling - check_midi_devices(); - // check encoder - ENCODER[ENC_L].update(); - ENCODER[ENC_R].update(); + timer1.begin(sequencer, 74'000,false); - if (seq_running) { - sequencer(); - } -#ifdef ENABLE_LCD_UI - LCDML.loop(); -#endif + } - // CONTROL-RATE-EVENT-HANDLING - if (control_rate > CONTROL_RATE_MS) - { - control_rate = 0; + void loop() + { + // MIDI input handling + check_midi_devices(); - // check for value changes, unused voices and CPU overload - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - active_voices[instance_id] = MicroDexed[instance_id]->getNumNotesPlaying(); - if (active_voices[instance_id] == 0) - midi_voices[instance_id] = 0; - } + // check encoder + ENCODER[ENC_L].update(); + ENCODER[ENC_R].update(); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) - { - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { +#ifdef ENABLE_LCD_UI + LCDML.loop(); +#endif + + if (seq_running) + { + if (seq_step != seq_UI_last_step) + { + seq_UI_last_step=seq_step; + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_sequencer)) //is in UI of Sequencer + { + lcd.setCursor(seq_step, 1); + lcd.print("X"); + if (seq_step == 0) + { + lcd.setCursor(15, 1); + lcd.print(seq_find_shortname(15)[0]); + } + else + { + lcd.setCursor(seq_step - 1, 1); + lcd.print(seq_find_shortname(seq_step - 1)[0]); + } + + } + else if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_arpeggio)) //is in UI of Arpeggiator + { + lcd.setCursor(7, 0); + lcd.print( seq_chord_names[arp_chord - 200][0]); + lcd.print( seq_chord_names[arp_chord - 200][1]); + lcd.print( seq_chord_names[arp_chord - 200][2]); + lcd.print( seq_chord_names[arp_chord - 200][3]); + } + } + } + // CONTROL-RATE-EVENT-HANDLING + if (control_rate > CONTROL_RATE_MS) + { + control_rate = 0; + + // check for value changes, unused voices and CPU overload + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + active_voices[instance_id] = MicroDexed[instance_id]->getNumNotesPlaying(); + if (active_voices[instance_id] == 0) + midi_voices[instance_id] = 0; + } + + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) + { + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { #ifdef TEENSY4 - if (midi_decay_timer > MIDI_DECAY_TIMER && midi_decay[instance_id] > 0) - { - midi_decay[instance_id]--; - lcd.createChar(6 + instance_id, (uint8_t*)special_chars[15 - (7 - midi_decay[instance_id])]); - lcd.setCursor(14 + instance_id, 1); - lcd.write(6 + instance_id); - } - else if (midi_voices[instance_id] == 0 && midi_decay[instance_id] == 0 && !MicroDexed[instance_id]->getSustain()) - { - midi_decay[instance_id]--; - lcd.setCursor(14 + instance_id, 1); - lcd.write(20); // blank - } + if (midi_decay_timer > MIDI_DECAY_TIMER && midi_decay[instance_id] > 0) + { + midi_decay[instance_id]--; + lcd.createChar(6 + instance_id, (uint8_t*)special_chars[15 - (7 - midi_decay[instance_id])]); + lcd.setCursor(14 + instance_id, 1); + lcd.write(6 + instance_id); + } + else if (midi_voices[instance_id] == 0 && midi_decay[instance_id] == 0 && !MicroDexed[instance_id]->getSustain()) + { + midi_decay[instance_id]--; + lcd.setCursor(14 + instance_id, 1); + lcd.write(20); // blank + } #else - static bool midi_playing[NUM_DEXED]; - if (midi_voices[instance_id] > 0 && midi_playing[instance_id] == false) - { - midi_playing[instance_id] = true; - lcd.setCursor(14 + instance_id, 1); - lcd.write(6 + instance_id); - } - else if (midi_voices[instance_id] == 0 && !MicroDexed[instance_id]->getSustain()) - { - midi_playing[instance_id] = false; - lcd.setCursor(14 + instance_id, 1); - lcd.write(20); // blank - } -#endif - } + static bool midi_playing[NUM_DEXED]; + if (midi_voices[instance_id] > 0 && midi_playing[instance_id] == false) + { + midi_playing[instance_id] = true; + lcd.setCursor(14 + instance_id, 1); + lcd.write(6 + instance_id); + } + else if (midi_voices[instance_id] == 0 && !MicroDexed[instance_id]->getSustain()) + { + midi_playing[instance_id] = false; + lcd.setCursor(14 + instance_id, 1); + lcd.write(20); // blank + } +#endif + } #ifdef TEENSY4 - if (midi_decay_timer > 250) - { - midi_decay_timer = 0; - } + if (midi_decay_timer > 250) + { + midi_decay_timer = 0; + } #endif - } - } - else - yield(); + } + } + else + yield(); #if defined (DEBUG) && defined (SHOW_CPU_LOAD_MSEC) - if (cpu_mem_millis >= SHOW_CPU_LOAD_MSEC) - { - if (master_peak_r.available()) - if (master_peak_r.read() == 1.0) - peak_r++; - if (master_peak_l.available()) - if (master_peak_l.read() == 1.0) - peak_l++; - if (microdexed_peak.available()) - { - peak_dexed_value = microdexed_peak.read(); - if (peak_dexed_value > 0.99) - peak_dexed++; - } - cpu_mem_millis -= SHOW_CPU_LOAD_MSEC; - show_cpu_and_mem_usage(); - } -#endif -} - -/****************************************************************************** - MIDI MESSAGE HANDLER - ******************************************************************************/ -void handleNoteOn(byte inChannel, byte inNumber, byte inVelocity) -{ - // Check for MicroDexed - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - if (checkMidiChannel(inChannel, instance_id)) - { - 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]++; + if (cpu_mem_millis >= SHOW_CPU_LOAD_MSEC) + { + if (master_peak_r.available()) + if (master_peak_r.read() == 1.0) + peak_r++; + if (master_peak_l.available()) + if (master_peak_l.read() == 1.0) + peak_l++; + if (microdexed_peak.available()) + { + peak_dexed_value = microdexed_peak.read(); + if (peak_dexed_value > 0.99) + peak_dexed++; + } + cpu_mem_millis -= SHOW_CPU_LOAD_MSEC; + show_cpu_and_mem_usage(); + } +#endif + } + + /****************************************************************************** + MIDI MESSAGE HANDLER + ******************************************************************************/ + void handleNoteOn(byte inChannel, byte inNumber, byte inVelocity) + { + // Check for MicroDexed + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + if (checkMidiChannel(inChannel, instance_id)) + { + 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]++; #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); - } -#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(); -#endif - return; - } - } - } + 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(); +#endif + return; + } + } + } #if NUM_DRUMS > 0 - // Check for Drum - if (inChannel == DRUM_MIDI_CHANNEL) - { - if (drum_counter >= NUM_DRUMS) - drum_counter = 0; - -#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); -#endif - - for (uint8_t d = 0; d < NUM_DRUMSET_CONFIG; d++) - { - 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) * pseudo_log_curve(mapfloat(inVelocity, 0, 127, drum_config[d].vol_min, drum_config[d].vol_max))); - drum_mixer_l.gain(slot, (pan) * pseudo_log_curve(mapfloat(inVelocity, 0, 127, drum_config[d].vol_min, drum_config[d].vol_max))); - drum_reverb_send_mixer_r.gain(slot, (1.0 - pan) * pseudo_log_curve(drum_config[d].reverb_send)); - drum_reverb_send_mixer_l.gain(slot, pan * pseudo_log_curve(drum_config[d].reverb_send)); - - if (drum_config[d].drum_data != NULL) - Drum[slot]->play(drum_config[d].drum_data); - -#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; - } - } - } -#endif -} + // Check for Drum + if (inChannel == DRUM_MIDI_CHANNEL) + { + if (drum_counter >= NUM_DRUMS) + drum_counter = 0; + +#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); +#endif + + for (uint8_t d = 0; d < NUM_DRUMSET_CONFIG; d++) + { + 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) * pseudo_log_curve(mapfloat(inVelocity, 0, 127, drum_config[d].vol_min, drum_config[d].vol_max))); + drum_mixer_l.gain(slot, (pan) * pseudo_log_curve(mapfloat(inVelocity, 0, 127, drum_config[d].vol_min, drum_config[d].vol_max))); + drum_reverb_send_mixer_r.gain(slot, (1.0 - pan) * pseudo_log_curve(drum_config[d].reverb_send)); + drum_reverb_send_mixer_l.gain(slot, pan * pseudo_log_curve(drum_config[d].reverb_send)); + + if (drum_config[d].drum_data != NULL) + Drum[slot]->play(drum_config[d].drum_data); + +#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; + } + } + } +#endif + } #if NUM_DRUMS > 0 -uint8_t drum_get_slot(uint8_t dt) -{ - for (uint8_t i = 0; i < NUM_DRUMS; i++) - { - if (!Drum[i]->isPlaying()) - drum_type[i] = DRUM_NONE; - else - { - if (drum_type[i] == dt) - { -#ifdef DEBUG - Serial.print(F("Stopping Drum ")); - Serial.print(i); - Serial.print(F(" type ")); - Serial.println(dt); -#endif - Drum[i]->stop(); - return (i); - } - } - } -#ifdef DEBUG - Serial.print(F("Using next free Drum slot ")); - Serial.println(drum_counter % 4); -#endif - drum_type[drum_counter % 4] = dt; - drum_counter++; - return (drum_counter - 1 % 4); -} -#endif - -void handleNoteOff(byte inChannel, byte inNumber, byte inVelocity) -{ - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - if (checkMidiChannel(inChannel, instance_id)) - { - 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]->keyup(inNumber); - - midi_voices[instance_id]--; -#ifdef DEBUG - char note_name[4]; - getNoteName(note_name, inNumber); - Serial.print(F("KeyUp ")); - 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 - } - } - } -} - -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 (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); -#endif - - switch (inCtrl) { - case 0: // BankSelect MSB -#ifdef DEBUG - Serial.println(F("BANK-SELECT MSB CC")); -#endif - configuration.performance.bank[instance_id] = constrain((inValue << 7)&configuration.performance.bank[instance_id], 0, MAX_BANKS - 1); - /* load_sd_voice(configuration.performance.bank[instance_id], configuration.performance.voice[instance_id], 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")); -#endif - MicroDexed[instance_id]->setModWheel(inValue); - MicroDexed[instance_id]->ControllersRefresh(); - break; - case 2: -#ifdef DEBUG - Serial.println(F("BREATH CC")); -#endif - MicroDexed[instance_id]->setBreathController(inValue); - MicroDexed[instance_id]->ControllersRefresh(); - break; - case 4: -#ifdef DEBUG - 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 -#ifdef DEBUG - Serial.println(F("VOLUME CC")); -#endif - configuration.dexed[instance_id].sound_intensity = map(inValue, 0, 0x7f, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX); - MicroDexed[instance_id]->setGain(pseudo_log_curve(mapfloat(configuration.dexed[instance_id].sound_intensity, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX, 0.0, SOUND_INTENSITY_AMP_MAX))); - 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")); -#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 -#ifdef DEBUG - Serial.println(F("BANK-SELECT LSB CC")); -#endif - configuration.performance.bank[instance_id] = constrain(inValue, 0, MAX_BANKS - 1); - /*load_sd_voice(configuration.performance.bank[instance_id], configuration.performance.voice[instance_id], 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++) - { - 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; + uint8_t drum_get_slot(uint8_t dt) + { + for (uint8_t i = 0; i < NUM_DRUMS; i++) + { + if (!Drum[i]->isPlaying()) + drum_type[i] = DRUM_NONE; + else + { + if (drum_type[i] == dt) + { +#ifdef DEBUG + Serial.print(F("Stopping Drum ")); + Serial.print(i); + Serial.print(F(" type ")); + Serial.println(dt); +#endif + Drum[i]->stop(); + return (i); + } + } + } +#ifdef DEBUG + Serial.print(F("Using next free Drum slot ")); + Serial.println(drum_counter % 4); +#endif + drum_type[drum_counter % 4] = dt; + drum_counter++; + return (drum_counter - 1 % 4); + } +#endif + + void handleNoteOff(byte inChannel, byte inNumber, byte inVelocity) + { + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + if (checkMidiChannel(inChannel, instance_id)) + { + 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]->keyup(inNumber); + + midi_voices[instance_id]--; +#ifdef DEBUG + char note_name[4]; + getNoteName(note_name, inNumber); + Serial.print(F("KeyUp ")); + 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 + } + } + } + } + + 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 (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); +#endif + + switch (inCtrl) { + case 0: // BankSelect MSB +#ifdef DEBUG + Serial.println(F("BANK-SELECT MSB CC")); +#endif + configuration.performance.bank[instance_id] = constrain((inValue << 7)&configuration.performance.bank[instance_id], 0, MAX_BANKS - 1); + /* load_sd_voice(configuration.performance.bank[instance_id], configuration.performance.voice[instance_id], 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")); +#endif + MicroDexed[instance_id]->setModWheel(inValue); + MicroDexed[instance_id]->ControllersRefresh(); + break; + case 2: +#ifdef DEBUG + Serial.println(F("BREATH CC")); +#endif + MicroDexed[instance_id]->setBreathController(inValue); + MicroDexed[instance_id]->ControllersRefresh(); + break; + case 4: +#ifdef DEBUG + 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 +#ifdef DEBUG + Serial.println(F("VOLUME CC")); +#endif + configuration.dexed[instance_id].sound_intensity = map(inValue, 0, 0x7f, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX); + MicroDexed[instance_id]->setGain(pseudo_log_curve(mapfloat(configuration.dexed[instance_id].sound_intensity, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX, 0.0, SOUND_INTENSITY_AMP_MAX))); + 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")); +#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 +#ifdef DEBUG + Serial.println(F("BANK-SELECT LSB CC")); +#endif + configuration.performance.bank[instance_id] = constrain(inValue, 0, MAX_BANKS - 1); + /*load_sd_voice(configuration.performance.bank[instance_id], configuration.performance.voice[instance_id], 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++) + { + 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; #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, pseudo_log_curve(mapfloat(configuration.fx.reverb_send[selected_instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, 1.0))); - reverb_mixer_l.gain(selected_instance_id, pseudo_log_curve(mapfloat(configuration.fx.reverb_send[selected_instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, 1.0))); - 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, 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, pseudo_log_curve(mapfloat(configuration.fx.delay_feedback[instance_id], DELAY_FEEDBACK_MIN, DELAY_FEEDBACK_MAX, 0.0, 1.0))); // 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, pseudo_log_curve(mapfloat(configuration.fx.delay_level[instance_id], DELAY_LEVEL_MIN, DELAY_LEVEL_MAX, 0.0, 1.0))); - 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; - } - } - } -} - -void handleAfterTouch(byte inChannel, byte inPressure) -{ - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - if (checkMidiChannel(inChannel, instance_id)) - { - MicroDexed[instance_id]->setAftertouch(inPressure); - MicroDexed[instance_id]->ControllersRefresh(); - } - } -} - -void handlePitchBend(byte inChannel, int inPitch) -{ - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - if (checkMidiChannel(inChannel, instance_id)) - { - MicroDexed[instance_id]->setPitchbend(inPitch); - } - } -} - -void handleProgramChange(byte inChannel, byte inProgram) -{ - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - if (checkMidiChannel(inChannel, instance_id)) - { - configuration.performance.voice[instance_id] = constrain(inProgram, 0, MAX_VOICES - 1); + 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, pseudo_log_curve(mapfloat(configuration.fx.reverb_send[selected_instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, 1.0))); + reverb_mixer_l.gain(selected_instance_id, pseudo_log_curve(mapfloat(configuration.fx.reverb_send[selected_instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, 1.0))); + 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, 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, pseudo_log_curve(mapfloat(configuration.fx.delay_feedback[instance_id], DELAY_FEEDBACK_MIN, DELAY_FEEDBACK_MAX, 0.0, 1.0))); // 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, pseudo_log_curve(mapfloat(configuration.fx.delay_level[instance_id], DELAY_LEVEL_MIN, DELAY_LEVEL_MAX, 0.0, 1.0))); + 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; + } + } + } + } + + void handleAfterTouch(byte inChannel, byte inPressure) + { + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + if (checkMidiChannel(inChannel, instance_id)) + { + MicroDexed[instance_id]->setAftertouch(inPressure); + MicroDexed[instance_id]->ControllersRefresh(); + } + } + } + + void handlePitchBend(byte inChannel, int inPitch) + { + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + if (checkMidiChannel(inChannel, instance_id)) + { + MicroDexed[instance_id]->setPitchbend(inPitch); + } + } + } + + void handleProgramChange(byte inChannel, byte inProgram) + { + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + if (checkMidiChannel(inChannel, instance_id)) + { + configuration.performance.voice[instance_id] = constrain(inProgram, 0, MAX_VOICES - 1); #ifdef DISPLAY_LCD_SPI - change_disp_sd(false); + change_disp_sd(false); #endif - load_sd_voice(configuration.performance.bank[instance_id], configuration.performance.voice[instance_id], instance_id); + load_sd_voice(configuration.performance.bank[instance_id], configuration.performance.voice[instance_id], instance_id); #ifdef DISPLAY_LCD_SPI - change_disp_sd(true); -#endif - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - } - } -} - -void handleSystemExclusive(byte * sysex, uint len) -{ - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - if (!checkMidiChannel((sysex[2] & 0x0f) + 1 , instance_id)) - { -#ifdef DEBUG - Serial.print(F("INSTANCE ")); - Serial.print(instance_id, DEC); - Serial.println(F(": SYSEX-MIDI-Channel mismatch")); -#endif - return; - } - -#ifdef DEBUG - Serial.print(F("SysEx data length: [")); - Serial.print(len); - Serial.println(F("]")); - - Serial.println(F("SysEx data:")); - for (uint16_t i = 0; i < len; i++) - { - Serial.print(F("[0x")); - uint8_t s = sysex[i]; - if (s < 16) - Serial.print(F("0")); - Serial.print(s, HEX); - Serial.print(F("|")); - if (s < 100) - Serial.print(F("0")); - if (s < 10) - Serial.print(F("0")); - Serial.print(s, DEC); - Serial.print(F("]")); - if ((i + 1) % 16 == 0) - Serial.println(); - } - Serial.println(); -#endif - - // Check for SYSEX end byte - if (sysex[len - 1] != 0xf7) - { -#ifdef DEBUG - Serial.println(F("E: SysEx end status byte not detected.")); -#endif - return; - } - - if (sysex[1] != 0x43) // check for Yamaha sysex - { -#ifdef DEBUG - Serial.println(F("E: SysEx vendor not Yamaha.")); -#endif - return; - } - -#ifdef DEBUG - Serial.print(F("Substatus: [")); - Serial.print((sysex[2] & 0x70) >> 4); - Serial.println(F("]")); -#endif - - // parse parameter change - if (len == 7) - { - if (((sysex[3] & 0x7c) >> 2) != 0 && ((sysex[3] & 0x7c) >> 2) != 2) - { -#ifdef DEBUG - Serial.println(F("E: Not a SysEx parameter or function parameter change.")); -#endif - return; - } - - sysex[4] &= 0x7f; - sysex[5] &= 0x7f; - - if ((sysex[3] & 0x7c) >> 2 == 0) - { -#ifdef DEBUG - Serial.println(F("SysEx Voice parameter:")); - Serial.print("Parameter #"); - Serial.print(sysex[4] + ((sysex[3] & 0x03) * 128), DEC); - Serial.print(" Value: "); - Serial.println(sysex[5], DEC); -#endif - MicroDexed[instance_id]->setVoiceDataElement(sysex[4] + ((sysex[3] & 0x03) * 128), sysex[5]); - } - else if ((sysex[3] & 0x7c) >> 2 == 2) - { -#ifdef DEBUG - Serial.println(F("SysEx Function parameter:")); - Serial.print("Parameter #"); - Serial.print(sysex[4], DEC); - Serial.print(" Value: "); - Serial.println(sysex[5], DEC); -#endif - switch (sysex[4]) - { - case 65: - configuration.dexed[instance_id].pb_range = constrain(sysex[5], PB_RANGE_MIN, PB_RANGE_MAX); - MicroDexed[instance_id]->setPitchbendRange(configuration.dexed[instance_id].pb_range); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_pb_range)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 66: - configuration.dexed[instance_id].pb_step = constrain(sysex[5], PB_STEP_MIN, PB_STEP_MAX); - MicroDexed[instance_id]->setPitchbendRange(configuration.dexed[instance_id].pb_step); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_pb_step)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 67: - configuration.dexed[instance_id].portamento_mode = constrain(sysex[5], PORTAMENTO_MODE_MIN, PORTAMENTO_MODE_MAX); - 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 68: - configuration.dexed[instance_id].portamento_glissando = constrain(sysex[5], PORTAMENTO_GLISSANDO_MIN, PORTAMENTO_GLISSANDO_MAX); - 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_glissando)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 69: - configuration.dexed[instance_id].portamento_time = constrain(sysex[5], PORTAMENTO_TIME_MIN, PORTAMENTO_TIME_MAX); - 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_time)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 70: - configuration.dexed[instance_id].mw_range = constrain(sysex[5], MW_RANGE_MIN, MW_RANGE_MAX); - MicroDexed[instance_id]->setModWheelRange(configuration.dexed[instance_id].mw_range); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_mw_range)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 71: - configuration.dexed[instance_id].mw_assign = constrain(sysex[5], MW_ASSIGN_MIN, MW_ASSIGN_MAX); - MicroDexed[instance_id]->setModWheelTarget(configuration.dexed[instance_id].mw_assign); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_mw_assign)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 72: - configuration.dexed[instance_id].fc_range = constrain(sysex[5], FC_RANGE_MIN, FC_RANGE_MAX); - MicroDexed[instance_id]->setFootControllerRange(configuration.dexed[instance_id].fc_range); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_fc_range)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 73: - configuration.dexed[instance_id].fc_assign = constrain(sysex[5], FC_ASSIGN_MIN, FC_ASSIGN_MAX); - MicroDexed[instance_id]->setFootControllerTarget(configuration.dexed[instance_id].fc_assign); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_fc_assign)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 74: - configuration.dexed[instance_id].bc_range = constrain(sysex[5], BC_RANGE_MIN, BC_RANGE_MAX); - MicroDexed[instance_id]->setBreathControllerRange(configuration.dexed[instance_id].bc_range); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_bc_range)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 75: - configuration.dexed[instance_id].bc_assign = constrain(sysex[5], BC_ASSIGN_MIN, BC_ASSIGN_MAX); - MicroDexed[instance_id]->setBreathControllerTarget(configuration.dexed[instance_id].bc_assign); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_bc_assign)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 76: - configuration.dexed[instance_id].at_range = constrain(sysex[5], AT_RANGE_MIN, AT_RANGE_MAX); - MicroDexed[instance_id]->setAftertouchRange(configuration.dexed[instance_id].at_range); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_at_range)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - case 77: - configuration.dexed[instance_id].at_assign = constrain(sysex[5], AT_ASSIGN_MIN, AT_ASSIGN_MAX); - MicroDexed[instance_id]->setAftertouchTarget(configuration.dexed[instance_id].at_assign); - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_at_assign)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - break; - default: - MicroDexed[instance_id]->setVoiceDataElement(sysex[4], sysex[5]); // set function parameter - break; - } - MicroDexed[instance_id]->ControllersRefresh(); - } -#ifdef DEBUG - else - { - Serial.println(F("E: Unknown SysEx voice or function.")); - } -#endif - } - else if (len == 163) - { - int32_t bulk_checksum_calc = 0; - int8_t bulk_checksum = sysex[161]; - - // 1 Voice bulk upload -#ifdef DEBUG - Serial.println(F("One Voice bulk upload")); -#endif - - if ((sysex[3] & 0x7f) != 0) - { -#ifdef DEBUG - Serial.println(F("E: Not a SysEx voice bulk upload.")); -#endif - return; - } - - if (((sysex[4] << 7) | sysex[5]) != 0x9b) - { -#ifdef DEBUG - Serial.println(F("E: Wrong length for SysEx voice bulk upload (not 155).")); -#endif - return; - } - - // checksum calculation - for (uint8_t i = 0; i < 155 ; i++) - { - bulk_checksum_calc -= sysex[i + 6]; - } - bulk_checksum_calc &= 0x7f; - - if (bulk_checksum_calc != bulk_checksum) - { -#ifdef DEBUG - Serial.print(F("E: Checksum error for one voice [0x")); - Serial.print(bulk_checksum, HEX); - Serial.print(F("/0x")); - Serial.print(bulk_checksum_calc, HEX); - Serial.println(F("]")); + change_disp_sd(true); #endif - return; - } - - // fix voice name - for (uint8_t i = 0; i < 10; i++) - { - if (sysex[151 + i] > 126) // filter characters - sysex[151 + i] = 32; - } - - // load sysex-data into voice memory - MicroDexed[instance_id]->loadVoiceParameters(&sysex[6]); - -#ifdef DEBUG - show_patch(instance_id); -#endif - - // show voice name - strncpy(g_voice_name[instance_id], (char*)&sysex[151], VOICE_NAME_LEN - 1); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + } + } + } + + void handleSystemExclusive(byte * sysex, uint len) + { + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + if (!checkMidiChannel((sysex[2] & 0x0f) + 1 , instance_id)) + { +#ifdef DEBUG + Serial.print(F("INSTANCE ")); + Serial.print(instance_id, DEC); + Serial.println(F(": SYSEX-MIDI-Channel mismatch")); +#endif + return; + } + +#ifdef DEBUG + Serial.print(F("SysEx data length: [")); + Serial.print(len); + Serial.println(F("]")); + + Serial.println(F("SysEx data:")); + for (uint16_t i = 0; i < len; i++) + { + Serial.print(F("[0x")); + uint8_t s = sysex[i]; + if (s < 16) + Serial.print(F("0")); + Serial.print(s, HEX); + Serial.print(F("|")); + if (s < 100) + Serial.print(F("0")); + if (s < 10) + Serial.print(F("0")); + Serial.print(s, DEC); + Serial.print(F("]")); + if ((i + 1) % 16 == 0) + Serial.println(); + } + Serial.println(); +#endif + + // Check for SYSEX end byte + if (sysex[len - 1] != 0xf7) + { +#ifdef DEBUG + Serial.println(F("E: SysEx end status byte not detected.")); +#endif + return; + } + + if (sysex[1] != 0x43) // check for Yamaha sysex + { +#ifdef DEBUG + Serial.println(F("E: SysEx vendor not Yamaha.")); +#endif + return; + } + +#ifdef DEBUG + Serial.print(F("Substatus: [")); + Serial.print((sysex[2] & 0x70) >> 4); + Serial.println(F("]")); +#endif + + // parse parameter change + if (len == 7) + { + if (((sysex[3] & 0x7c) >> 2) != 0 && ((sysex[3] & 0x7c) >> 2) != 2) + { +#ifdef DEBUG + Serial.println(F("E: Not a SysEx parameter or function parameter change.")); +#endif + return; + } + + sysex[4] &= 0x7f; + sysex[5] &= 0x7f; + + if ((sysex[3] & 0x7c) >> 2 == 0) + { +#ifdef DEBUG + Serial.println(F("SysEx Voice parameter:")); + Serial.print("Parameter #"); + Serial.print(sysex[4] + ((sysex[3] & 0x03) * 128), DEC); + Serial.print(" Value: "); + Serial.println(sysex[5], DEC); +#endif + MicroDexed[instance_id]->setVoiceDataElement(sysex[4] + ((sysex[3] & 0x03) * 128), sysex[5]); + } + else if ((sysex[3] & 0x7c) >> 2 == 2) + { +#ifdef DEBUG + Serial.println(F("SysEx Function parameter:")); + Serial.print("Parameter #"); + Serial.print(sysex[4], DEC); + Serial.print(" Value: "); + Serial.println(sysex[5], DEC); +#endif + switch (sysex[4]) + { + case 65: + configuration.dexed[instance_id].pb_range = constrain(sysex[5], PB_RANGE_MIN, PB_RANGE_MAX); + MicroDexed[instance_id]->setPitchbendRange(configuration.dexed[instance_id].pb_range); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_pb_range)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 66: + configuration.dexed[instance_id].pb_step = constrain(sysex[5], PB_STEP_MIN, PB_STEP_MAX); + MicroDexed[instance_id]->setPitchbendRange(configuration.dexed[instance_id].pb_step); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_pb_step)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 67: + configuration.dexed[instance_id].portamento_mode = constrain(sysex[5], PORTAMENTO_MODE_MIN, PORTAMENTO_MODE_MAX); + 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 68: + configuration.dexed[instance_id].portamento_glissando = constrain(sysex[5], PORTAMENTO_GLISSANDO_MIN, PORTAMENTO_GLISSANDO_MAX); + 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_glissando)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 69: + configuration.dexed[instance_id].portamento_time = constrain(sysex[5], PORTAMENTO_TIME_MIN, PORTAMENTO_TIME_MAX); + 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_time)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 70: + configuration.dexed[instance_id].mw_range = constrain(sysex[5], MW_RANGE_MIN, MW_RANGE_MAX); + MicroDexed[instance_id]->setModWheelRange(configuration.dexed[instance_id].mw_range); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_mw_range)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 71: + configuration.dexed[instance_id].mw_assign = constrain(sysex[5], MW_ASSIGN_MIN, MW_ASSIGN_MAX); + MicroDexed[instance_id]->setModWheelTarget(configuration.dexed[instance_id].mw_assign); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_mw_assign)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 72: + configuration.dexed[instance_id].fc_range = constrain(sysex[5], FC_RANGE_MIN, FC_RANGE_MAX); + MicroDexed[instance_id]->setFootControllerRange(configuration.dexed[instance_id].fc_range); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_fc_range)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 73: + configuration.dexed[instance_id].fc_assign = constrain(sysex[5], FC_ASSIGN_MIN, FC_ASSIGN_MAX); + MicroDexed[instance_id]->setFootControllerTarget(configuration.dexed[instance_id].fc_assign); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_fc_assign)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 74: + configuration.dexed[instance_id].bc_range = constrain(sysex[5], BC_RANGE_MIN, BC_RANGE_MAX); + MicroDexed[instance_id]->setBreathControllerRange(configuration.dexed[instance_id].bc_range); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_bc_range)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 75: + configuration.dexed[instance_id].bc_assign = constrain(sysex[5], BC_ASSIGN_MIN, BC_ASSIGN_MAX); + MicroDexed[instance_id]->setBreathControllerTarget(configuration.dexed[instance_id].bc_assign); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_bc_assign)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 76: + configuration.dexed[instance_id].at_range = constrain(sysex[5], AT_RANGE_MIN, AT_RANGE_MAX); + MicroDexed[instance_id]->setAftertouchRange(configuration.dexed[instance_id].at_range); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_at_range)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + case 77: + configuration.dexed[instance_id].at_assign = constrain(sysex[5], AT_ASSIGN_MIN, AT_ASSIGN_MAX); + MicroDexed[instance_id]->setAftertouchTarget(configuration.dexed[instance_id].at_assign); + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_at_assign)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + break; + default: + MicroDexed[instance_id]->setVoiceDataElement(sysex[4], sysex[5]); // set function parameter + break; + } + MicroDexed[instance_id]->ControllersRefresh(); + } +#ifdef DEBUG + else + { + Serial.println(F("E: Unknown SysEx voice or function.")); + } +#endif + } + else if (len == 163) + { + int32_t bulk_checksum_calc = 0; + int8_t bulk_checksum = sysex[161]; + + // 1 Voice bulk upload +#ifdef DEBUG + Serial.println(F("One Voice bulk upload")); +#endif + + if ((sysex[3] & 0x7f) != 0) + { +#ifdef DEBUG + Serial.println(F("E: Not a SysEx voice bulk upload.")); +#endif + return; + } + + if (((sysex[4] << 7) | sysex[5]) != 0x9b) + { +#ifdef DEBUG + Serial.println(F("E: Wrong length for SysEx voice bulk upload (not 155).")); +#endif + return; + } + + // checksum calculation + for (uint8_t i = 0; i < 155 ; i++) + { + bulk_checksum_calc -= sysex[i + 6]; + } + bulk_checksum_calc &= 0x7f; + + if (bulk_checksum_calc != bulk_checksum) + { +#ifdef DEBUG + Serial.print(F("E: Checksum error for one voice [0x")); + Serial.print(bulk_checksum, HEX); + Serial.print(F("/0x")); + Serial.print(bulk_checksum_calc, HEX); + Serial.println(F("]")); +#endif + return; + } + + // fix voice name + for (uint8_t i = 0; i < 10; i++) + { + if (sysex[151 + i] > 126) // filter characters + sysex[151 + i] = 32; + } + + // load sysex-data into voice memory + MicroDexed[instance_id]->loadVoiceParameters(&sysex[6]); + +#ifdef DEBUG + show_patch(instance_id); +#endif + + // show voice name + strncpy(g_voice_name[instance_id], (char*)&sysex[151], VOICE_NAME_LEN - 1); + + if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) + { + LCDML.OTHER_updateFunc(); + LCDML.loop_menu(); + } + } + else if (len == 4104) + { + if (strlen(receive_bank_filename) > 0 && LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_sysex_receive_bank)) + { + int32_t bulk_checksum_calc = 0; + int8_t bulk_checksum = sysex[4102]; - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_voice_select)) - { - LCDML.OTHER_updateFunc(); - LCDML.loop_menu(); - } - } - else if (len == 4104) - { - if (strlen(receive_bank_filename) > 0 && LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_sysex_receive_bank)) - { - int32_t bulk_checksum_calc = 0; - int8_t bulk_checksum = sysex[4102]; - - // 1 Bank bulk upload - if ((sysex[3] & 0x7f) != 9) - { + // 1 Bank bulk upload + if ((sysex[3] & 0x7f) != 9) + { #ifdef DEBUG - Serial.println(F("E: Not a SysEx bank bulk upload.")); + Serial.println(F("E: Not a SysEx bank bulk upload.")); #endif - lcd.setCursor(0, 1); - lcd.print(F("Error (TYPE) ")); - delay(MESSAGE_WAIT_TIME); - LCDML.FUNC_goBackToMenu(); - return; - } + lcd.setCursor(0, 1); + lcd.print(F("Error (TYPE) ")); + delay(MESSAGE_WAIT_TIME); + LCDML.FUNC_goBackToMenu(); + return; + } #ifdef DEBUG - Serial.println(F("Bank bulk upload.")); + Serial.println(F("Bank bulk upload.")); #endif - if (((sysex[4] << 7) | sysex[5]) != 0x1000) - { + if (((sysex[4] << 7) | sysex[5]) != 0x1000) + { #ifdef DEBUG - Serial.println(F("E: Wrong length for SysEx bank bulk upload (not 4096).")); + Serial.println(F("E: Wrong length for SysEx bank bulk upload (not 4096).")); #endif - lcd.setCursor(0, 1); - lcd.print(F("Error (SIZE) ")); - delay(MESSAGE_WAIT_TIME); - LCDML.FUNC_goBackToMenu(); - return; - } + lcd.setCursor(0, 1); + lcd.print(F("Error (SIZE) ")); + delay(MESSAGE_WAIT_TIME); + LCDML.FUNC_goBackToMenu(); + return; + } #ifdef DEBUG - Serial.println(F("Bank type ok")); + Serial.println(F("Bank type ok")); #endif - // checksum calculation - for (uint16_t i = 0; i < 4096 ; i++) - { - bulk_checksum_calc -= sysex[i + 6]; - } - bulk_checksum_calc &= 0x7f; + // checksum calculation + for (uint16_t i = 0; i < 4096 ; i++) + { + bulk_checksum_calc -= sysex[i + 6]; + } + bulk_checksum_calc &= 0x7f; - if (bulk_checksum_calc != bulk_checksum) - { + if (bulk_checksum_calc != bulk_checksum) + { #ifdef DEBUG - Serial.print(F("E: Checksum error for bank [0x")); - Serial.print(bulk_checksum, HEX); - Serial.print(F("/0x")); - Serial.print(bulk_checksum_calc, HEX); - Serial.println(F("]")); + Serial.print(F("E: Checksum error for bank [0x")); + Serial.print(bulk_checksum, HEX); + Serial.print(F("/0x")); + Serial.print(bulk_checksum_calc, HEX); + Serial.println(F("]")); #endif - lcd.setCursor(0, 1); - lcd.print(F("Error (CHECKSUM)")); - delay(MESSAGE_WAIT_TIME); - LCDML.FUNC_goBackToMenu(); - return; - } + lcd.setCursor(0, 1); + lcd.print(F("Error (CHECKSUM)")); + delay(MESSAGE_WAIT_TIME); + LCDML.FUNC_goBackToMenu(); + return; + } #ifdef DEBUG - Serial.println(F("Bank checksum ok")); + Serial.println(F("Bank checksum ok")); #endif - if (save_sd_bank(receive_bank_filename, sysex)) - { + if (save_sd_bank(receive_bank_filename, sysex)) + { #ifdef DEBUG - Serial.print(F("Bank saved as [")); - Serial.print(receive_bank_filename); - Serial.println(F("]")); + Serial.print(F("Bank saved as [")); + Serial.print(receive_bank_filename); + Serial.println(F("]")); #endif - lcd.setCursor(0, 1); - lcd.print(F("Done. ")); - delay(MESSAGE_WAIT_TIME); - LCDML.FUNC_goBackToMenu(); - } - else - { + lcd.setCursor(0, 1); + lcd.print(F("Done. ")); + delay(MESSAGE_WAIT_TIME); + LCDML.FUNC_goBackToMenu(); + } + else + { #ifdef DEBUG - Serial.println(F("Error during saving bank as [")); - Serial.print(receive_bank_filename); - Serial.println(F("]")); + Serial.println(F("Error during saving bank as [")); + Serial.print(receive_bank_filename); + Serial.println(F("]")); #endif - lcd.setCursor(0, 1); - lcd.print(F("Error. ")); - delay(MESSAGE_WAIT_TIME); - LCDML.FUNC_goBackToMenu(); - } - memset(receive_bank_filename, 0, FILENAME_LEN); - } + lcd.setCursor(0, 1); + lcd.print(F("Error. ")); + delay(MESSAGE_WAIT_TIME); + LCDML.FUNC_goBackToMenu(); + } + memset(receive_bank_filename, 0, FILENAME_LEN); + } #ifdef DEBUG - else - Serial.println(F("E: Not in MIDI receive bank mode.")); + else + Serial.println(F("E: Not in MIDI receive bank mode.")); #endif - } + } #ifdef DEBUG - else - Serial.println(F("E: SysEx parameter length wrong.")); + else + Serial.println(F("E: SysEx parameter length wrong.")); #endif - } -} + } + } -void handleTimeCodeQuarterFrame(byte data) -{ - ; -} + void handleTimeCodeQuarterFrame(byte data) + { + ; + } -void handleAfterTouchPoly(byte inChannel, byte inNumber, byte inVelocity) -{ - ; -} + void handleAfterTouchPoly(byte inChannel, byte inNumber, byte inVelocity) + { + ; + } -void handleSongSelect(byte inSong) -{ - ; -} + void handleSongSelect(byte inSong) + { + ; + } -void handleTuneRequest(void) -{ - ; -} + void handleTuneRequest(void) + { + ; + } -void handleClock(void) -{ - if (midi_bpm_counter % 24 == 0) - { - midi_bpm = (60000.0f / float(midi_bpm_timer) + 0.5); + void handleClock(void) + { + if (midi_bpm_counter % 24 == 0) + { + midi_bpm = (60000.0f / float(midi_bpm_timer) + 0.5); - if (_midi_bpm > -1 && _midi_bpm != midi_bpm) - { + if (_midi_bpm > -1 && _midi_bpm != midi_bpm) + { #ifdef DEBUG - Serial.print(F("MIDI Clock: ")); - Serial.print(midi_bpm); - Serial.print(F(" bpm (")); - Serial.print(midi_bpm_timer, DEC); - Serial.println(F("ms per quarter)")); + Serial.print(F("MIDI Clock: ")); + Serial.print(midi_bpm); + Serial.print(F(" bpm (")); + Serial.print(midi_bpm_timer, DEC); + Serial.println(F("ms per quarter)")); #endif #ifdef USE_FX - /* - 1 1/16 = 6 ticks / 0.0625 - 2 1/16T = 9 ticks / 0.09375 - 3 1/8 = 12 ticks / 0.125 - 4 1/8T = 18 ticks / 0.1875 - 5 1/4 = 24 ticks / 0.25 - 6 1/4T = 36 ticks / 0.375 - 7 1/2 = 48 ticks / 0.5 - 8 1/2T = 72 ticks / 0.75 - 9 1/1 = 96 ticks / 1.0 - */ - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - if (configuration.fx.delay_sync[instance_id] > 0) - { - uint16_t midi_sync_delay_time = uint16_t(60000.0 * midi_ticks_factor[configuration.fx.delay_sync[instance_id]] / float(midi_bpm) + 0.5); - delay_fx[instance_id]->delay(0, constrain(midi_sync_delay_time, DELAY_TIME_MIN * 10, DELAY_TIME_MAX * 10)); -#ifdef DEBUG - Serial.print(F("Setting Delay-Sync of instance ")); - Serial.print(instance_id); - Serial.print(F(" to ")); - Serial.print(constrain(midi_sync_delay_time, DELAY_TIME_MIN * 10, DELAY_TIME_MAX * 10), DEC); - Serial.println(F(" ms")); -#endif - } - } -#endif - } - - _midi_bpm = midi_bpm; - midi_bpm_counter = 0; - midi_bpm_timer = 0; - } - - midi_bpm_counter++; -} - -void handleStart(void) -{ - midi_bpm_timer = 0; - midi_bpm_counter = 0; - _midi_bpm = -1; -} - -void handleContinue(void) -{ - ; -} - -void handleStop(void) -{ - ; -} - -void handleActiveSensing(void) -{ - ; -} - -void handleSystemReset(void) -{ - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { -#ifdef DEBUG - Serial.println(F("MIDI SYSEX RESET")); -#endif - MicroDexed[instance_id]->notesOff(); - MicroDexed[instance_id]->panic(); - MicroDexed[instance_id]->resetControllers(); - } -} - -/****************************************************************************** - MIDI HELPER - ******************************************************************************/ -bool checkMidiChannel(byte inChannel, uint8_t instance_id) -{ - // check for MIDI channel - if (configuration.dexed[instance_id].midi_channel == MIDI_CHANNEL_OMNI) - { - return (true); - } - else if (inChannel != configuration.dexed[instance_id].midi_channel) - { -#ifdef DEBUG - Serial.print(F("INSTANCE ")); - Serial.print(instance_id, DEC); - Serial.print(F(": Ignoring MIDI data on channel ")); - Serial.print(inChannel); - Serial.print(F("(listening on ")); - Serial.print(configuration.dexed[instance_id].midi_channel); - Serial.println(F(")")); -#endif - return (false); - } - return (true); -} - -void init_MIDI_send_CC(void) -{ -#ifdef DEBUG - Serial.println("init_MIDI_send_CC():"); -#endif - MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 7, configuration.dexed[selected_instance_id].sound_intensity); - MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 10, configuration.dexed[selected_instance_id].pan); - MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 91, configuration.fx.reverb_send[selected_instance_id]); - MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 93, configuration.fx.chorus_level[selected_instance_id]); - MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 94, configuration.dexed[selected_instance_id].tune); - MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 103, configuration.fx.filter_resonance[selected_instance_id]); - MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 104, configuration.fx.filter_cutoff[selected_instance_id]); - MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 105, configuration.fx.delay_time[selected_instance_id]); - MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 106, configuration.fx.delay_feedback[selected_instance_id]); - MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 107, configuration.fx.delay_level[selected_instance_id]); -} - -/****************************************************************************** - VOLUME HELPER - ******************************************************************************/ - -void set_volume(uint8_t v, uint8_t m) -{ - configuration.sys.vol = v; - - if (configuration.sys.vol > 100) - configuration.sys.vol = 100; - - configuration.sys.mono = m; - -#ifdef DEBUG - Serial.print(F("Setting volume: VOL=")); - Serial.println(v, DEC); -#endif - - volume_r.gain(pseudo_log_curve(v / 100.0)); - volume_l.gain(pseudo_log_curve(v / 100.0)); - - switch (m) - { - case 0: // stereo - stereo2mono.stereo(true); - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - mono2stereo[instance_id]->panorama(mapfloat(configuration.dexed[instance_id].pan, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); - break; - case 1: // mono both - stereo2mono.stereo(false); - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - mono2stereo[instance_id]->panorama(mapfloat(PANORAMA_DEFAULT, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); - break; - case 2: // mono right - volume_l.gain(0.0); - stereo2mono.stereo(false); - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - mono2stereo[instance_id]->panorama(mapfloat(PANORAMA_MAX, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); - break; - case 3: // mono left - volume_r.gain(0.0); - stereo2mono.stereo(false); - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - mono2stereo[instance_id]->panorama(mapfloat(PANORAMA_MIN, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); - break; - } -} - -/****************************************************************************** - EEPROM HELPER - ******************************************************************************/ - -void initial_values_from_eeprom(bool init) -{ - uint16_t _m_; - - if (init == true) - init_configuration(); - else - { - _m_ = (EEPROM[EEPROM_START_ADDRESS + offsetof(configuration_s, _marker_)] << 8) | EEPROM[EEPROM_START_ADDRESS + offsetof(configuration_s, _marker_) + 1]; - if (_m_ != EEPROM_MARKER) - { -#ifdef DEBUG - Serial.println(F("Found wrong EEPROM marker, initializing EEPROM...")); -#endif - configuration._marker_ = EEPROM_MARKER; - init_configuration(); - } - -#ifdef DEBUG - Serial.println(F("Loading initial system data from EEPROM.")); -#endif - - eeprom_get_performance(); - eeprom_get_sys(); - eeprom_get_fx(); - for (uint8_t i = 0; i < NUM_DEXED; i++) - { - eeprom_get_dexed(i); - } - -#ifdef DEBUG - Serial.println(F("OK, loaded!")); -#endif - - check_configuration(); - } - - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - set_voiceconfig_params(instance_id); - } - set_fx_params(); - set_sys_params(); - set_volume(configuration.sys.vol, configuration.sys.mono); - -#ifdef DEBUG - show_configuration(); -#endif -} - -void check_configuration(void) -{ - check_configuration_sys(); - check_configuration_fx(); - check_configuration_performance(); - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - check_configuration_dexed(instance_id); -} - -void check_configuration_sys(void) -{ - configuration.sys.instances = constrain(configuration.sys.instances, INSTANCES_MIN, INSTANCES_MAX); - configuration.sys.vol = constrain(configuration.sys.vol, VOLUME_MIN, VOLUME_MAX); - configuration.sys.mono = constrain(configuration.sys.mono, MONO_MIN, MONO_MAX); - configuration.sys.soft_midi_thru = constrain(configuration.sys.soft_midi_thru, SOFT_MIDI_THRU_MIN, SOFT_MIDI_THRU_MAX); - configuration.sys.performance_number = constrain(configuration.sys.performance_number, PERFORMANCE_NUM_MIN, PERFORMANCE_NUM_MAX); -} - -void check_configuration_fx(void) -{ + /* + 1 1/16 = 6 ticks / 0.0625 + 2 1/16T = 9 ticks / 0.09375 + 3 1/8 = 12 ticks / 0.125 + 4 1/8T = 18 ticks / 0.1875 + 5 1/4 = 24 ticks / 0.25 + 6 1/4T = 36 ticks / 0.375 + 7 1/2 = 48 ticks / 0.5 + 8 1/2T = 72 ticks / 0.75 + 9 1/1 = 96 ticks / 1.0 + */ + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + if (configuration.fx.delay_sync[instance_id] > 0) + { + uint16_t midi_sync_delay_time = uint16_t(60000.0 * midi_ticks_factor[configuration.fx.delay_sync[instance_id]] / float(midi_bpm) + 0.5); + delay_fx[instance_id]->delay(0, constrain(midi_sync_delay_time, DELAY_TIME_MIN * 10, DELAY_TIME_MAX * 10)); +#ifdef DEBUG + Serial.print(F("Setting Delay-Sync of instance ")); + Serial.print(instance_id); + Serial.print(F(" to ")); + Serial.print(constrain(midi_sync_delay_time, DELAY_TIME_MIN * 10, DELAY_TIME_MAX * 10), DEC); + Serial.println(F(" ms")); +#endif + } + } +#endif + } + + _midi_bpm = midi_bpm; + midi_bpm_counter = 0; + midi_bpm_timer = 0; + } + + midi_bpm_counter++; + } + + void handleStart(void) + { + midi_bpm_timer = 0; + midi_bpm_counter = 0; + _midi_bpm = -1; + } + + void handleContinue(void) + { + ; + } + + void handleStop(void) + { + ; + } + + void handleActiveSensing(void) + { + ; + } + + void handleSystemReset(void) + { + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { +#ifdef DEBUG + Serial.println(F("MIDI SYSEX RESET")); +#endif + MicroDexed[instance_id]->notesOff(); + MicroDexed[instance_id]->panic(); + MicroDexed[instance_id]->resetControllers(); + } + } + + /****************************************************************************** + MIDI HELPER + ******************************************************************************/ + bool checkMidiChannel(byte inChannel, uint8_t instance_id) + { + // check for MIDI channel + if (configuration.dexed[instance_id].midi_channel == MIDI_CHANNEL_OMNI) + { + return (true); + } + else if (inChannel != configuration.dexed[instance_id].midi_channel) + { +#ifdef DEBUG + Serial.print(F("INSTANCE ")); + Serial.print(instance_id, DEC); + Serial.print(F(": Ignoring MIDI data on channel ")); + Serial.print(inChannel); + Serial.print(F("(listening on ")); + Serial.print(configuration.dexed[instance_id].midi_channel); + Serial.println(F(")")); +#endif + return (false); + } + return (true); + } + + void init_MIDI_send_CC(void) + { +#ifdef DEBUG + Serial.println("init_MIDI_send_CC():"); +#endif + MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 7, configuration.dexed[selected_instance_id].sound_intensity); + MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 10, configuration.dexed[selected_instance_id].pan); + MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 91, configuration.fx.reverb_send[selected_instance_id]); + MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 93, configuration.fx.chorus_level[selected_instance_id]); + MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 94, configuration.dexed[selected_instance_id].tune); + MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 103, configuration.fx.filter_resonance[selected_instance_id]); + MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 104, configuration.fx.filter_cutoff[selected_instance_id]); + MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 105, configuration.fx.delay_time[selected_instance_id]); + MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 106, configuration.fx.delay_feedback[selected_instance_id]); + MD_sendControlChange(configuration.dexed[selected_instance_id].midi_channel, 107, configuration.fx.delay_level[selected_instance_id]); + } + + /****************************************************************************** + VOLUME HELPER + ******************************************************************************/ + + void set_volume(uint8_t v, uint8_t m) + { + configuration.sys.vol = v; + + if (configuration.sys.vol > 100) + configuration.sys.vol = 100; + + configuration.sys.mono = m; + +#ifdef DEBUG + Serial.print(F("Setting volume: VOL=")); + Serial.println(v, DEC); +#endif + + volume_r.gain(pseudo_log_curve(v / 100.0)); + volume_l.gain(pseudo_log_curve(v / 100.0)); + + switch (m) + { + case 0: // stereo + stereo2mono.stereo(true); + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + mono2stereo[instance_id]->panorama(mapfloat(configuration.dexed[instance_id].pan, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); + break; + case 1: // mono both + stereo2mono.stereo(false); + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + mono2stereo[instance_id]->panorama(mapfloat(PANORAMA_DEFAULT, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); + break; + case 2: // mono right + volume_l.gain(0.0); + stereo2mono.stereo(false); + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + mono2stereo[instance_id]->panorama(mapfloat(PANORAMA_MAX, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); + break; + case 3: // mono left + volume_r.gain(0.0); + stereo2mono.stereo(false); + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + mono2stereo[instance_id]->panorama(mapfloat(PANORAMA_MIN, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); + break; + } + } + + /****************************************************************************** + EEPROM HELPER + ******************************************************************************/ + + void initial_values_from_eeprom(bool init) + { + uint16_t _m_; + + if (init == true) + init_configuration(); + else + { + _m_ = (EEPROM[EEPROM_START_ADDRESS + offsetof(configuration_s, _marker_)] << 8) | EEPROM[EEPROM_START_ADDRESS + offsetof(configuration_s, _marker_) + 1]; + if (_m_ != EEPROM_MARKER) + { +#ifdef DEBUG + Serial.println(F("Found wrong EEPROM marker, initializing EEPROM...")); +#endif + configuration._marker_ = EEPROM_MARKER; + init_configuration(); + } + +#ifdef DEBUG + Serial.println(F("Loading initial system data from EEPROM.")); +#endif + + eeprom_get_performance(); + eeprom_get_sys(); + eeprom_get_fx(); + for (uint8_t i = 0; i < NUM_DEXED; i++) + { + eeprom_get_dexed(i); + } + +#ifdef DEBUG + Serial.println(F("OK, loaded!")); +#endif + + check_configuration(); + } + + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + set_voiceconfig_params(instance_id); + } + set_fx_params(); + set_sys_params(); + set_volume(configuration.sys.vol, configuration.sys.mono); + +#ifdef DEBUG + show_configuration(); +#endif + } + + void check_configuration(void) + { + check_configuration_sys(); + check_configuration_fx(); + check_configuration_performance(); + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + check_configuration_dexed(instance_id); + } + + void check_configuration_sys(void) + { + configuration.sys.instances = constrain(configuration.sys.instances, INSTANCES_MIN, INSTANCES_MAX); + configuration.sys.vol = constrain(configuration.sys.vol, VOLUME_MIN, VOLUME_MAX); + configuration.sys.mono = constrain(configuration.sys.mono, MONO_MIN, MONO_MAX); + configuration.sys.soft_midi_thru = constrain(configuration.sys.soft_midi_thru, SOFT_MIDI_THRU_MIN, SOFT_MIDI_THRU_MAX); + configuration.sys.performance_number = constrain(configuration.sys.performance_number, PERFORMANCE_NUM_MIN, PERFORMANCE_NUM_MAX); + } + + void check_configuration_fx(void) + { #ifdef USE_PLATEREVERB - configuration.fx.reverb_lowpass = constrain(configuration.fx.reverb_lowpass, REVERB_LOWPASS_MIN, REVERB_LOWPASS_MAX); - configuration.fx.reverb_lodamp = constrain(configuration.fx.reverb_lodamp, REVERB_LODAMP_MIN, REVERB_LODAMP_MAX); - configuration.fx.reverb_hidamp = constrain(configuration.fx.reverb_hidamp, REVERB_HIDAMP_MIN, REVERB_HIDAMP_MAX); - configuration.fx.reverb_diffusion = constrain(configuration.fx.reverb_diffusion, REVERB_DIFFUSION_MIN, REVERB_DIFFUSION_MAX); + configuration.fx.reverb_lowpass = constrain(configuration.fx.reverb_lowpass, REVERB_LOWPASS_MIN, REVERB_LOWPASS_MAX); + configuration.fx.reverb_lodamp = constrain(configuration.fx.reverb_lodamp, REVERB_LODAMP_MIN, REVERB_LODAMP_MAX); + configuration.fx.reverb_hidamp = constrain(configuration.fx.reverb_hidamp, REVERB_HIDAMP_MIN, REVERB_HIDAMP_MAX); + configuration.fx.reverb_diffusion = constrain(configuration.fx.reverb_diffusion, REVERB_DIFFUSION_MIN, REVERB_DIFFUSION_MAX); #else - configuration.fx.reverb_damping = constrain(configuration.fx.reverb_damping, REVERB_DAMPING_MIN, REVERB_DAMPING_MAX); -#endif - configuration.fx.reverb_roomsize = constrain(configuration.fx.reverb_roomsize, REVERB_ROOMSIZE_MIN, REVERB_ROOMSIZE_MAX); - configuration.fx.reverb_level = constrain(configuration.fx.reverb_level, REVERB_LEVEL_MIN, REVERB_LEVEL_MAX); - - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - configuration.fx.filter_cutoff[instance_id] = constrain(configuration.fx.filter_cutoff[instance_id], FILTER_CUTOFF_MIN, FILTER_CUTOFF_MAX); - configuration.fx.filter_resonance[instance_id] = constrain(configuration.fx.filter_resonance[instance_id], FILTER_RESONANCE_MIN, FILTER_RESONANCE_MAX); - configuration.fx.chorus_frequency[instance_id] = constrain(configuration.fx.chorus_frequency[instance_id], CHORUS_FREQUENCY_MIN, CHORUS_FREQUENCY_MAX); - configuration.fx.chorus_waveform[instance_id] = constrain(configuration.fx.chorus_waveform[instance_id], CHORUS_WAVEFORM_MIN, CHORUS_WAVEFORM_MAX); - configuration.fx.chorus_depth[instance_id] = constrain(configuration.fx.chorus_depth[instance_id], CHORUS_DEPTH_MIN, CHORUS_DEPTH_MAX); - configuration.fx.chorus_level[instance_id] = constrain(configuration.fx.chorus_level[instance_id], CHORUS_LEVEL_MIN, CHORUS_LEVEL_MAX); - configuration.fx.delay_time[instance_id] = constrain(configuration.fx.delay_time[instance_id], DELAY_TIME_MIN, DELAY_TIME_MAX); - configuration.fx.delay_feedback[instance_id] = constrain(configuration.fx.delay_feedback[instance_id], DELAY_FEEDBACK_MIN, DELAY_FEEDBACK_MAX); - configuration.fx.delay_level[instance_id] = constrain(configuration.fx.delay_level[instance_id], DELAY_LEVEL_MIN, DELAY_LEVEL_MAX); - configuration.fx.delay_sync[instance_id] = constrain(configuration.fx.delay_sync[instance_id], DELAY_SYNC_MIN, DELAY_SYNC_MAX); - configuration.fx.reverb_send[instance_id] = constrain(configuration.fx.reverb_send[instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX); - } -} - -void check_configuration_performance(void) -{ - configuration.performance.fx_number = constrain(configuration.performance.fx_number, FX_NUM_MIN, FX_NUM_MAX); - - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - configuration.performance.bank[instance_id] = constrain(configuration.performance.bank[instance_id], 0, MAX_BANKS - 1); - configuration.performance.voice[instance_id] = constrain(configuration.performance.voice[instance_id], 0, MAX_VOICES - 1); - configuration.performance.voiceconfig_number[instance_id] = constrain(configuration.performance.voiceconfig_number[instance_id], VOICECONFIG_NUM_MIN, VOICECONFIG_NUM_MAX); - } -} - -void check_configuration_dexed(uint8_t instance_id) -{ - configuration.dexed[instance_id].midi_channel = constrain(configuration.dexed[instance_id].midi_channel, MIDI_CHANNEL_MIN, MIDI_CHANNEL_MAX); - configuration.dexed[instance_id].lowest_note = constrain(configuration.dexed[instance_id].lowest_note, INSTANCE_LOWEST_NOTE_MIN, INSTANCE_LOWEST_NOTE_MAX); - configuration.dexed[instance_id].highest_note = constrain(configuration.dexed[instance_id].highest_note, INSTANCE_HIGHEST_NOTE_MIN, INSTANCE_HIGHEST_NOTE_MAX); - configuration.dexed[instance_id].sound_intensity = constrain(configuration.dexed[instance_id].sound_intensity, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX); - configuration.dexed[instance_id].pan = constrain(configuration.dexed[instance_id].pan, PANORAMA_MIN, PANORAMA_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].polyphony = constrain(configuration.dexed[instance_id].polyphony, POLYPHONY_MIN, POLYPHONY_MAX); - configuration.dexed[instance_id].velocity_level = constrain(configuration.dexed[instance_id].velocity_level, VELOCITY_LEVEL_MIN, VELOCITY_LEVEL_MAX); - configuration.dexed[instance_id].monopoly = constrain(configuration.dexed[instance_id].monopoly, MONOPOLY_MIN, MONOPOLY_MAX); - configuration.dexed[instance_id].note_refresh = constrain(configuration.dexed[instance_id].note_refresh, NOTE_REFRESH_MIN, NOTE_REFRESH_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].mw_mode = constrain(configuration.dexed[instance_id].mw_mode, MW_MODE_MIN, MW_MODE_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].fc_mode = constrain(configuration.dexed[instance_id].fc_mode, FC_MODE_MIN, FC_MODE_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].bc_mode = constrain(configuration.dexed[instance_id].bc_mode, BC_MODE_MIN, BC_MODE_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].at_mode = constrain(configuration.dexed[instance_id].at_mode, AT_MODE_MIN, AT_MODE_MAX); - configuration.dexed[instance_id].portamento_mode = constrain(configuration.dexed[instance_id].portamento_mode, PORTAMENTO_MODE_MIN, PORTAMENTO_MODE_MAX); - configuration.dexed[instance_id].portamento_glissando = constrain(configuration.dexed[instance_id].portamento_glissando, PORTAMENTO_GLISSANDO_MIN, PORTAMENTO_GLISSANDO_MAX); - configuration.dexed[instance_id].portamento_time = constrain(configuration.dexed[instance_id].portamento_time, PORTAMENTO_TIME_MIN, PORTAMENTO_TIME_MAX); - configuration.dexed[instance_id].op_enabled = constrain(configuration.dexed[instance_id].op_enabled, OP_ENABLED_MIN, OP_ENABLED_MAX); -} - -void init_configuration(void) -{ -#ifdef DEBUG - Serial.println(F("INITIALIZING CONFIGURATION")); -#endif - - configuration.sys.instances = INSTANCES_DEFAULT; - configuration.sys.vol = VOLUME_DEFAULT; - configuration.sys.mono = MONO_DEFAULT; - configuration.sys.soft_midi_thru = SOFT_MIDI_THRU_DEFAULT; - configuration.sys.performance_number = PERFORMANCE_NUM_DEFAULT; + configuration.fx.reverb_damping = constrain(configuration.fx.reverb_damping, REVERB_DAMPING_MIN, REVERB_DAMPING_MAX); +#endif + configuration.fx.reverb_roomsize = constrain(configuration.fx.reverb_roomsize, REVERB_ROOMSIZE_MIN, REVERB_ROOMSIZE_MAX); + configuration.fx.reverb_level = constrain(configuration.fx.reverb_level, REVERB_LEVEL_MIN, REVERB_LEVEL_MAX); + + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + configuration.fx.filter_cutoff[instance_id] = constrain(configuration.fx.filter_cutoff[instance_id], FILTER_CUTOFF_MIN, FILTER_CUTOFF_MAX); + configuration.fx.filter_resonance[instance_id] = constrain(configuration.fx.filter_resonance[instance_id], FILTER_RESONANCE_MIN, FILTER_RESONANCE_MAX); + configuration.fx.chorus_frequency[instance_id] = constrain(configuration.fx.chorus_frequency[instance_id], CHORUS_FREQUENCY_MIN, CHORUS_FREQUENCY_MAX); + configuration.fx.chorus_waveform[instance_id] = constrain(configuration.fx.chorus_waveform[instance_id], CHORUS_WAVEFORM_MIN, CHORUS_WAVEFORM_MAX); + configuration.fx.chorus_depth[instance_id] = constrain(configuration.fx.chorus_depth[instance_id], CHORUS_DEPTH_MIN, CHORUS_DEPTH_MAX); + configuration.fx.chorus_level[instance_id] = constrain(configuration.fx.chorus_level[instance_id], CHORUS_LEVEL_MIN, CHORUS_LEVEL_MAX); + configuration.fx.delay_time[instance_id] = constrain(configuration.fx.delay_time[instance_id], DELAY_TIME_MIN, DELAY_TIME_MAX); + configuration.fx.delay_feedback[instance_id] = constrain(configuration.fx.delay_feedback[instance_id], DELAY_FEEDBACK_MIN, DELAY_FEEDBACK_MAX); + configuration.fx.delay_level[instance_id] = constrain(configuration.fx.delay_level[instance_id], DELAY_LEVEL_MIN, DELAY_LEVEL_MAX); + configuration.fx.delay_sync[instance_id] = constrain(configuration.fx.delay_sync[instance_id], DELAY_SYNC_MIN, DELAY_SYNC_MAX); + configuration.fx.reverb_send[instance_id] = constrain(configuration.fx.reverb_send[instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX); + } + } + + void check_configuration_performance(void) + { + configuration.performance.fx_number = constrain(configuration.performance.fx_number, FX_NUM_MIN, FX_NUM_MAX); + + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + configuration.performance.bank[instance_id] = constrain(configuration.performance.bank[instance_id], 0, MAX_BANKS - 1); + configuration.performance.voice[instance_id] = constrain(configuration.performance.voice[instance_id], 0, MAX_VOICES - 1); + configuration.performance.voiceconfig_number[instance_id] = constrain(configuration.performance.voiceconfig_number[instance_id], VOICECONFIG_NUM_MIN, VOICECONFIG_NUM_MAX); + } + } + + void check_configuration_dexed(uint8_t instance_id) + { + configuration.dexed[instance_id].midi_channel = constrain(configuration.dexed[instance_id].midi_channel, MIDI_CHANNEL_MIN, MIDI_CHANNEL_MAX); + configuration.dexed[instance_id].lowest_note = constrain(configuration.dexed[instance_id].lowest_note, INSTANCE_LOWEST_NOTE_MIN, INSTANCE_LOWEST_NOTE_MAX); + configuration.dexed[instance_id].highest_note = constrain(configuration.dexed[instance_id].highest_note, INSTANCE_HIGHEST_NOTE_MIN, INSTANCE_HIGHEST_NOTE_MAX); + configuration.dexed[instance_id].sound_intensity = constrain(configuration.dexed[instance_id].sound_intensity, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX); + configuration.dexed[instance_id].pan = constrain(configuration.dexed[instance_id].pan, PANORAMA_MIN, PANORAMA_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].polyphony = constrain(configuration.dexed[instance_id].polyphony, POLYPHONY_MIN, POLYPHONY_MAX); + configuration.dexed[instance_id].velocity_level = constrain(configuration.dexed[instance_id].velocity_level, VELOCITY_LEVEL_MIN, VELOCITY_LEVEL_MAX); + configuration.dexed[instance_id].monopoly = constrain(configuration.dexed[instance_id].monopoly, MONOPOLY_MIN, MONOPOLY_MAX); + configuration.dexed[instance_id].note_refresh = constrain(configuration.dexed[instance_id].note_refresh, NOTE_REFRESH_MIN, NOTE_REFRESH_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].mw_mode = constrain(configuration.dexed[instance_id].mw_mode, MW_MODE_MIN, MW_MODE_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].fc_mode = constrain(configuration.dexed[instance_id].fc_mode, FC_MODE_MIN, FC_MODE_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].bc_mode = constrain(configuration.dexed[instance_id].bc_mode, BC_MODE_MIN, BC_MODE_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].at_mode = constrain(configuration.dexed[instance_id].at_mode, AT_MODE_MIN, AT_MODE_MAX); + configuration.dexed[instance_id].portamento_mode = constrain(configuration.dexed[instance_id].portamento_mode, PORTAMENTO_MODE_MIN, PORTAMENTO_MODE_MAX); + configuration.dexed[instance_id].portamento_glissando = constrain(configuration.dexed[instance_id].portamento_glissando, PORTAMENTO_GLISSANDO_MIN, PORTAMENTO_GLISSANDO_MAX); + configuration.dexed[instance_id].portamento_time = constrain(configuration.dexed[instance_id].portamento_time, PORTAMENTO_TIME_MIN, PORTAMENTO_TIME_MAX); + configuration.dexed[instance_id].op_enabled = constrain(configuration.dexed[instance_id].op_enabled, OP_ENABLED_MIN, OP_ENABLED_MAX); + } + + void init_configuration(void) + { +#ifdef DEBUG + Serial.println(F("INITIALIZING CONFIGURATION")); +#endif + + configuration.sys.instances = INSTANCES_DEFAULT; + configuration.sys.vol = VOLUME_DEFAULT; + configuration.sys.mono = MONO_DEFAULT; + configuration.sys.soft_midi_thru = SOFT_MIDI_THRU_DEFAULT; + configuration.sys.performance_number = PERFORMANCE_NUM_DEFAULT; #ifdef USE_PLATEREVERB - configuration.fx.reverb_lowpass = REVERB_LOWPASS_DEFAULT; - configuration.fx.reverb_lodamp = REVERB_LODAMP_DEFAULT; - configuration.fx.reverb_hidamp = REVERB_HIDAMP_DEFAULT; - configuration.fx.reverb_diffusion = REVERB_DIFFUSION_DEFAULT; + configuration.fx.reverb_lowpass = REVERB_LOWPASS_DEFAULT; + configuration.fx.reverb_lodamp = REVERB_LODAMP_DEFAULT; + configuration.fx.reverb_hidamp = REVERB_HIDAMP_DEFAULT; + configuration.fx.reverb_diffusion = REVERB_DIFFUSION_DEFAULT; #else - configuration.fx.reverb_damping = REVERB_DAMPING_DEFAULT; -#endif - - configuration.fx.reverb_roomsize = REVERB_ROOMSIZE_DEFAULT; - configuration.fx.reverb_level = REVERB_LEVEL_DEFAULT; - - configuration.performance.fx_number = FX_NUM_DEFAULT; - - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - configuration.performance.bank[instance_id] = SYSEXBANK_DEFAULT; - configuration.performance.voice[instance_id] = SYSEXSOUND_DEFAULT; - configuration.performance.voiceconfig_number[instance_id] = VOICECONFIG_NUM_DEFAULT; - - configuration.dexed[instance_id].midi_channel = DEFAULT_MIDI_CHANNEL; - configuration.dexed[instance_id].lowest_note = INSTANCE_LOWEST_NOTE_MIN; - configuration.dexed[instance_id].highest_note = INSTANCE_HIGHEST_NOTE_MAX; - configuration.dexed[instance_id].sound_intensity = SOUND_INTENSITY_DEFAULT; - configuration.dexed[instance_id].pan = PANORAMA_DEFAULT; - configuration.dexed[instance_id].transpose = TRANSPOSE_DEFAULT; - configuration.dexed[instance_id].tune = TUNE_DEFAULT; - configuration.dexed[instance_id].polyphony = POLYPHONY_DEFAULT; - configuration.dexed[instance_id].velocity_level = VELOCITY_LEVEL_DEFAULT; - configuration.dexed[instance_id].monopoly = MONOPOLY_DEFAULT; - configuration.dexed[instance_id].note_refresh = NOTE_REFRESH_DEFAULT; - configuration.dexed[instance_id].pb_range = PB_RANGE_DEFAULT; - configuration.dexed[instance_id].pb_step = PB_STEP_DEFAULT; - configuration.dexed[instance_id].mw_range = MW_RANGE_DEFAULT; - configuration.dexed[instance_id].mw_assign = MW_ASSIGN_DEFAULT; - configuration.dexed[instance_id].mw_mode = MW_MODE_DEFAULT; - configuration.dexed[instance_id].fc_range = FC_RANGE_DEFAULT; - configuration.dexed[instance_id].fc_assign = FC_ASSIGN_DEFAULT; - configuration.dexed[instance_id].fc_mode = FC_MODE_DEFAULT; - configuration.dexed[instance_id].bc_range = BC_RANGE_DEFAULT; - configuration.dexed[instance_id].bc_assign = BC_ASSIGN_DEFAULT; - configuration.dexed[instance_id].bc_mode = BC_MODE_DEFAULT; - configuration.dexed[instance_id].at_range = AT_RANGE_DEFAULT; - configuration.dexed[instance_id].at_assign = AT_ASSIGN_DEFAULT; - configuration.dexed[instance_id].at_mode = AT_MODE_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; - - configuration.fx.filter_cutoff[instance_id] = FILTER_CUTOFF_DEFAULT; - configuration.fx.filter_resonance[instance_id] = FILTER_RESONANCE_DEFAULT; - configuration.fx.chorus_frequency[instance_id] = CHORUS_FREQUENCY_DEFAULT; - configuration.fx.chorus_waveform[instance_id] = CHORUS_WAVEFORM_DEFAULT; - configuration.fx.chorus_depth[instance_id] = CHORUS_DEPTH_DEFAULT; - configuration.fx.chorus_level[instance_id] = CHORUS_LEVEL_DEFAULT; - configuration.fx.delay_time[instance_id] = DELAY_TIME_DEFAULT / 10; - configuration.fx.delay_feedback[instance_id] = DELAY_FEEDBACK_DEFAULT; - configuration.fx.delay_level[instance_id] = DELAY_LEVEL_DEFAULT; - configuration.fx.delay_sync[instance_id] = DELAY_SYNC_DEFAULT; - configuration.fx.reverb_send[instance_id] = REVERB_SEND_DEFAULT; - - configuration.performance.bank[instance_id] = SYSEXBANK_DEFAULT; - configuration.performance.voice[instance_id] = SYSEXSOUND_DEFAULT; - - configuration.dexed[instance_id].polyphony = POLYPHONY_DEFAULT; - - MicroDexed[instance_id]->ControllersRefresh(); - } - - set_volume(configuration.sys.vol, configuration.sys.mono); - - eeprom_update(); -} - -void eeprom_update(void) -{ - uint8_t* c = (uint8_t*)&configuration; - for (uint16_t i = 0; i < sizeof(configuration); i++) - EEPROM.update(EEPROM_START_ADDRESS + i, c[i]); -} - -void eeprom_update_sys(void) -{ - uint8_t* c = (uint8_t*)&configuration.sys; - - for (uint16_t i = 0; i < sizeof(configuration.sys); i++) - EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, sys) + i, c[i]); - -#ifdef DEBUG - Serial.println(F("Updating EEPROM sys.")); -#endif -} - -bool eeprom_get_sys(void) -{ - EEPROM.get(EEPROM_START_ADDRESS + offsetof(configuration_s, sys), configuration.sys); - return (true); -} - -void eeprom_update_fx(void) -{ - uint8_t* c = (uint8_t*)&configuration.fx; - - for (uint16_t i = 0; i < sizeof(configuration.fx); i++) - EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx) + i, c[i]); - -#ifdef DEBUG - Serial.println(F("Updating EEPROM fx.")); -#endif -} - -bool eeprom_get_fx(void) -{ - EEPROM.get(EEPROM_START_ADDRESS + offsetof(configuration_s, fx), configuration.fx); - return (true); -} - -void eeprom_update_dexed(uint8_t instance_id) -{ + configuration.fx.reverb_damping = REVERB_DAMPING_DEFAULT; +#endif + + configuration.fx.reverb_roomsize = REVERB_ROOMSIZE_DEFAULT; + configuration.fx.reverb_level = REVERB_LEVEL_DEFAULT; + + configuration.performance.fx_number = FX_NUM_DEFAULT; + + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + configuration.performance.bank[instance_id] = SYSEXBANK_DEFAULT; + configuration.performance.voice[instance_id] = SYSEXSOUND_DEFAULT; + configuration.performance.voiceconfig_number[instance_id] = VOICECONFIG_NUM_DEFAULT; + + configuration.dexed[instance_id].midi_channel = DEFAULT_MIDI_CHANNEL; + configuration.dexed[instance_id].lowest_note = INSTANCE_LOWEST_NOTE_MIN; + configuration.dexed[instance_id].highest_note = INSTANCE_HIGHEST_NOTE_MAX; + configuration.dexed[instance_id].sound_intensity = SOUND_INTENSITY_DEFAULT; + configuration.dexed[instance_id].pan = PANORAMA_DEFAULT; + configuration.dexed[instance_id].transpose = TRANSPOSE_DEFAULT; + configuration.dexed[instance_id].tune = TUNE_DEFAULT; + configuration.dexed[instance_id].polyphony = POLYPHONY_DEFAULT; + configuration.dexed[instance_id].velocity_level = VELOCITY_LEVEL_DEFAULT; + configuration.dexed[instance_id].monopoly = MONOPOLY_DEFAULT; + configuration.dexed[instance_id].note_refresh = NOTE_REFRESH_DEFAULT; + configuration.dexed[instance_id].pb_range = PB_RANGE_DEFAULT; + configuration.dexed[instance_id].pb_step = PB_STEP_DEFAULT; + configuration.dexed[instance_id].mw_range = MW_RANGE_DEFAULT; + configuration.dexed[instance_id].mw_assign = MW_ASSIGN_DEFAULT; + configuration.dexed[instance_id].mw_mode = MW_MODE_DEFAULT; + configuration.dexed[instance_id].fc_range = FC_RANGE_DEFAULT; + configuration.dexed[instance_id].fc_assign = FC_ASSIGN_DEFAULT; + configuration.dexed[instance_id].fc_mode = FC_MODE_DEFAULT; + configuration.dexed[instance_id].bc_range = BC_RANGE_DEFAULT; + configuration.dexed[instance_id].bc_assign = BC_ASSIGN_DEFAULT; + configuration.dexed[instance_id].bc_mode = BC_MODE_DEFAULT; + configuration.dexed[instance_id].at_range = AT_RANGE_DEFAULT; + configuration.dexed[instance_id].at_assign = AT_ASSIGN_DEFAULT; + configuration.dexed[instance_id].at_mode = AT_MODE_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; + + configuration.fx.filter_cutoff[instance_id] = FILTER_CUTOFF_DEFAULT; + configuration.fx.filter_resonance[instance_id] = FILTER_RESONANCE_DEFAULT; + configuration.fx.chorus_frequency[instance_id] = CHORUS_FREQUENCY_DEFAULT; + configuration.fx.chorus_waveform[instance_id] = CHORUS_WAVEFORM_DEFAULT; + configuration.fx.chorus_depth[instance_id] = CHORUS_DEPTH_DEFAULT; + configuration.fx.chorus_level[instance_id] = CHORUS_LEVEL_DEFAULT; + configuration.fx.delay_time[instance_id] = DELAY_TIME_DEFAULT / 10; + configuration.fx.delay_feedback[instance_id] = DELAY_FEEDBACK_DEFAULT; + configuration.fx.delay_level[instance_id] = DELAY_LEVEL_DEFAULT; + configuration.fx.delay_sync[instance_id] = DELAY_SYNC_DEFAULT; + configuration.fx.reverb_send[instance_id] = REVERB_SEND_DEFAULT; + + configuration.performance.bank[instance_id] = SYSEXBANK_DEFAULT; + configuration.performance.voice[instance_id] = SYSEXSOUND_DEFAULT; + + configuration.dexed[instance_id].polyphony = POLYPHONY_DEFAULT; + + MicroDexed[instance_id]->ControllersRefresh(); + } + + set_volume(configuration.sys.vol, configuration.sys.mono); + + eeprom_update(); + } + + void eeprom_update(void) + { + uint8_t* c = (uint8_t*)&configuration; + for (uint16_t i = 0; i < sizeof(configuration); i++) + EEPROM.update(EEPROM_START_ADDRESS + i, c[i]); + } + + void eeprom_update_sys(void) + { + uint8_t* c = (uint8_t*)&configuration.sys; + + for (uint16_t i = 0; i < sizeof(configuration.sys); i++) + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, sys) + i, c[i]); + +#ifdef DEBUG + Serial.println(F("Updating EEPROM sys.")); +#endif + } + + bool eeprom_get_sys(void) + { + EEPROM.get(EEPROM_START_ADDRESS + offsetof(configuration_s, sys), configuration.sys); + return (true); + } + + void eeprom_update_fx(void) + { + uint8_t* c = (uint8_t*)&configuration.fx; + + for (uint16_t i = 0; i < sizeof(configuration.fx); i++) + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx) + i, c[i]); + +#ifdef DEBUG + Serial.println(F("Updating EEPROM fx.")); +#endif + } + + bool eeprom_get_fx(void) + { + EEPROM.get(EEPROM_START_ADDRESS + offsetof(configuration_s, fx), configuration.fx); + return (true); + } + + void eeprom_update_dexed(uint8_t instance_id) + { #if NUM_DEXED == 1 - uint8_t* c = (uint8_t*)&configuration.dexed[0]; + uint8_t* c = (uint8_t*)&configuration.dexed[0]; - for (uint16_t i = 0; i < sizeof(configuration.dexed[0]); i++) - EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, dexed[0]) + i, c[i]); + for (uint16_t i = 0; i < sizeof(configuration.dexed[0]); i++) + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, dexed[0]) + i, c[i]); #else - uint8_t* c; + uint8_t* c; - if (instance_id == 0) - c = (uint8_t*)&configuration.dexed[0]; - else - c = (uint8_t*)&configuration.dexed[1]; + if (instance_id == 0) + c = (uint8_t*)&configuration.dexed[0]; + else + c = (uint8_t*)&configuration.dexed[1]; - for (uint16_t i = 0; i < sizeof(configuration.dexed[instance_id]); i++) - { - if (instance_id == 0) - EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, dexed[0]) + i, c[i]); - else - EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, dexed[1]) + i, c[i]); - } + for (uint16_t i = 0; i < sizeof(configuration.dexed[instance_id]); i++) + { + if (instance_id == 0) + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, dexed[0]) + i, c[i]); + else + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, dexed[1]) + i, c[i]); + } #endif #ifdef DEBUG - Serial.print(F("Updating EEPROM dexed (instance ")); - Serial.print(instance_id); - Serial.println(F(").")); + Serial.print(F("Updating EEPROM dexed (instance ")); + Serial.print(instance_id); + Serial.println(F(").")); #endif -} + } -bool eeprom_get_dexed(uint8_t instance_id) -{ - for (uint8_t instance_id = 0; instance_id < MAX_DEXED; instance_id++) - { - if (instance_id == 0) - EEPROM.get(EEPROM_START_ADDRESS + offsetof(configuration_s, dexed[0]), configuration.dexed[0]); - else - EEPROM.get(EEPROM_START_ADDRESS + offsetof(configuration_s, dexed[1]), configuration.dexed[1]); - } - return (true); -} + bool eeprom_get_dexed(uint8_t instance_id) + { + for (uint8_t instance_id = 0; instance_id < MAX_DEXED; instance_id++) + { + if (instance_id == 0) + EEPROM.get(EEPROM_START_ADDRESS + offsetof(configuration_s, dexed[0]), configuration.dexed[0]); + else + EEPROM.get(EEPROM_START_ADDRESS + offsetof(configuration_s, dexed[1]), configuration.dexed[1]); + } + return (true); + } -void eeprom_update_performance() -{ - EEPROM.put(EEPROM_START_ADDRESS + offsetof(configuration_s, performance), configuration.performance); + void eeprom_update_performance() + { + EEPROM.put(EEPROM_START_ADDRESS + offsetof(configuration_s, performance), configuration.performance); #ifdef DEBUG - Serial.println(F("Updating EEPROM performance.")); + Serial.println(F("Updating EEPROM performance.")); #endif -} + } -bool eeprom_get_performance() -{ - EEPROM.get(EEPROM_START_ADDRESS + offsetof(configuration_s, performance), configuration.performance); + bool eeprom_get_performance() + { + EEPROM.get(EEPROM_START_ADDRESS + offsetof(configuration_s, performance), configuration.performance); #ifdef DEBUG - Serial.println(F("Getting EEPROM performance.")); + Serial.println(F("Getting EEPROM performance.")); #endif - return (true); -} + return (true); + } -/****************************************************************************** - PARAMETER-HELPERS -******************************************************************************/ + /****************************************************************************** + PARAMETER-HELPERS + ******************************************************************************/ -void set_fx_params(void) -{ + void set_fx_params(void) + { #if defined(USE_FX) - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - // CHORUS - switch (configuration.fx.chorus_waveform[instance_id]) - { - case 0: - chorus_modulator[instance_id]->begin(WAVEFORM_TRIANGLE); - break; - case 1: - chorus_modulator[instance_id]->begin(WAVEFORM_SINE); - break; - default: - chorus_modulator[instance_id]->begin(WAVEFORM_TRIANGLE); - } - chorus_modulator[instance_id]->phase(0); - chorus_modulator[instance_id]->frequency(configuration.fx.chorus_frequency[instance_id] / 10.0); - chorus_modulator[instance_id]->amplitude(mapfloat(configuration.fx.chorus_depth[instance_id], CHORUS_DEPTH_MIN, CHORUS_DEPTH_MAX, 0.0, 1.0)); - chorus_modulator[instance_id]->offset(0.0); + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + // CHORUS + switch (configuration.fx.chorus_waveform[instance_id]) + { + case 0: + chorus_modulator[instance_id]->begin(WAVEFORM_TRIANGLE); + break; + case 1: + chorus_modulator[instance_id]->begin(WAVEFORM_SINE); + break; + default: + chorus_modulator[instance_id]->begin(WAVEFORM_TRIANGLE); + } + chorus_modulator[instance_id]->phase(0); + chorus_modulator[instance_id]->frequency(configuration.fx.chorus_frequency[instance_id] / 10.0); + chorus_modulator[instance_id]->amplitude(mapfloat(configuration.fx.chorus_depth[instance_id], CHORUS_DEPTH_MIN, CHORUS_DEPTH_MAX, 0.0, 1.0)); + chorus_modulator[instance_id]->offset(0.0); #if MOD_FILTER_OUTPUT == MOD_BUTTERWORTH_FILTER_OUTPUT - // Butterworth filter, 12 db/octave - modchorus_filter[instance_id]->setLowpass(0, MOD_FILTER_CUTOFF_HZ, 0.707); + // Butterworth filter, 12 db/octave + modchorus_filter[instance_id]->setLowpass(0, MOD_FILTER_CUTOFF_HZ, 0.707); #elif MOD_FILTER_OUTPUT == MOD_LINKWITZ_RILEY_FILTER_OUTPUT - // Linkwitz-Riley filter, 48 dB/octave - modchorus_filter[instance_id]->setLowpass(0, MOD_FILTER_CUTOFF_HZ, 0.54); - modchorus_filter[instance_id]->setLowpass(1, MOD_FILTER_CUTOFF_HZ, 1.3); - modchorus_filter[instance_id]->setLowpass(2, MOD_FILTER_CUTOFF_HZ, 0.54); - modchorus_filter[instance_id]->setLowpass(3, MOD_FILTER_CUTOFF_HZ, 1.3); -#endif - chorus_mixer[instance_id]->gain(0, 1.0); - chorus_mixer[instance_id]->gain(1, mapfloat(configuration.fx.chorus_level[instance_id], CHORUS_LEVEL_MIN, CHORUS_LEVEL_MAX, 0.0, 0.5)); - - // DELAY - delay_mixer[instance_id]->gain(0, 1.0); - delay_mixer[instance_id]->gain(1, pseudo_log_curve(mapfloat(configuration.fx.delay_level[instance_id], DELAY_LEVEL_MIN, DELAY_LEVEL_MAX, 0.0, 1.0))); - delay_fb_mixer[instance_id]->gain(0, 1.0); - delay_fb_mixer[instance_id]->gain(1, pseudo_log_curve(mapfloat(configuration.fx.delay_feedback[instance_id], DELAY_FEEDBACK_MIN, DELAY_FEEDBACK_MAX, 0.0, 1.0))); - if (configuration.fx.delay_level[selected_instance_id] <= DELAY_LEVEL_MIN) - delay_fx[instance_id]->disable(0); - else - delay_fx[instance_id]->delay(0, constrain(configuration.fx.delay_time[instance_id], DELAY_TIME_MIN, DELAY_TIME_MAX) * 10); - - // REVERB SEND - reverb_mixer_r.gain(instance_id, pseudo_log_curve(mapfloat(configuration.fx.reverb_send[instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, 1.0))); - reverb_mixer_l.gain(instance_id, pseudo_log_curve(mapfloat(configuration.fx.reverb_send[instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, 1.0))); - - // DEXED FILTER - MicroDexed[instance_id]->setFilterResonance(mapfloat(configuration.fx.filter_resonance[instance_id], FILTER_RESONANCE_MIN, FILTER_RESONANCE_MAX, 1.0, 0.0)); - MicroDexed[instance_id]->setFilterCutoff(mapfloat(configuration.fx.filter_cutoff[instance_id], FILTER_CUTOFF_MIN, FILTER_CUTOFF_MAX, 1.0, 0.0)); - MicroDexed[instance_id]->doRefreshVoice(); - } - - // REVERB + // Linkwitz-Riley filter, 48 dB/octave + modchorus_filter[instance_id]->setLowpass(0, MOD_FILTER_CUTOFF_HZ, 0.54); + modchorus_filter[instance_id]->setLowpass(1, MOD_FILTER_CUTOFF_HZ, 1.3); + modchorus_filter[instance_id]->setLowpass(2, MOD_FILTER_CUTOFF_HZ, 0.54); + modchorus_filter[instance_id]->setLowpass(3, MOD_FILTER_CUTOFF_HZ, 1.3); +#endif + chorus_mixer[instance_id]->gain(0, 1.0); + chorus_mixer[instance_id]->gain(1, mapfloat(configuration.fx.chorus_level[instance_id], CHORUS_LEVEL_MIN, CHORUS_LEVEL_MAX, 0.0, 0.5)); + + // DELAY + delay_mixer[instance_id]->gain(0, 1.0); + delay_mixer[instance_id]->gain(1, pseudo_log_curve(mapfloat(configuration.fx.delay_level[instance_id], DELAY_LEVEL_MIN, DELAY_LEVEL_MAX, 0.0, 1.0))); + delay_fb_mixer[instance_id]->gain(0, 1.0); + delay_fb_mixer[instance_id]->gain(1, pseudo_log_curve(mapfloat(configuration.fx.delay_feedback[instance_id], DELAY_FEEDBACK_MIN, DELAY_FEEDBACK_MAX, 0.0, 1.0))); + if (configuration.fx.delay_level[selected_instance_id] <= DELAY_LEVEL_MIN) + delay_fx[instance_id]->disable(0); + else + delay_fx[instance_id]->delay(0, constrain(configuration.fx.delay_time[instance_id], DELAY_TIME_MIN, DELAY_TIME_MAX) * 10); + + // REVERB SEND + reverb_mixer_r.gain(instance_id, pseudo_log_curve(mapfloat(configuration.fx.reverb_send[instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, 1.0))); + reverb_mixer_l.gain(instance_id, pseudo_log_curve(mapfloat(configuration.fx.reverb_send[instance_id], REVERB_SEND_MIN, REVERB_SEND_MAX, 0.0, 1.0))); + + // DEXED FILTER + MicroDexed[instance_id]->setFilterResonance(mapfloat(configuration.fx.filter_resonance[instance_id], FILTER_RESONANCE_MIN, FILTER_RESONANCE_MAX, 1.0, 0.0)); + MicroDexed[instance_id]->setFilterCutoff(mapfloat(configuration.fx.filter_cutoff[instance_id], FILTER_CUTOFF_MIN, FILTER_CUTOFF_MAX, 1.0, 0.0)); + MicroDexed[instance_id]->doRefreshVoice(); + } + + // REVERB #ifdef USE_PLATEREVERB - reverb.size(mapfloat(configuration.fx.reverb_roomsize, REVERB_ROOMSIZE_MIN, REVERB_ROOMSIZE_MAX, 0.0, 1.0)); - reverb.lowpass(mapfloat(configuration.fx.reverb_lowpass, REVERB_LOWPASS_MIN, REVERB_LOWPASS_MAX, 0.0, 1.0)); - reverb.lodamp(mapfloat(configuration.fx.reverb_lodamp, REVERB_LODAMP_MIN, REVERB_LODAMP_MAX, 0.0, 1.0)); - reverb.hidamp(mapfloat(configuration.fx.reverb_hidamp, REVERB_HIDAMP_MIN, REVERB_HIDAMP_MAX, 0.0, 1.0)); - reverb.diffusion(mapfloat(configuration.fx.reverb_diffusion, REVERB_DIFFUSION_MIN, REVERB_DIFFUSION_MAX, 0.0, 1.0)); + reverb.size(mapfloat(configuration.fx.reverb_roomsize, REVERB_ROOMSIZE_MIN, REVERB_ROOMSIZE_MAX, 0.0, 1.0)); + reverb.lowpass(mapfloat(configuration.fx.reverb_lowpass, REVERB_LOWPASS_MIN, REVERB_LOWPASS_MAX, 0.0, 1.0)); + reverb.lodamp(mapfloat(configuration.fx.reverb_lodamp, REVERB_LODAMP_MIN, REVERB_LODAMP_MAX, 0.0, 1.0)); + reverb.hidamp(mapfloat(configuration.fx.reverb_hidamp, REVERB_HIDAMP_MIN, REVERB_HIDAMP_MAX, 0.0, 1.0)); + reverb.diffusion(mapfloat(configuration.fx.reverb_diffusion, REVERB_DIFFUSION_MIN, REVERB_DIFFUSION_MAX, 0.0, 1.0)); #else - freeverb.roomsize(mapfloat(configuration.fx.reverb_roomsize, REVERB_ROOMSIZE_MIN, REVERB_ROOMSIZE_MAX, 0.0, 1.0)); - freeverb.damping(mapfloat(configuration.fx.reverb_damping, REVERB_DAMPING_MIN, REVERB_DAMPING_MAX, 0.0, 1.0)); + freeverb.roomsize(mapfloat(configuration.fx.reverb_roomsize, REVERB_ROOMSIZE_MIN, REVERB_ROOMSIZE_MAX, 0.0, 1.0)); + freeverb.damping(mapfloat(configuration.fx.reverb_damping, REVERB_DAMPING_MIN, REVERB_DAMPING_MAX, 0.0, 1.0)); #endif #if NUM_DRUMS > 0 #ifdef USE_FX - reverb_mixer_r.gain(2, 1.0); // Drums Reverb-Send - reverb_mixer_l.gain(2, 1.0); // Drums Reverb-Send + reverb_mixer_r.gain(2, 1.0); // Drums Reverb-Send + reverb_mixer_l.gain(2, 1.0); // Drums Reverb-Send #endif #endif - master_mixer_r.gain(3, pseudo_log_curve(mapfloat(configuration.fx.reverb_level, REVERB_LEVEL_MIN, REVERB_LEVEL_MAX, 0.0, 1.0))); - master_mixer_l.gain(3, pseudo_log_curve(mapfloat(configuration.fx.reverb_level, REVERB_LEVEL_MIN, REVERB_LEVEL_MAX, 0.0, 1.0))); + master_mixer_r.gain(3, pseudo_log_curve(mapfloat(configuration.fx.reverb_level, REVERB_LEVEL_MIN, REVERB_LEVEL_MAX, 0.0, 1.0))); + master_mixer_l.gain(3, pseudo_log_curve(mapfloat(configuration.fx.reverb_level, REVERB_LEVEL_MIN, REVERB_LEVEL_MAX, 0.0, 1.0))); #endif #ifdef SGTL5000_AUDIO_ENHANCE - sgtl5000_1.eqBands( - mapfloat(configuration.fx.eq_bass, EQ_BASS_MIN, EQ_BASS_MAX, -1.0, 1.0), - mapfloat(configuration.fx.eq_midbass, EQ_MIDBASS_MIN, EQ_MIDBASS_MAX, -1.0, 1.0), - mapfloat(configuration.fx.eq_mid, EQ_MID_MIN, EQ_MID_MAX, -1.0, 1.0), - mapfloat(configuration.fx.eq_midtreble, EQ_MIDTREBLE_MIN, EQ_MIDTREBLE_MAX, -1.0, 1.0), - mapfloat(configuration.fx.eq_treble, EQ_TREBLE_MIN, EQ_TREBLE_MAX, -1.0, 1.0) - ); -#endif - - init_MIDI_send_CC(); -} - -void set_voiceconfig_params(uint8_t instance_id) -{ - // INIT PEAK MIXER - microdexed_peak_mixer.gain(instance_id, 1.0); - - // Controller - MicroDexed[instance_id]->setMaxNotes(configuration.dexed[instance_id].polyphony); - MicroDexed[instance_id]->setPBController(configuration.dexed[instance_id].pb_range, configuration.dexed[instance_id].pb_step); - MicroDexed[instance_id]->setMWController(configuration.dexed[instance_id].mw_range, configuration.dexed[instance_id].mw_assign, configuration.dexed[instance_id].mw_mode); - MicroDexed[instance_id]->setFCController(configuration.dexed[instance_id].fc_range, configuration.dexed[instance_id].fc_assign, configuration.dexed[instance_id].fc_mode); - MicroDexed[instance_id]->setBCController(configuration.dexed[instance_id].bc_range, configuration.dexed[instance_id].bc_assign, configuration.dexed[instance_id].bc_mode); - MicroDexed[instance_id]->setATController(configuration.dexed[instance_id].at_range, configuration.dexed[instance_id].at_assign, configuration.dexed[instance_id].at_mode); - MicroDexed[instance_id]->ControllersRefresh(); - MicroDexed[instance_id]->setOPAll(configuration.dexed[instance_id].op_enabled); - MicroDexed[instance_id]->doRefreshVoice(); - MicroDexed[instance_id]->setMonoMode(configuration.dexed[instance_id].monopoly); - - // Dexed output level - MicroDexed[instance_id]->setGain(mapfloat(configuration.dexed[instance_id].sound_intensity, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX, 0.0, SOUND_INTENSITY_AMP_MAX)); - - // PANORAMA - mono2stereo[instance_id]->panorama(mapfloat(configuration.dexed[instance_id].pan, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); -} - -void set_sys_params(void) -{ - // set initial volume - set_volume(configuration.sys.vol, configuration.sys.mono); -} - -/****************************************************************************** - HELPERS - ******************************************************************************/ - -// https://www.reddit.com/r/Teensy/comments/7r19uk/reset_and_reboot_teensy_lc_via_code/ + sgtl5000_1.eqBands( + mapfloat(configuration.fx.eq_bass, EQ_BASS_MIN, EQ_BASS_MAX, -1.0, 1.0), + mapfloat(configuration.fx.eq_midbass, EQ_MIDBASS_MIN, EQ_MIDBASS_MAX, -1.0, 1.0), + mapfloat(configuration.fx.eq_mid, EQ_MID_MIN, EQ_MID_MAX, -1.0, 1.0), + mapfloat(configuration.fx.eq_midtreble, EQ_MIDTREBLE_MIN, EQ_MIDTREBLE_MAX, -1.0, 1.0), + mapfloat(configuration.fx.eq_treble, EQ_TREBLE_MIN, EQ_TREBLE_MAX, -1.0, 1.0) + ); +#endif + + init_MIDI_send_CC(); + } + + void set_voiceconfig_params(uint8_t instance_id) + { + // INIT PEAK MIXER + microdexed_peak_mixer.gain(instance_id, 1.0); + + // Controller + MicroDexed[instance_id]->setMaxNotes(configuration.dexed[instance_id].polyphony); + MicroDexed[instance_id]->setPBController(configuration.dexed[instance_id].pb_range, configuration.dexed[instance_id].pb_step); + MicroDexed[instance_id]->setMWController(configuration.dexed[instance_id].mw_range, configuration.dexed[instance_id].mw_assign, configuration.dexed[instance_id].mw_mode); + MicroDexed[instance_id]->setFCController(configuration.dexed[instance_id].fc_range, configuration.dexed[instance_id].fc_assign, configuration.dexed[instance_id].fc_mode); + MicroDexed[instance_id]->setBCController(configuration.dexed[instance_id].bc_range, configuration.dexed[instance_id].bc_assign, configuration.dexed[instance_id].bc_mode); + MicroDexed[instance_id]->setATController(configuration.dexed[instance_id].at_range, configuration.dexed[instance_id].at_assign, configuration.dexed[instance_id].at_mode); + MicroDexed[instance_id]->ControllersRefresh(); + MicroDexed[instance_id]->setOPAll(configuration.dexed[instance_id].op_enabled); + MicroDexed[instance_id]->doRefreshVoice(); + MicroDexed[instance_id]->setMonoMode(configuration.dexed[instance_id].monopoly); + + // Dexed output level + MicroDexed[instance_id]->setGain(mapfloat(configuration.dexed[instance_id].sound_intensity, SOUND_INTENSITY_MIN, SOUND_INTENSITY_MAX, 0.0, SOUND_INTENSITY_AMP_MAX)); + + // PANORAMA + mono2stereo[instance_id]->panorama(mapfloat(configuration.dexed[instance_id].pan, PANORAMA_MIN, PANORAMA_MAX, -1.0, 1.0)); + } + + void set_sys_params(void) + { + // set initial volume + set_volume(configuration.sys.vol, configuration.sys.mono); + } + + /****************************************************************************** + HELPERS + ******************************************************************************/ + + // https://www.reddit.com/r/Teensy/comments/7r19uk/reset_and_reboot_teensy_lc_via_code/ #define SCB_AIRCR (*(volatile uint32_t *)0xE000ED0C) // Application Interrupt and Reset Control location -void _softRestart(void) -{ - Serial.end(); //clears the serial monitor if used - SCB_AIRCR = 0x05FA0004; //write value for restart -} - -float pseudo_log_curve(float value) -{ - //return (mapfloat(_pseudo_log * arm_sin_f32(value), 0.0, _pseudo_log * arm_sin_f32(1.0), 0.0, 1.0)); - //return (1 - sqrt(1 - value * value)); - //return (pow(2, value) - 1); - return (pow(value, 2.2)); -} - -uint32_t crc32(byte * calc_start, uint16_t calc_bytes) // base code from https://www.arduino.cc/en/Tutorial/EEPROMCrc -{ - const uint32_t crc_table[16] = - { - 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, - 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, - 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, - 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c - }; - uint32_t crc = ~0L; - - for (byte* index = calc_start ; index < (calc_start + calc_bytes) ; ++index) - { - crc = crc_table[(crc ^ *index) & 0x0f] ^ (crc >> 4); - crc = crc_table[(crc ^ (*index >> 4)) & 0x0f] ^ (crc >> 4); - crc = ~crc; - } - - return (crc); -} - -void generate_version_string(char* buffer, uint8_t len) -{ - char tmp[3]; - - memset(buffer, 0, len); - strncat(buffer, VERSION, len); + void _softRestart(void) + { + Serial.end(); //clears the serial monitor if used + SCB_AIRCR = 0x05FA0004; //write value for restart + } + + float pseudo_log_curve(float value) + { + //return (mapfloat(_pseudo_log * arm_sin_f32(value), 0.0, _pseudo_log * arm_sin_f32(1.0), 0.0, 1.0)); + //return (1 - sqrt(1 - value * value)); + //return (pow(2, value) - 1); + return (pow(value, 2.2)); + } + + uint32_t crc32(byte * calc_start, uint16_t calc_bytes) // base code from https://www.arduino.cc/en/Tutorial/EEPROMCrc + { + const uint32_t crc_table[16] = + { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c + }; + uint32_t crc = ~0L; + + for (byte* index = calc_start ; index < (calc_start + calc_bytes) ; ++index) + { + crc = crc_table[(crc ^ *index) & 0x0f] ^ (crc >> 4); + crc = crc_table[(crc ^ (*index >> 4)) & 0x0f] ^ (crc >> 4); + crc = ~crc; + } + + return (crc); + } + + void generate_version_string(char* buffer, uint8_t len) + { + char tmp[3]; + + memset(buffer, 0, len); + strncat(buffer, VERSION, len); #if defined(TEENSY3_5) - strncat(buffer, "-3.5", 4); + strncat(buffer, "-3.5", 4); #elif defined(TEENSY3_6) - strncat(buffer, "-3.6", 4); + strncat(buffer, "-3.6", 4); #elif defined(TEENSY4) - strncat(buffer, "-4.0", 4); + strncat(buffer, "-4.0", 4); #endif #if defined(USE_FX) - strncat(buffer, "FX", 2); + strncat(buffer, "FX", 2); #endif #if defined(MAX_NOTES) - strncat(buffer, "-", 1); - itoa (MAX_NOTES, tmp, 10); - strncat(buffer, tmp, 2); + strncat(buffer, "-", 1); + itoa (MAX_NOTES, tmp, 10); + strncat(buffer, tmp, 2); #endif -} + } #ifdef DISPLAY_LCD_SPI -void change_disp_sd(bool disp) -{ - if (sd_card > 0) - { - digitalWrite(sd_card, disp); - digitalWrite(U8X8_CS_PIN, !disp); - } -} + void change_disp_sd(bool disp) + { + if (sd_card > 0) + { + digitalWrite(sd_card, disp); + digitalWrite(U8X8_CS_PIN, !disp); + } + } #endif -uint8_t check_sd_cards(void) -{ - uint8_t ret = 0; + uint8_t check_sd_cards(void) + { + uint8_t ret = 0; - memset(sd_string, 0, sizeof(sd_string)); + memset(sd_string, 0, sizeof(sd_string)); - for (uint8_t i = 0; i < sizeof(cs_pins); i++) - { + for (uint8_t i = 0; i < sizeof(cs_pins); i++) + { #ifdef DEBUG - Serial.print(F("Checking CS pin ")); - Serial.print(cs_pins[i], DEC); - Serial.println(F(" for SD card")); + Serial.print(F("Checking CS pin ")); + Serial.print(cs_pins[i], DEC); + Serial.println(F(" for SD card")); #endif - SPI.setMOSI(mosi_pins[i]); - SPI.setSCK(sck_pins[i]); + SPI.setMOSI(mosi_pins[i]); + SPI.setSCK(sck_pins[i]); - if (SD.begin(cs_pins[i]) == true) - { + if (SD.begin(cs_pins[i]) == true) + { #ifdef DEBUG - Serial.print(F("Found. Using pin ")); - Serial.println(cs_pins[i], DEC); + Serial.print(F("Found. Using pin ")); + Serial.println(cs_pins[i], DEC); #endif - ret = cs_pins[i]; - break; - } - } + ret = cs_pins[i]; + break; + } + } - if (ret >= 0) - { - if (!card.init(SPI_HALF_SPEED, ret)) - { + if (ret >= 0) + { + if (!card.init(SPI_HALF_SPEED, ret)) + { #ifdef DEBUG - Serial.println(F("SD card initialization failed.")); + Serial.println(F("SD card initialization failed.")); #endif - ret = -1; - } - } + ret = -1; + } + } - if (ret >= 0) - { + if (ret >= 0) + { #ifdef DEBUG - Serial.print(F("Card type: ")); + Serial.print(F("Card type: ")); #endif - switch (card.type()) { - case SD_CARD_TYPE_SD1: - sprintf(sd_string, "%-5s", "SD1"); + switch (card.type()) { + case SD_CARD_TYPE_SD1: + sprintf(sd_string, "%-5s", "SD1"); #ifdef DEBUG - Serial.println(F("SD1")); + Serial.println(F("SD1")); #endif - break; - case SD_CARD_TYPE_SD2: - sprintf(sd_string, "%-5s", "SD2"); + break; + case SD_CARD_TYPE_SD2: + sprintf(sd_string, "%-5s", "SD2"); #ifdef DEBUG - Serial.println(F("SD2")); + Serial.println(F("SD2")); #endif - break; - case SD_CARD_TYPE_SDHC: - sprintf(sd_string, "%-5s", "SD2"); + break; + case SD_CARD_TYPE_SDHC: + sprintf(sd_string, "%-5s", "SD2"); #ifdef DEBUG - Serial.println(F("SDHC")); + Serial.println(F("SDHC")); #endif - break; - default: - sprintf(sd_string, "%-5s", "UKNW"); + break; + default: + sprintf(sd_string, "%-5s", "UKNW"); #ifdef DEBUG - Serial.println(F("Unknown")); + Serial.println(F("Unknown")); #endif - } + } - if (!volume.init(card)) - { + if (!volume.init(card)) + { #ifdef DEBUG - Serial.println(F("Could not find FAT16/FAT32 partition.")); + Serial.println(F("Could not find FAT16/FAT32 partition.")); #endif - ret = -1; - } - } + ret = -1; + } + } - if (ret >= 0) - { - uint32_t volumesize; + if (ret >= 0) + { + uint32_t volumesize; - volumesize = volume.blocksPerCluster() * volume.clusterCount() / 2097152; + volumesize = volume.blocksPerCluster() * volume.clusterCount() / 2097152; #ifdef DEBUG - Serial.print(F("Volume type is FAT")); - Serial.println(volume.fatType(), DEC); - Serial.print(F("Volume size (GB): ")); - Serial.println(volumesize); + Serial.print(F("Volume type is FAT")); + Serial.println(volume.fatType(), DEC); + Serial.print(F("Volume size (GB): ")); + Serial.println(volumesize); #endif - sprintf(sd_string + 5, "FAT%2d %02dGB", volume.fatType(), int(volumesize)); - } + sprintf(sd_string + 5, "FAT%2d %02dGB", volume.fatType(), int(volumesize)); + } #ifdef DEBUG - Serial.println(sd_string); + Serial.println(sd_string); #endif - return (ret); -} + return (ret); + } -void check_and_create_directories(void) -{ - if (sd_card > 0) - { - uint8_t i; - char tmp[FILENAME_LEN]; + void check_and_create_directories(void) + { + if (sd_card > 0) + { + uint8_t i; + char tmp[FILENAME_LEN]; #ifdef DEBUG - Serial.println(F("Directory check... ")); + Serial.println(F("Directory check... ")); #endif - // create directories for banks - for (i = 0; i < MAX_BANKS; i++) - { - sprintf(tmp, "/%d", i); - if (!SD.exists(tmp)) - { + // create directories for banks + for (i = 0; i < MAX_BANKS; i++) + { + sprintf(tmp, "/%d", i); + if (!SD.exists(tmp)) + { #ifdef DEBUG - Serial.print(F("Creating directory ")); - Serial.println(tmp); + Serial.print(F("Creating directory ")); + Serial.println(tmp); #endif - SD.mkdir(tmp); - } - } + SD.mkdir(tmp); + } + } - // create directories for configuration files - sprintf(tmp, "/%s", VOICE_CONFIG_PATH); - if (!SD.exists(tmp)) - { + // create directories for configuration files + sprintf(tmp, "/%s", VOICE_CONFIG_PATH); + if (!SD.exists(tmp)) + { #ifdef DEBUG - Serial.print(F("Creating directory ")); - Serial.println(tmp); + Serial.print(F("Creating directory ")); + Serial.println(tmp); #endif - SD.mkdir(tmp); - } - sprintf(tmp, "/%s", PERFORMANCE_CONFIG_PATH); - if (!SD.exists(tmp)) - { + SD.mkdir(tmp); + } + sprintf(tmp, "/%s", PERFORMANCE_CONFIG_PATH); + if (!SD.exists(tmp)) + { #ifdef DEBUG - Serial.print(F("Creating directory ")); - Serial.println(tmp); + Serial.print(F("Creating directory ")); + Serial.println(tmp); #endif - SD.mkdir(tmp); - } - sprintf(tmp, "/%s", FX_CONFIG_PATH); - if (!SD.exists(tmp)) - { + SD.mkdir(tmp); + } + sprintf(tmp, "/%s", FX_CONFIG_PATH); + if (!SD.exists(tmp)) + { #ifdef DEBUG - Serial.print(F("Creating directory ")); - Serial.println(tmp); + Serial.print(F("Creating directory ")); + Serial.println(tmp); #endif - SD.mkdir(tmp); - } - sprintf(tmp, "/%s", FAV_CONFIG_PATH); - if (!SD.exists(tmp)) - { + SD.mkdir(tmp); + } + sprintf(tmp, "/%s", FAV_CONFIG_PATH); + if (!SD.exists(tmp)) + { #ifdef DEBUG - Serial.print(F("Creating directory ")); - Serial.println(tmp); + Serial.print(F("Creating directory ")); + Serial.println(tmp); #endif - SD.mkdir(tmp); - } - sprintf(tmp, "/%s", SEQ_CONFIG_PATH); - if (!SD.exists(tmp)) - { + SD.mkdir(tmp); + } + sprintf(tmp, "/%s", SEQ_CONFIG_PATH); + if (!SD.exists(tmp)) + { #ifdef DEBUG - Serial.print(F("Creating directory ")); - Serial.println(tmp); + Serial.print(F("Creating directory ")); + Serial.println(tmp); #endif - SD.mkdir(tmp); - } + SD.mkdir(tmp); + } - //check if updated Fav-System is ready or if setup has to run once. - - sprintf(tmp, "/%s/fav-v2", FAV_CONFIG_PATH); - if (!SD.exists(tmp)) { - - // Clear now obsolte marker files from Favs. - // Only needs to run once. - for (uint8_t i = 0; i < MAX_BANKS; i++) - { - sprintf(tmp, "/%s/%d/hasfav", FAV_CONFIG_PATH, i); -#ifdef DEBUG - Serial.print(F("Delete Marker File")); - Serial.println(tmp); -#endif - if (SD.exists(tmp)) - SD.remove(tmp); - } - // Remove empty Folders. rmdir will only remove strictly emtpy folders, which is the desired result. - // Only needs to run once. - for (uint8_t i = 0; i < MAX_BANKS; i++) - { - sprintf(tmp, "/%s/%d", FAV_CONFIG_PATH, i); -#ifdef DEBUG - Serial.print(F("Delete empty folder ")); - Serial.println(tmp); -#endif - if (SD.exists(tmp)) - SD.rmdir(tmp); - } - sprintf(tmp, "/%s/fav-v2", FAV_CONFIG_PATH); - if (!SD.exists(tmp)) - SD.mkdir(tmp); // Set Marker so that the Cleanup loops only run once. - } -#ifdef DEBUG - else - Serial.println(F("No SD card for directory check available.")); -#endif - } -} + //check if updated Fav-System is ready or if setup has to run once. -/****************************************************************************** - DEBUG HELPER - ******************************************************************************/ -#if defined (DEBUG) && defined (SHOW_CPU_LOAD_MSEC) -void show_cpu_and_mem_usage(void) -{ - uint32_t sum_xrun = 0; - uint16_t sum_render_time_max = 0; + sprintf(tmp, "/%s/fav-v2", FAV_CONFIG_PATH); + if (!SD.exists(tmp)) { - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - sum_xrun += MicroDexed[instance_id]->getXRun(); - sum_render_time_max += MicroDexed[instance_id]->getRenderTimeMax(); - MicroDexed[instance_id]->resetRenderTimeMax(); - } - if (AudioProcessorUsageMax() > 99.9) - { - cpumax++; + // Clear now obsolte marker files from Favs. + // Only needs to run once. + for (uint8_t i = 0; i < MAX_BANKS; i++) + { + sprintf(tmp, "/%s/%d/hasfav", FAV_CONFIG_PATH, i); #ifdef DEBUG - Serial.print(F("*")); + Serial.print(F("Delete Marker File")); + Serial.println(tmp); #endif - } + if (SD.exists(tmp)) + SD.remove(tmp); + } + // Remove empty Folders. rmdir will only remove strictly emtpy folders, which is the desired result. + // Only needs to run once. + for (uint8_t i = 0; i < MAX_BANKS; i++) + { + sprintf(tmp, "/%s/%d", FAV_CONFIG_PATH, i); #ifdef DEBUG - else - Serial.print(F(" ")); - Serial.print(F("CPU:")); - Serial.print(AudioProcessorUsage(), 2); - Serial.print(F("%|CPUMAX:")); - Serial.print(AudioProcessorUsageMax(), 2); - Serial.print(F("%|CPUMAXCNT:")); - Serial.print(cpumax, DEC); + Serial.print(F("Delete empty folder ")); + Serial.println(tmp); +#endif + if (SD.exists(tmp)) + SD.rmdir(tmp); + } + sprintf(tmp, "/%s/fav-v2", FAV_CONFIG_PATH); + if (!SD.exists(tmp)) + SD.mkdir(tmp); // Set Marker so that the Cleanup loops only run once. + } +#ifdef DEBUG + else + Serial.println(F("No SD card for directory check available.")); +#endif + } + } + + /****************************************************************************** + DEBUG HELPER + ******************************************************************************/ +#if defined (DEBUG) && defined (SHOW_CPU_LOAD_MSEC) + void show_cpu_and_mem_usage(void) + { + uint32_t sum_xrun = 0; + uint16_t sum_render_time_max = 0; + + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + sum_xrun += MicroDexed[instance_id]->getXRun(); + sum_render_time_max += MicroDexed[instance_id]->getRenderTimeMax(); + MicroDexed[instance_id]->resetRenderTimeMax(); + } + if (AudioProcessorUsageMax() > 99.9) + { + cpumax++; +#ifdef DEBUG + Serial.print(F("*")); +#endif + } +#ifdef DEBUG + else + Serial.print(F(" ")); + Serial.print(F("CPU:")); + Serial.print(AudioProcessorUsage(), 2); + Serial.print(F("%|CPUMAX:")); + Serial.print(AudioProcessorUsageMax(), 2); + Serial.print(F("%|CPUMAXCNT:")); + Serial.print(cpumax, DEC); #ifdef TEENSY4 - Serial.print(F("|CPUTEMP:")); - Serial.print(tempmonGetTemp(), 2); - Serial.print(F("C|MEM:")); + Serial.print(F("|CPUTEMP:")); + Serial.print(tempmonGetTemp(), 2); + Serial.print(F("C|MEM:")); #else - Serial.print(F("|MEM:")); -#endif - Serial.print(AudioMemoryUsage(), DEC); - Serial.print(F("|MEMMAX:")); - Serial.print(AudioMemoryUsageMax(), DEC); - Serial.print(F("|AUDIO_MEM_MAX:")); - Serial.print(AUDIO_MEM, DEC); - Serial.print(F("|RENDERTIMEMAX:")); - Serial.print(sum_render_time_max, DEC); - Serial.print(F("|XRUN:")); - Serial.print(sum_xrun, DEC); - Serial.print(F("|PEAKR:")); - Serial.print(peak_r, DEC); - Serial.print(F("|PEAKL:")); - Serial.print(peak_l, DEC); - Serial.print(F("|PEAKMD:")); - Serial.print(peak_dexed, DEC); - Serial.print(F("|ACTPEAKMD:")); - Serial.print(peak_dexed_value, 1); - Serial.print(F("|BLOCKSIZE:")); - Serial.print(AUDIO_BLOCK_SAMPLES, DEC); - Serial.print(F("|RAM:")); - Serial.print(FreeMem(), DEC); - - Serial.print(F("|ACTVOICES:")); - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - Serial.print(instance_id, DEC); - Serial.print(F("=")); - Serial.print(active_voices[instance_id], DEC); - Serial.print(F("/")); - Serial.print(MAX_NOTES / NUM_DEXED, DEC); - if (instance_id != NUM_DEXED - 1) - Serial.print(F(",")); - } - Serial.println(); -#endif - AudioProcessorUsageMaxReset(); - AudioMemoryUsageMaxReset(); -} -#endif - -#ifdef DEBUG -void show_configuration(void) -{ - Serial.println(); - Serial.println(F("CONFIGURATION:")); - Serial.println(F("System")); - Serial.print(F(" Instances ")); Serial.println(configuration.sys.instances, DEC); - Serial.print(F(" Volume ")); Serial.println(configuration.sys.vol, DEC); - Serial.print(F(" Mono ")); Serial.println(configuration.sys.mono, DEC); - Serial.print(F(" Soft MIDI Thru ")); Serial.println(configuration.sys.soft_midi_thru, DEC); - Serial.println(F("FX")); - Serial.print(F(" Reverb Roomsize ")); Serial.println(configuration.fx.reverb_roomsize, DEC); - Serial.print(F(" Reverb Level ")); Serial.println(configuration.fx.reverb_level, DEC); + Serial.print(F("|MEM:")); +#endif + Serial.print(AudioMemoryUsage(), DEC); + Serial.print(F("|MEMMAX:")); + Serial.print(AudioMemoryUsageMax(), DEC); + Serial.print(F("|AUDIO_MEM_MAX:")); + Serial.print(AUDIO_MEM, DEC); + Serial.print(F("|RENDERTIMEMAX:")); + Serial.print(sum_render_time_max, DEC); + Serial.print(F("|XRUN:")); + Serial.print(sum_xrun, DEC); + Serial.print(F("|PEAKR:")); + Serial.print(peak_r, DEC); + Serial.print(F("|PEAKL:")); + Serial.print(peak_l, DEC); + Serial.print(F("|PEAKMD:")); + Serial.print(peak_dexed, DEC); + Serial.print(F("|ACTPEAKMD:")); + Serial.print(peak_dexed_value, 1); + Serial.print(F("|BLOCKSIZE:")); + Serial.print(AUDIO_BLOCK_SAMPLES, DEC); + Serial.print(F("|RAM:")); + Serial.print(FreeMem(), DEC); + + Serial.print(F("|ACTVOICES:")); + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + Serial.print(instance_id, DEC); + Serial.print(F("=")); + Serial.print(active_voices[instance_id], DEC); + Serial.print(F("/")); + Serial.print(MAX_NOTES / NUM_DEXED, DEC); + if (instance_id != NUM_DEXED - 1) + Serial.print(F(",")); + } + Serial.println(); +#endif + AudioProcessorUsageMaxReset(); + AudioMemoryUsageMaxReset(); + } +#endif + +#ifdef DEBUG + void show_configuration(void) + { + Serial.println(); + Serial.println(F("CONFIGURATION:")); + Serial.println(F("System")); + Serial.print(F(" Instances ")); Serial.println(configuration.sys.instances, DEC); + Serial.print(F(" Volume ")); Serial.println(configuration.sys.vol, DEC); + Serial.print(F(" Mono ")); Serial.println(configuration.sys.mono, DEC); + Serial.print(F(" Soft MIDI Thru ")); Serial.println(configuration.sys.soft_midi_thru, DEC); + Serial.println(F("FX")); + Serial.print(F(" Reverb Roomsize ")); Serial.println(configuration.fx.reverb_roomsize, DEC); + Serial.print(F(" Reverb Level ")); Serial.println(configuration.fx.reverb_level, DEC); #ifdef USE_PLATEREVERB - Serial.print(F(" Reverb Lowpass ")); Serial.println(configuration.fx.reverb_lowpass, DEC); - Serial.print(F(" Reverb Lodamp ")); Serial.println(configuration.fx.reverb_lodamp, DEC); - Serial.print(F(" Reverb Hidamp ")); Serial.println(configuration.fx.reverb_hidamp, DEC); - Serial.print(F(" Reverb Diffusion ")); Serial.println(configuration.fx.reverb_diffusion, DEC); + Serial.print(F(" Reverb Lowpass ")); Serial.println(configuration.fx.reverb_lowpass, DEC); + Serial.print(F(" Reverb Lodamp ")); Serial.println(configuration.fx.reverb_lodamp, DEC); + Serial.print(F(" Reverb Hidamp ")); Serial.println(configuration.fx.reverb_hidamp, DEC); + Serial.print(F(" Reverb Diffusion ")); Serial.println(configuration.fx.reverb_diffusion, DEC); #else - Serial.print(F(" Reverb Damping ")); Serial.println(configuration.fx.reverb_damping, DEC); -#endif - - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - Serial.print(F("Dexed instance ")); - Serial.println(instance_id, DEC); - Serial.print(F(" MIDI-Channel ")); Serial.println(configuration.dexed[instance_id].midi_channel, DEC); - Serial.print(F(" Lowest Note ")); Serial.println(configuration.dexed[instance_id].lowest_note, DEC); - Serial.print(F(" Highest Note ")); Serial.println(configuration.dexed[instance_id].highest_note, DEC); - Serial.print(F(" Filter Cutoff ")); Serial.println(configuration.fx.filter_cutoff[instance_id], DEC); - Serial.print(F(" Filter Resonance ")); Serial.println(configuration.fx.filter_resonance[instance_id], DEC); - Serial.print(F(" Chorus Frequency ")); Serial.println(configuration.fx.chorus_frequency[instance_id], DEC); - Serial.print(F(" Chorus Waveform ")); Serial.println(configuration.fx.chorus_waveform[instance_id], DEC); - Serial.print(F(" Chorus Depth ")); Serial.println(configuration.fx.chorus_depth[instance_id], DEC); - Serial.print(F(" Chorus Level ")); Serial.println(configuration.fx.chorus_level[instance_id], DEC); - Serial.print(F(" Delay Time ")); Serial.println(configuration.fx.delay_time[instance_id], DEC); - Serial.print(F(" Delay Feedback ")); Serial.println(configuration.fx.delay_feedback[instance_id], DEC); - Serial.print(F(" Delay Level ")); Serial.println(configuration.fx.delay_level[instance_id], DEC); - Serial.print(F(" Delay Sync ")); Serial.println(configuration.fx.delay_sync[instance_id], DEC); - Serial.print(F(" Reverb Send ")); Serial.println(configuration.fx.reverb_send[instance_id], DEC); - Serial.print(F(" Sound Intensity ")); Serial.println(configuration.dexed[instance_id].sound_intensity, DEC); - Serial.print(F(" Panorama ")); Serial.println(configuration.dexed[instance_id].pan, 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(" Mono/Poly ")); Serial.println(configuration.dexed[instance_id].monopoly, DEC); - Serial.print(F(" Note Refresh ")); Serial.println(configuration.dexed[instance_id].note_refresh, 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(" Modwheel Mode ")); Serial.println(configuration.dexed[instance_id].mw_mode, 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(" Footctrl Mode ")); Serial.println(configuration.dexed[instance_id].fc_mode, 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(" Breathctrl Mode ")); Serial.println(configuration.dexed[instance_id].bc_mode, 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(" Aftertouch Mode ")); Serial.println(configuration.dexed[instance_id].at_mode, 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(F("Performance")); - for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) - { - Serial.print(F(" Bank ")); Serial.print(instance_id, DEC); Serial.print(" "); Serial.println(configuration.performance.bank[instance_id], DEC); - Serial.print(F(" Voice ")); Serial.print(instance_id, DEC); Serial.print(" "); Serial.println(configuration.performance.voice[instance_id], DEC); - } - Serial.print(F(" FX-Number ")); Serial.println(configuration.performance.fx_number, DEC); - - Serial.println(); - Serial.flush(); -} - -void show_patch(uint8_t instance_id) -{ - char vn[VOICE_NAME_LEN]; - - Serial.print(F("INSTANCE ")); - Serial.println(instance_id, DEC); - - memset(vn, 0, sizeof(vn)); - Serial.println(F("+==========================================================================================================+")); - for (int8_t i = 5; i >= 0; --i) - { - Serial.println(F("+==========================================================================================================+")); - Serial.print(F("| OP")); - Serial.print(6 - i, DEC); - Serial.println(F(" |")); - Serial.println(F("+======+======+======+======+======+======+======+======+================+================+================+")); - Serial.println(F("| R1 | R2 | R3 | R4 | L1 | L2 | L3 | L4 | LEV_SCL_BRK_PT | SCL_LEFT_DEPTH | SCL_RGHT_DEPTH |")); - Serial.println(F("+------+------+------+------+------+------+------+------+----------------+----------------+----------------+")); - Serial.print("| "); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_R1)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_R2)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_R3)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_R4)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_L1)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_L2)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_L3)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_L4)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_LEV_SCL_BRK_PT)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_SCL_LEFT_DEPTH)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_SCL_RGHT_DEPTH)); - Serial.println(F(" |")); - Serial.println(F("+======+======+======+======+======+===+==+==+===+======+====+========+==+====+=======+===+================+")); - Serial.println(F("| SCL_L_CURVE | SCL_R_CURVE | RT_SCALE | AMS | KVS | OUT_LEV | OP_MOD | FRQ_C | FRQ_F | DETUNE |")); - Serial.println(F("+-------------+-------------+----------+-----+-----+---------+--------+-------+-------+--------------------+")); - Serial.print(F("| ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_SCL_LEFT_CURVE)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_SCL_RGHT_CURVE)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_OSC_RATE_SCALE)); - Serial.print(F(" |")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_AMP_MOD_SENS)); - Serial.print(F(" |")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_KEY_VEL_SENS)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_OUTPUT_LEV)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_OSC_MODE)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_FREQ_COARSE)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_FREQ_FINE)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_OSC_DETUNE)); - Serial.println(F(" |")); - } - Serial.println(F("+=======+=====+=+=======+===+===+======++====+==+==+====+====+==+======+======+=====+=+====================+")); - Serial.println(F("| PR1 | PR2 | PR3 | PR4 | PL1 | PL2 | PL3 | PL4 | ALG | FB | OKS | TRANSPOSE |")); - Serial.println(F("+-------+-------+-------+-------+-------+-------+-------+-------+------+------+-----+----------------------+")); - Serial.print(F("| ")); - for (int8_t i = 0; i < 8; i++) - { - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + i)); - Serial.print(F(" | ")); - } - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_ALGORITHM)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_FEEDBACK)); - Serial.print(F(" |")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_OSC_KEY_SYNC)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_TRANSPOSE)); - Serial.println(F(" |")); - Serial.println(F("+=======+=+=====+===+===+=====+=+=======+=======+==+====+=====+=+======++=====+=====+======================+")); - Serial.println(F("| LFO SPD | LFO DLY | LFO PMD | LFO AMD | LFO SYNC | LFO WAVE | LFO PMS | NAME |")); - Serial.println(F("+---------+---------+---------+---------+----------+----------+---------+----------------------------------+")); - Serial.print(F("| ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_SPEED)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_DELAY)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_DEP)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_AMP_MOD_DEP)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_SYNC)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_WAVE)); - Serial.print(F(" | ")); - SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_SENS)); - Serial.print(F(" | ")); - MicroDexed[instance_id]->getName(vn); - Serial.print(vn); - Serial.println(F(" |")); - Serial.println(F("+=========+=========+=========+=========+==========+==========+=========+==================================+")); - Serial.println(F("+==========================================================================================================+")); -} - -void SerialPrintFormatInt3(uint8_t num) -{ - char buf[4]; - sprintf(buf, "%3d", num); - Serial.print(buf); -} + Serial.print(F(" Reverb Damping ")); Serial.println(configuration.fx.reverb_damping, DEC); +#endif + + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + Serial.print(F("Dexed instance ")); + Serial.println(instance_id, DEC); + Serial.print(F(" MIDI-Channel ")); Serial.println(configuration.dexed[instance_id].midi_channel, DEC); + Serial.print(F(" Lowest Note ")); Serial.println(configuration.dexed[instance_id].lowest_note, DEC); + Serial.print(F(" Highest Note ")); Serial.println(configuration.dexed[instance_id].highest_note, DEC); + Serial.print(F(" Filter Cutoff ")); Serial.println(configuration.fx.filter_cutoff[instance_id], DEC); + Serial.print(F(" Filter Resonance ")); Serial.println(configuration.fx.filter_resonance[instance_id], DEC); + Serial.print(F(" Chorus Frequency ")); Serial.println(configuration.fx.chorus_frequency[instance_id], DEC); + Serial.print(F(" Chorus Waveform ")); Serial.println(configuration.fx.chorus_waveform[instance_id], DEC); + Serial.print(F(" Chorus Depth ")); Serial.println(configuration.fx.chorus_depth[instance_id], DEC); + Serial.print(F(" Chorus Level ")); Serial.println(configuration.fx.chorus_level[instance_id], DEC); + Serial.print(F(" Delay Time ")); Serial.println(configuration.fx.delay_time[instance_id], DEC); + Serial.print(F(" Delay Feedback ")); Serial.println(configuration.fx.delay_feedback[instance_id], DEC); + Serial.print(F(" Delay Level ")); Serial.println(configuration.fx.delay_level[instance_id], DEC); + Serial.print(F(" Delay Sync ")); Serial.println(configuration.fx.delay_sync[instance_id], DEC); + Serial.print(F(" Reverb Send ")); Serial.println(configuration.fx.reverb_send[instance_id], DEC); + Serial.print(F(" Sound Intensity ")); Serial.println(configuration.dexed[instance_id].sound_intensity, DEC); + Serial.print(F(" Panorama ")); Serial.println(configuration.dexed[instance_id].pan, 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(" Mono/Poly ")); Serial.println(configuration.dexed[instance_id].monopoly, DEC); + Serial.print(F(" Note Refresh ")); Serial.println(configuration.dexed[instance_id].note_refresh, 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(" Modwheel Mode ")); Serial.println(configuration.dexed[instance_id].mw_mode, 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(" Footctrl Mode ")); Serial.println(configuration.dexed[instance_id].fc_mode, 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(" Breathctrl Mode ")); Serial.println(configuration.dexed[instance_id].bc_mode, 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(" Aftertouch Mode ")); Serial.println(configuration.dexed[instance_id].at_mode, 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(F("Performance")); + for (uint8_t instance_id = 0; instance_id < NUM_DEXED; instance_id++) + { + Serial.print(F(" Bank ")); Serial.print(instance_id, DEC); Serial.print(" "); Serial.println(configuration.performance.bank[instance_id], DEC); + Serial.print(F(" Voice ")); Serial.print(instance_id, DEC); Serial.print(" "); Serial.println(configuration.performance.voice[instance_id], DEC); + } + Serial.print(F(" FX-Number ")); Serial.println(configuration.performance.fx_number, DEC); + + Serial.println(); + Serial.flush(); + } + + void show_patch(uint8_t instance_id) + { + char vn[VOICE_NAME_LEN]; + + Serial.print(F("INSTANCE ")); + Serial.println(instance_id, DEC); + + memset(vn, 0, sizeof(vn)); + Serial.println(F("+==========================================================================================================+")); + for (int8_t i = 5; i >= 0; --i) + { + Serial.println(F("+==========================================================================================================+")); + Serial.print(F("| OP")); + Serial.print(6 - i, DEC); + Serial.println(F(" |")); + Serial.println(F("+======+======+======+======+======+======+======+======+================+================+================+")); + Serial.println(F("| R1 | R2 | R3 | R4 | L1 | L2 | L3 | L4 | LEV_SCL_BRK_PT | SCL_LEFT_DEPTH | SCL_RGHT_DEPTH |")); + Serial.println(F("+------+------+------+------+------+------+------+------+----------------+----------------+----------------+")); + Serial.print("| "); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_R1)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_R2)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_R3)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_R4)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_L1)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_L2)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_L3)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_EG_L4)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_LEV_SCL_BRK_PT)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_SCL_LEFT_DEPTH)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_SCL_RGHT_DEPTH)); + Serial.println(F(" |")); + Serial.println(F("+======+======+======+======+======+===+==+==+===+======+====+========+==+====+=======+===+================+")); + Serial.println(F("| SCL_L_CURVE | SCL_R_CURVE | RT_SCALE | AMS | KVS | OUT_LEV | OP_MOD | FRQ_C | FRQ_F | DETUNE |")); + Serial.println(F("+-------------+-------------+----------+-----+-----+---------+--------+-------+-------+--------------------+")); + Serial.print(F("| ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_SCL_LEFT_CURVE)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_SCL_RGHT_CURVE)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_OSC_RATE_SCALE)); + Serial.print(F(" |")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_AMP_MOD_SENS)); + Serial.print(F(" |")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_KEY_VEL_SENS)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_OUTPUT_LEV)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_OSC_MODE)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_FREQ_COARSE)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_FREQ_FINE)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement((i * 21) + DEXED_OP_OSC_DETUNE)); + Serial.println(F(" |")); + } + Serial.println(F("+=======+=====+=+=======+===+===+======++====+==+==+====+====+==+======+======+=====+=+====================+")); + Serial.println(F("| PR1 | PR2 | PR3 | PR4 | PL1 | PL2 | PL3 | PL4 | ALG | FB | OKS | TRANSPOSE |")); + Serial.println(F("+-------+-------+-------+-------+-------+-------+-------+-------+------+------+-----+----------------------+")); + Serial.print(F("| ")); + for (int8_t i = 0; i < 8; i++) + { + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + i)); + Serial.print(F(" | ")); + } + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_ALGORITHM)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_FEEDBACK)); + Serial.print(F(" |")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_OSC_KEY_SYNC)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_TRANSPOSE)); + Serial.println(F(" |")); + Serial.println(F("+=======+=+=====+===+===+=====+=+=======+=======+==+====+=====+=+======++=====+=====+======================+")); + Serial.println(F("| LFO SPD | LFO DLY | LFO PMD | LFO AMD | LFO SYNC | LFO WAVE | LFO PMS | NAME |")); + Serial.println(F("+---------+---------+---------+---------+----------+----------+---------+----------------------------------+")); + Serial.print(F("| ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_SPEED)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_DELAY)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_DEP)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_AMP_MOD_DEP)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_SYNC)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_WAVE)); + Serial.print(F(" | ")); + SerialPrintFormatInt3(MicroDexed[instance_id]->getVoiceDataElement(DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_SENS)); + Serial.print(F(" | ")); + MicroDexed[instance_id]->getName(vn); + Serial.print(vn); + Serial.println(F(" |")); + Serial.println(F("+=========+=========+=========+=========+==========+==========+=========+==================================+")); + Serial.println(F("+==========================================================================================================+")); + } + + void SerialPrintFormatInt3(uint8_t num) + { + char buf[4]; + sprintf(buf, "%3d", num); + Serial.print(buf); + } #ifdef TEENSY3_6 -/* From: https://forum.pjrc.com/threads/33443-How-to-display-free-ram */ -extern "C" char* sbrk(int incr); -uint32_t FreeMem(void) -{ - char top; - return &top - reinterpret_cast(sbrk(0)); -} + /* From: https://forum.pjrc.com/threads/33443-How-to-display-free-ram */ + extern "C" char* sbrk(int incr); + uint32_t FreeMem(void) + { + char top; + return &top - reinterpret_cast(sbrk(0)); + } #else -/* From: https://forum.pjrc.com/threads/33443-How-to-display-free-ram */ -extern unsigned long _heap_end; -extern char *__brkval; -int FreeMem(void) -{ - return (char *)&_heap_end - __brkval; -} + /* From: https://forum.pjrc.com/threads/33443-How-to-display-free-ram */ + extern unsigned long _heap_end; + extern char *__brkval; + int FreeMem(void) + { + return (char *)&_heap_end - __brkval; + } #endif #endif diff --git a/UI.hpp b/UI.hpp index 56c718b..9f5f57b 100644 --- a/UI.hpp +++ b/UI.hpp @@ -51,6 +51,9 @@ #endif #define _LCDML_DISP_cfg_scrollbar 1 // enable a scrollbar +extern PeriodicTimer timer1; +extern void sequencer(void); + extern config_t configuration; extern void set_volume(uint8_t v, uint8_t m); extern bool load_sysex(uint8_t b, uint8_t v); @@ -65,6 +68,7 @@ extern float pseudo_log_curve(float value); extern uint8_t selected_instance_id; extern char receive_bank_filename[FILENAME_LEN]; + #if NUM_DRUMS > 0 #include "drums.h" extern drum_config_t drum_config[NUM_DRUMSET_CONFIG]; @@ -82,7 +86,6 @@ extern bool seq_recording; uint8_t seq_active_function = 99; uint8_t activesample; extern uint8_t seq_active_track; -extern elapsedMillis seq_millis_timer; extern uint8_t seq_menu; extern uint8_t seq_temp_select_menu; extern uint8_t seq_temp_active_menu; @@ -4068,7 +4071,7 @@ void UI_func_seq_lenght(uint8_t param) void UI_func_seq_tempo(uint8_t param) { - char tmp[5]; + char tmp[7]; if (LCDML.FUNC_setup()) // ****** SETUP ********* { encoderDir[ENC_R].reset(); @@ -4084,19 +4087,21 @@ void UI_func_seq_tempo(uint8_t param) 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_bpm = constrain(seq_bpm + ENCODER[ENC_R].speed(), 60, 180); + seq_bpm = constrain(seq_bpm + ENCODER[ENC_R].speed(), 50, 190); else if (LCDML.BT_checkUp()) - seq_bpm = constrain(seq_bpm - ENCODER[ENC_R].speed(), 60, 180); + seq_bpm = constrain(seq_bpm - ENCODER[ENC_R].speed(), 50, 190); } - seq_tempo_ms = 60000 / seq_bpm / 4; + // seq_tempo_ms = 60000 / seq_bpm / 4; + seq_tempo_ms = 60000000 / seq_bpm / 4; - sprintf(tmp, "[%3d]", seq_bpm); lcd.setCursor(0, 1); + sprintf(tmp, "[%3d]", seq_bpm); lcd.print(tmp); - - lcd.setCursor(9, 1); - sprintf(tmp, "[%3d]", seq_tempo_ms); + lcd.setCursor(11, 1); + sprintf(tmp, "%3d", seq_tempo_ms / 1000); lcd.print(tmp); + //timer1.stop(); + timer1.begin(sequencer, seq_tempo_ms / 2); } if (LCDML.FUNC_close()) // ****** STABLE END ********* { @@ -4522,6 +4527,7 @@ void UI_func_sequencer(uint8_t param) { seq_running = true; lcd.print("REC"); + timer1.start(); } else if (seq_running == true && seq_recording == false) { seq_running = true; @@ -4536,6 +4542,7 @@ void UI_func_sequencer(uint8_t param) seq_step = 0; seq_chain_active_step = 0; lcd.print("PLY"); + timer1.stop(); MicroDexed[0]->panic(); } } else if ( seq_menu == 2) @@ -4788,7 +4795,8 @@ void UI_func_arpeggio(uint8_t param) lcd.print("Maj"); lcd.setCursor( 11, 1); lcd.print("1/16"); - + lcd.setCursor( 7, 0); + lcd.print(" "); arp_refresh_display_play_status(); } if (LCDML.FUNC_loop()) // ****** LOOP ********* @@ -4841,15 +4849,26 @@ void UI_func_arpeggio(uint8_t param) seq_temp_active_menu = 0; } - else if ( seq_temp_select_menu == 2 && seq_temp_active_menu == 0 ) + //else if ( seq_temp_select_menu == 2 && seq_temp_active_menu == 0 ) + else if ( seq_temp_select_menu == 2 ) { - seq_running = !seq_running; - arp_refresh_display_play_status(); - MicroDexed[0]->panic(); - seq_step = 0; - arp_octave = 0; - arp_step = 0; - seq_chain_active_step = 0; + + if (seq_running) { + seq_running = !seq_running; + timer1.stop(); + MicroDexed[0]->panic(); + arp_refresh_display_play_status(); + seq_step = 0; + arp_octave = 0; + arp_step = 0; + seq_chain_active_step = 0; + } else + { + seq_running = !seq_running; + arp_refresh_display_play_status(); + timer1.start(); + } + } else if ( seq_temp_select_menu == 3 && seq_temp_active_menu == 0 ) { @@ -4871,6 +4890,10 @@ void UI_func_arpeggio(uint8_t param) if (arp_speed == 0)lcd.print("1/16"); else if (arp_speed == 1)lcd.print("1/8 "); if (seq_temp_select_menu == 0) { + lcd.setCursor( 3, 0); + lcd.print("["); + lcd.setCursor( 5, 0); + lcd.print("]"); lcd.setCursor( 11, 0); lcd.print(" "); lcd.setCursor( 15, 0); @@ -4879,24 +4902,22 @@ void UI_func_arpeggio(uint8_t param) lcd.print(" "); lcd.setCursor( 9, 1); lcd.print(" "); - lcd.setCursor( 3, 0); - lcd.print("["); - lcd.setCursor( 5, 0); - lcd.print("]"); lcd.setCursor( 10, 1); lcd.print(" "); lcd.setCursor( 15, 1); lcd.print(" "); + } else if (seq_temp_select_menu == 1) { - lcd.setCursor( 11, 0); - lcd.print(" "); - lcd.print(" "); lcd.setCursor( 3, 0); lcd.print(" "); lcd.setCursor( 5, 0); lcd.print(" "); + lcd.setCursor( 11, 0); + lcd.print(" "); + lcd.setCursor( 15, 0); + lcd.print(" "); lcd.setCursor( 5, 1); lcd.print("["); lcd.setCursor( 9, 1); @@ -4908,14 +4929,14 @@ void UI_func_arpeggio(uint8_t param) lcd.print(" "); lcd.setCursor( 9, 1); lcd.print(" "); - lcd.setCursor( 11, 0); - lcd.print("["); - lcd.setCursor( 15, 0); - lcd.print("]"); lcd.setCursor( 10, 1); lcd.print(" "); lcd.setCursor( 15, 1); lcd.print(" "); + lcd.setCursor( 11, 0); + lcd.print("["); + lcd.setCursor( 15, 0); + lcd.print("]"); } else if (seq_temp_select_menu == 3) { @@ -4923,14 +4944,14 @@ void UI_func_arpeggio(uint8_t param) lcd.print(" "); lcd.setCursor( 15, 0); lcd.print(" "); - lcd.setCursor( 10, 1); - lcd.print("["); - lcd.setCursor( 15, 1); - lcd.print("]"); lcd.setCursor( 3, 0); lcd.print(" "); lcd.setCursor( 5, 0); lcd.print(" "); + lcd.setCursor( 10, 1); + lcd.print("["); + lcd.setCursor( 15, 1); + lcd.print("]"); } } if (LCDML.FUNC_close()) // ****** STABLE END ********* diff --git a/sequencer.cpp b/sequencer.cpp index bad6929..5bf8ae8 100644 --- a/sequencer.cpp +++ b/sequencer.cpp @@ -12,8 +12,9 @@ extern void handleNoteOff(byte , byte , byte ); extern void UI_func_sequencer(uint8_t); extern void UI_func_arpeggio(uint8_t); extern const char* seq_find_shortname(uint8_t); +boolean interrupt_swapper = false; -void sequencer(void) +void sequencer_part1(void) { //if (seq_note_in > 0 && seq_note_in < 62 && seq_recording == false ) { //handleNoteOff(configuration.dexed[0].midi_channel, seq_data[3][seq_step] + seq_transpose , 0); @@ -24,155 +25,143 @@ void sequencer(void) //seq_note_in = 0; //} - if (seq_millis_timer > seq_timer_previous + seq_tempo_ms) - { - seq_timer_previous = seq_millis_timer; - - if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_sequencer)) { //is in UI of Sequencer + // if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_sequencer)) { //is in UI of Sequencer - //write to sequencer if in sequencer menu - if (seq_note_in > 0 && seq_recording == true) { + //write to sequencer if in sequencer menu + // if (seq_note_in > 0 && seq_recording == true) { + // + // // if ( seq_content_type[ seq_patternchain[seq_chain_active_step][active_track] ] == 1) handleNoteOff(configuration.dexed[0].midi_channel, seq_data[active_track][seq_step] + seq_transpose , 0); + // + // seq_data[seq_active_track][seq_step] = seq_note_in; + // seq_vel[seq_active_track][seq_step] = seq_note_in_velocity; + // seq_note_in = 0; + // seq_note_in_velocity = 0; + // } - // if ( seq_content_type[ seq_patternchain[seq_chain_active_step][active_track] ] == 1) handleNoteOff(configuration.dexed[0].midi_channel, seq_data[active_track][seq_step] + seq_transpose , 0); - - seq_data[seq_active_track][seq_step] = seq_note_in; - seq_vel[seq_active_track][seq_step] = seq_note_in_velocity; - seq_note_in = 0; - seq_note_in_velocity = 0; - } - lcd.setCursor(seq_step, 1); - lcd.print("X"); - if (seq_step == 0) { - lcd.setCursor(15, 1); - lcd.print(seq_find_shortname(15)[0]); - } - else + for (uint8_t d = 0; d < 4; d++) + { + if ( seq_track_type[d] == 0) { // drum track + if (seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] > 0 && seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] > 0) { - lcd.setCursor(seq_step - 1, 1); - lcd.print(seq_find_shortname(seq_step - 1)[0]); + handleNoteOn(DRUM_MIDI_CHANNEL, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] , seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step]); } } - else if (LCDML.FUNC_getID() == LCDML.OTHER_getIDFromFunction(UI_func_arpeggio)) { //is in UI of Arpeggiator - lcd.setCursor(7, 0); - lcd.print( seq_chord_names[arp_chord - 200][0]); - lcd.print( seq_chord_names[arp_chord - 200][1]); - lcd.print( seq_chord_names[arp_chord - 200][2]); - lcd.print( seq_chord_names[arp_chord - 200][3]); - } - for (uint8_t d = 0; d < 4; d++) - { - if ( seq_track_type[d] == 0) { // drum track - if (seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] > 0 && seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] > 0) + else { + if (seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] > 0 && seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] > 0) // instrument track + { + if (seq_track_type[d] == 1 || seq_track_type[d] == 3 ) { - handleNoteOn(DRUM_MIDI_CHANNEL, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] , seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step]); + handleNoteOn(configuration.dexed[seq_inst_dexed[d]].midi_channel, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose , seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step]); } - } - else { - if (seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] > 0 && seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] > 0) // instrument track - { - if (seq_track_type[d] == 1 || seq_track_type[d] == 3 ) + seq_prev_note[d] = seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose; + seq_prev_vel[d] = seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step]; + if (seq_track_type[d] == 2) { //Chords + if (seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] > 199) { - handleNoteOn(configuration.dexed[seq_inst_dexed[d]].midi_channel, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose , seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step]); - } - seq_prev_note[d] = seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose; - seq_prev_vel[d] = seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step]; - if (seq_track_type[d] == 2) { //Chords - if (seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] > 199) - { - handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose + seq_chords[seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] - 200][0], seq_chord_velocity); - handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose + seq_chords[seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] - 200][1], seq_chord_velocity); - handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose + seq_chords[seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] - 200][2], seq_chord_velocity); - handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose + seq_chords[seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] - 200][3], seq_chord_velocity); - } - } - else if (seq_track_type[d] == 3) { //Arp - arp_step = 0; - arp_counter = 0; - arp_note = seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose; - arp_chord = seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step]; + handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose + seq_chords[seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] - 200][0], seq_chord_velocity); + handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose + seq_chords[seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] - 200][1], seq_chord_velocity); + handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose + seq_chords[seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] - 200][2], seq_chord_velocity); + handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose + seq_chords[seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step] - 200][3], seq_chord_velocity); } } + else if (seq_track_type[d] == 3) { //Arp + arp_step = 0; + arp_counter = 0; + arp_note = seq_data[ seq_patternchain[seq_chain_active_step][d] ][seq_step] + seq_transpose; + arp_chord = seq_vel[ seq_patternchain[seq_chain_active_step][d] ][seq_step]; + } } - if (seq_track_type[d] == 3) - { //Arp - if (arp_speed == 0 || (arp_speed == 1 && arp_counter == 2) ) { - if (arp_step == 0 ) { - handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note + arp_octave * 12 , seq_chord_velocity); - arp_note_prev = arp_note + arp_octave * 12; + } + if (seq_track_type[d] == 3) + { //Arp + if (arp_speed == 0 || (arp_speed == 1 && arp_counter == 2) ) { + if (arp_step == 0 ) { + handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note + arp_octave * 12 , seq_chord_velocity); + arp_note_prev = arp_note + arp_octave * 12; + } + else + { if (arp_style == 0) { //arp up + handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note + seq_chords[arp_chord - 200][arp_step - 1] + arp_octave * 12, seq_chord_velocity); + arp_note_prev = arp_note + seq_chords[arp_chord - 200][arp_step - 1] + arp_octave * 12; } - else - { if (arp_style == 0) { //arp up - handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note + seq_chords[arp_chord - 200][arp_step - 1] + arp_octave * 12, seq_chord_velocity); - arp_note_prev = arp_note + seq_chords[arp_chord - 200][arp_step - 1] + arp_octave * 12; - } - else if (arp_style == 3) { //arp random - uint8_t rnd1 = random(4); - uint8_t rnd2 = random(arp_oct_usersetting + 1) * 12; - handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note + seq_chords[arp_chord - 200][rnd1] + rnd2, seq_chord_velocity); - arp_note_prev = arp_note + seq_chords[arp_chord - 200][rnd1] + rnd2; - } + else if (arp_style == 3) { //arp random + uint8_t rnd1 = random(4); + uint8_t rnd2 = random(arp_oct_usersetting + 1) * 12; + handleNoteOn(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note + seq_chords[arp_chord - 200][rnd1] + rnd2, seq_chord_velocity); + arp_note_prev = arp_note + seq_chords[arp_chord - 200][rnd1] + rnd2; } } } - seq_noteoffsent[d] = false; } + seq_noteoffsent[d] = false; + } - seq_step++; + seq_step++; - if (arp_speed == 0) // Arp Speed 1/16 - { - arp_step++; - } - else + if (arp_speed == 0) // Arp Speed 1/16 + { + arp_step++; + } + else + { + if (arp_speed == 1) // Arp Speed 1/8 { - if (arp_speed == 1) // Arp Speed 1/8 - { - if (arp_counter > 1) { - arp_counter = 0; - arp_step++; - } - arp_counter++; + if (arp_counter > 1) { + arp_counter = 0; + arp_step++; } + arp_counter++; } - if (arp_step > 3 || seq_chords[arp_chord - 200][arp_step] == 0 ) { - arp_step = 0; - arp_octave++; - if (arp_octave > arp_oct_usersetting) arp_octave = 0; - } - if (seq_step > 15) { - seq_step = 0; - if (seq_chain_lenght > 0) { - seq_chain_active_step++; - if (seq_chain_active_step > seq_chain_lenght) - { - seq_chain_active_step = 0; - } + } + if (arp_step > 3 || seq_chords[arp_chord - 200][arp_step] == 0 ) { + arp_step = 0; + arp_octave++; + if (arp_octave > arp_oct_usersetting) arp_octave = 0; + } + if (seq_step > 15) { + seq_step = 0; + if (seq_chain_lenght > 0) { + seq_chain_active_step++; + if (seq_chain_active_step > seq_chain_lenght) + { + seq_chain_active_step = 0; } } } - if (seq_millis_timer > seq_timer_previous + 80 ) +} + +void sequencer_part2(void) +{ + for (uint8_t d = 0; d < 4; d++) { - for (uint8_t d = 0; d < 4; d++) - { - if ( seq_noteoffsent[d] == false) { - if ( seq_prev_note[d] > 0 && seq_track_type[d] > 0) //test instrument sequencer Instance=0 - { - handleNoteOff(configuration.dexed[seq_inst_dexed[d]].midi_channel, seq_prev_note[d] , 0); - if (seq_track_type[d] == 2) { //Chords - if ( seq_prev_vel[d] > 199) { - handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][0], 0); - handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][1] , 0); - handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][2] , 0); - handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][3] , 0); - } - } - else if (seq_track_type[d] == 3) - { //Arp - handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note_prev, 0); + if ( seq_noteoffsent[d] == false) { + if ( seq_prev_note[d] > 0 && seq_track_type[d] > 0) + { + handleNoteOff(configuration.dexed[seq_inst_dexed[d]].midi_channel, seq_prev_note[d] , 0); + if (seq_track_type[d] == 2) { //Chords + if ( seq_prev_vel[d] > 199) { + handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][0], 0); + handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][1] , 0); + handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][2] , 0); + handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, seq_prev_note[d] + seq_chords[seq_prev_vel[d] - 200][3] , 0); } } + else if (seq_track_type[d] == 3) + { //Arp + handleNoteOff(configuration.dexed[seq_chord_dexed_inst].midi_channel, arp_note_prev, 0); + } } seq_noteoffsent[d] = true; } } } + + +void sequencer(void) +{ // Runs in Interrupt Timer. Switches between the Noteon and Noteoff Task, each cycle + + interrupt_swapper = !interrupt_swapper; + + if (interrupt_swapper) sequencer_part1(); + else sequencer_part2(); +} diff --git a/sequencer.h b/sequencer.h index 84a5237..025d8eb 100644 --- a/sequencer.h +++ b/sequencer.h @@ -3,15 +3,13 @@ uint8_t seq_active_track = 0; uint8_t seq_menu; bool seq_button_r = false; bool seq_noteoffsent[4] = {false, false, false, false}; -elapsedMillis seq_millis_timer; uint8_t seq_step = 0; -uint32_t seq_timer_previous = 0; bool seq_running = false; bool seq_recording = false; uint8_t seq_note_in; uint8_t seq_note_in_velocity; int seq_transpose; -uint8_t seq_inst_dexed[4] = { 1, 1, 1, 1 }; +uint8_t seq_inst_dexed[4] = { 0, 0, 0, 1 }; uint8_t seq_chord_dexed_inst = 0; uint8_t seq_chord_velocity = 50; uint8_t arp_style = 0; // up, down, up&down, random @@ -64,11 +62,10 @@ uint8_t seq_data[10][16] = {72 , 0 , 0 , 0 , 72 , 0 , 0 , 0 , 72 , 0 , 0 74 , 0 , 0 , 72 , 0 , 0 , 74 , 0 , 0 , 0 , 76 , 0 , 0 , 0 , 0 , 0 , 74 , 0 , 0 , 72 , 0 , 0 , 71 , 0 , 0 , 0 , 67 , 0 , 0 , 0 , 0 , 0 , 69 , 0 , 0 , 76 , 0 , 0 , 69 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 - }; uint8_t seq_vel[10][16] = {120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, 0, 105, 80, 105, 70, 106, 98, 106, 70, 126, 97, 106, 70, 106, 99, 90, 65, - 120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 120, 50, 120, 120, 0, 0, + 120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 120, 60, 120, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 200, 200, 200, 200, 200, 200, 201, 0, 0, 0, 0, 0, 0, 0, 0, 200, @@ -80,8 +77,8 @@ uint8_t seq_vel[10][16] = {120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, 0, 120, 0, 0, uint8_t seq_patternchain[4][4] = { 0 , 1 , 6 , 9 , 0 , 1 , 5 , 8 , 0 , 1 , 6 , 9 , 2 , 1 , 5 , 7 }; -uint8_t seq_content_type[10] = { 0, 0, 0, 0 , 1, 2, 2 , 1 , 1 , 1 }; // 0 = track is Drumtrack, 1= Instrumenttrack, 2= Chord or Arpeggio -uint8_t seq_track_type[4] = { 0, 0, 3, 1 }; // 0 = track is Drumtrack, 1 = Instrumenttrack, 2 = Chord, 3 = Arp +uint8_t seq_content_type[10] = { 0, 0, 0, 0 , 1, 1, 1 , 1 , 1 , 1 }; // 0 = track is Drumtrack, 1= Instrumenttrack, 2= Chord or Arpeggio +uint8_t seq_track_type[4] = { 0, 0, 1, 1 }; // 0 = track is Drumtrack, 1 = Instrumenttrack, 2 = Chord, 3 = Arp //uint8_t seq_reverb[4][16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, From da9f5c0fd598cd6df48482fb947cf7d11c8c7986 Mon Sep 17 00:00:00 2001 From: positionhigh Date: Sat, 14 Aug 2021 11:38:51 +0200 Subject: [PATCH 5/5] =?UTF-8?q?Dateien=20hochladen=20nach=20=E2=80=9Ethird?= =?UTF-8?q?-party=E2=80=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- third-party/TeensyTimerTool-master.zip | Bin 0 -> 112547 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 third-party/TeensyTimerTool-master.zip diff --git a/third-party/TeensyTimerTool-master.zip b/third-party/TeensyTimerTool-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..531b1824121cff523211c0ce3d1a8f4388c88da6 GIT binary patch literal 112547 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%0D2|KitNj1J6nI75g=AbiXAHU3Otf#C2|~D8Xe% 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 L4ViTf3>g0h4@~>Q literal 0 HcmV?d00001