diff --git a/EEPROMAnything.h b/EEPROMAnything.h new file mode 100644 index 0000000..b2a7515 --- /dev/null +++ b/EEPROMAnything.h @@ -0,0 +1,53 @@ +/* + MicroDexed + + MicroDexed is a port of the Dexed sound engine + (https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6 with audio shield. + Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android + + (c)2018 H. Wirtz + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + // Idea from: https://playground.arduino.cc/Code/EEPROMWriteAnything/ + +#include +#include // for type definitions + +uint32_t crc32(uint8_t* calc_start, uint16_t calc_bytes); + +template int EEPROM_writeAnything(int ee, const T& value) +{ + uint8_t* p = (uint8_t*)(const void*)&value; + uint16_t i; + uint32_t checksum=crc32(p+4,sizeof(value)-4); + + *p=checksum; + + for (i = 0; i < sizeof(value); i++) + EEPROM.update(ee++, *p++); + return i; +} + +template int EEPROM_readAnything(int ee, T& value) +{ + uint8_t* p = (uint8_t*)(void*)&value; + unsigned int i; + for (i = 0; i < sizeof(value); i++) + *p++ = EEPROM.read(ee++); + return i; +} diff --git a/MicroDexed.ino b/MicroDexed.ino index f7dd9a6..64d633c 100644 --- a/MicroDexed.ino +++ b/MicroDexed.ino @@ -29,11 +29,12 @@ #include #include #include +#include "EEPROMAnything.h" #include "midi_devices.hpp" #include #include "dexed.h" #include "dexed_sysex.h" - +#include "PluginFx.h" #ifdef I2C_DISPLAY // selecting sounds by encoder, button and display #include "UI.h" #include @@ -50,89 +51,72 @@ uint8_t ui_main_state = UI_MAIN_VOICE; AudioPlayQueue queue1; AudioAnalyzePeak peak1; -AudioFilterStateVariable filter1; AudioEffectDelay delay1; AudioMixer4 mixer1; AudioMixer4 mixer2; -AudioFilterBiquad antialias; AudioConnection patchCord0(queue1, peak1); -AudioConnection patchCord1(queue1, antialias); -AudioConnection patchCord2(antialias, 0, filter1, 0); -AudioConnection patchCord3(filter1, 0, delay1, 0); -AudioConnection patchCord4(filter1, 0, mixer1, 0); -AudioConnection patchCord5(filter1, 0, mixer2, 0); -AudioConnection patchCord6(delay1, 0, mixer1, 1); -AudioConnection patchCord7(delay1, 0, mixer2, 2); -AudioConnection patchCord8(mixer1, delay1); -AudioConnection patchCord9(queue1, 0, mixer1, 3); // for disabling the filter -AudioConnection patchCord10(mixer1, 0, mixer2, 1); +AudioConnection patchCord1(queue1, 0, mixer1, 0); +AudioConnection patchCord2(queue1, 0, mixer2, 0); +AudioConnection patchCord3(delay1, 0, mixer1, 1); +AudioConnection patchCord4(delay1, 0, mixer2, 2); +AudioConnection patchCord5(mixer1, delay1); +AudioConnection patchCord6(mixer1, 0, mixer2, 1); #if defined(TEENSY_AUDIO_BOARD) AudioOutputI2S i2s1; -AudioConnection patchCord111(mixer2, 0, i2s1, 0); -AudioConnection patchCord112(mixer2, 0, i2s1, 1); +AudioConnection patchCord7(mixer2, 0, i2s1, 0); +AudioConnection patchCord8(mixer2, 0, i2s1, 1); AudioControlSGTL5000 sgtl5000_1; #elif defined(TGA_AUDIO_BOARD) AudioOutputI2S i2s1; AudioAmplifier volume_r; AudioAmplifier volume_l; -AudioConnection patchCord11(mixer2, volume_r); -AudioConnection patchCord12(mixer2, volume_l); -AudioConnection patchCord13(volume_r, 0, i2s1, 1); -AudioConnection patchCord14(volume_l, 0, i2s1, 0); +AudioConnection patchCord7(mixer2, volume_r); +AudioConnection patchCord8(mixer2, volume_l); +AudioConnection patchCord9(volume_r, 0, i2s1, 1); +AudioConnection patchCord10(volume_l, 0, i2s1, 0); AudioControlWM8731master wm8731_1; #else AudioOutputPT8211 pt8211_1; AudioAmplifier volume_master; AudioAmplifier volume_r; AudioAmplifier volume_l; -AudioConnection patchCord11(mixer2, 0, volume_master, 0); -AudioConnection patchCord12(volume_master, volume_r); -AudioConnection patchCord13(volume_master, volume_l); -AudioConnection patchCord14(volume_r, 0, pt8211_1, 0); -AudioConnection patchCord15(volume_l, 0, pt8211_1, 1); +AudioConnection patchCord7(mixer2, 0, volume_master, 0); +AudioConnection patchCord8(volume_master, volume_r); +AudioConnection patchCord9(volume_master, volume_l); +AudioConnection patchCord10(volume_r, 0, pt8211_1, 0); +AudioConnection patchCord11(volume_l, 0, pt8211_1, 1); #endif Dexed* dexed = new Dexed(SAMPLE_RATE); bool sd_card_available = false; -uint8_t midi_channel = DEFAULT_MIDI_CHANNEL; uint32_t xrun = 0; uint32_t overload = 0; uint32_t peak = 0; uint16_t render_time_max = 0; -uint8_t bank = 0; uint8_t max_loaded_banks = 0; -uint8_t voice = 0; -float vol = VOLUME; -float pan = 0.5f; char bank_name[BANK_NAME_LEN]; char voice_name[VOICE_NAME_LEN]; char bank_names[MAX_BANKS][BANK_NAME_LEN]; char voice_names[MAX_VOICES][VOICE_NAME_LEN]; elapsedMillis autostore; -uint8_t eeprom_update_status = 0; -uint16_t autostore_value = AUTOSTORE_MS; uint8_t midi_timing_counter = 0; // 24 per qarter elapsedMillis midi_timing_timestep; uint16_t midi_timing_quarter = 0; elapsedMillis long_button_pressed; -uint8_t effect_filter_frq = ENC_FILTER_FRQ_STEPS; +uint8_t effect_filter_cutoff = 0; uint8_t effect_filter_resonance = 0; -uint8_t effect_filter_octave = (1.0 * ENC_FILTER_RES_STEPS / 8.0) + 0.5; uint8_t effect_delay_time = 0; uint8_t effect_delay_feedback = 0; uint8_t effect_delay_volume = 0; bool effect_delay_sync = 0; elapsedMicros fill_audio_buffer; - +elapsedMillis control_rate; +uint8_t active_voices = 0; #ifdef SHOW_CPU_LOAD_MSEC elapsedMillis cpu_mem_millis; #endif - -#ifdef TEST_NOTE -IntervalTimer sched_note_on; -IntervalTimer sched_note_off; -uint8_t _voice_counter = 0; -#endif +config_t configuration = {0xffff, 0, 0, VOLUME, 0.5f, DEFAULT_MIDI_CHANNEL}; +bool eeprom_update_flag = false; void setup() { @@ -159,9 +143,10 @@ void setup() Serial.println(F("MicroDexed based on https://github.com/asb2m10/dexed")); Serial.println(F("(c)2018,2019 H. Wirtz ")); Serial.println(F("https://github.com/dcoredump/MicroDexed")); + Serial.print(F("Version: ")); + Serial.println(VERSION); Serial.println(F("")); - //init_eeprom(); initial_values_from_eeprom(); setup_midi_devices(); @@ -192,7 +177,7 @@ void setup() Serial.println(F("PT8211 enabled.")); #endif - set_volume(vol, pan); + set_volume(configuration.vol, configuration.pan); // start SD card SPI.setMOSI(SDCARD_MOSI_PIN); @@ -210,13 +195,13 @@ void setup() // read all bank names max_loaded_banks = get_bank_names(); - strip_extension(bank_names[bank], bank_name); + strip_extension(bank_names[configuration.bank], bank_name); // read all voice name for actual bank - get_voice_names_from_bank(bank); + get_voice_names_from_bank(configuration.bank); #ifdef DEBUG Serial.print(F("Bank [")); - Serial.print(bank_names[bank]); + Serial.print(bank_names[configuration.bank]); Serial.print(F("/")); Serial.print(bank_name); Serial.println(F("]")); @@ -233,29 +218,25 @@ void setup() #endif // Init effects - antialias.setLowpass(0, 6000, 0.707); - filter1.frequency(EXP_FUNC((float)map(effect_filter_frq, 0, ENC_FILTER_FRQ_STEPS, 0, 1024) / 150.0) * 10.0 + 80.0); - //filter1.resonance(mapfloat(effect_filter_resonance, 0, ENC_FILTER_RES_STEPS, 0.7, 5.0)); - filter1.resonance(EXP_FUNC(mapfloat(effect_filter_resonance, 0, ENC_FILTER_RES_STEPS, 0.7, 5.0)) * 0.044 + 0.61); - filter1.octaveControl(mapfloat(effect_filter_octave, 0, ENC_FILTER_OCT_STEPS, 0.0, 7.0)); delay1.delay(0, mapfloat(effect_delay_feedback, 0, ENC_DELAY_TIME_STEPS, 0.0, DELAY_MAX_TIME)); // mixer1 is the feedback-adding mixer, mixer2 the whole delay (with/without feedback) mixer mixer1.gain(0, 1.0); // original signal mixer1.gain(1, mapfloat(effect_delay_feedback, 0, ENC_DELAY_FB_STEPS, 0.0, 1.0)); // amount of feedback - mixer1.gain(0, 0.0); // filtered signal off - mixer1.gain(3, 1.0); // original signal on mixer2.gain(0, 1.0 - mapfloat(effect_delay_volume, 0, ENC_DELAY_VOLUME_STEPS, 0.0, 1.0)); // original signal mixer2.gain(1, mapfloat(effect_delay_volume, 0, ENC_DELAY_VOLUME_STEPS, 0.0, 1.0)); // delayed signal (including feedback) mixer2.gain(2, mapfloat(effect_delay_volume, 0, ENC_DELAY_VOLUME_STEPS, 0.0, 1.0)); // only delayed signal (without feedback) + dexed->fx.Gain = 1.0; + dexed->fx.Reso = 1.0 - float(effect_filter_resonance) / ENC_FILTER_RES_STEPS; + dexed->fx.Cutoff = 1.0 - float(effect_filter_cutoff) / ENC_FILTER_CUT_STEPS; // load default SYSEX data - load_sysex(bank, voice); + load_sysex(configuration.bank, configuration.voice); } #ifdef I2C_DISPLAY - enc[0].write(map(vol * 100, 0, 100, 0, ENC_VOL_STEPS)); + enc[0].write(map(configuration.vol * 100, 0, 100, 0, ENC_VOL_STEPS)); enc_val[0] = enc[0].read(); - enc[1].write(voice); + enc[1].write(configuration.voice); enc_val[1] = enc[1].read(); but[0].update(); but[1].update(); @@ -269,9 +250,9 @@ void setup() #ifdef DEBUG Serial.print(F("Bank/Voice from EEPROM [")); - Serial.print(EEPROM.read(EEPROM_OFFSET + EEPROM_BANK_ADDR), DEC); + Serial.print(configuration.bank, DEC); Serial.print(F("/")); - Serial.print(EEPROM.read(EEPROM_OFFSET + EEPROM_VOICE_ADDR), DEC); + Serial.print(configuration.voice, DEC); Serial.println(F("]")); show_patch(); #endif @@ -320,23 +301,32 @@ void loop() } #ifndef TEENSY_AUDIO_BOARD for (uint8_t i = 0; i < AUDIO_BLOCK_SAMPLES; i++) - audio_buffer[i] *= vol; + audio_buffer[i] *= configuration.vol; #endif queue1.playBuffer(); } // EEPROM update handling - if (eeprom_update_status > 0 && autostore >= autostore_value) + if (autostore >= AUTOSTORE_MS && active_voices == 0 && eeprom_update_flag == true) { - autostore = 0; + // only store configuration data to EEPROM when AUTOSTORE_MS is reached and no voices are activated anymore eeprom_update(); } // MIDI input handling check_midi_devices(); + // CONTROL-RATE-EVENT-HANDLING + if (control_rate > CONTROL_RATE_MS) + { + control_rate = 0; + + // Shutdown unused voices + active_voices = dexed->getNumNotesPlaying(); + } + #ifdef I2C_DISPLAY - // UI + // UI-HANDLING if (master_timer >= TIMER_UI_HANDLING_MS) { master_timer -= TIMER_UI_HANDLING_MS; @@ -388,7 +378,7 @@ void handleControlChange(byte inChannel, byte inCtrl, byte inValue) case 0: if (inValue < MAX_BANKS) { - bank = inValue; + configuration.bank = inValue; handle_ui(); } break; @@ -405,15 +395,15 @@ void handleControlChange(byte inChannel, byte inCtrl, byte inValue) dexed->controllers.refresh(); break; case 7: // Volume - vol = float(inValue) / 0x7f; - set_volume(vol, pan); + configuration.vol = float(inValue) / 0x7f; + set_volume(configuration.vol, configuration.pan); break; case 10: // Pan - pan = float(inValue) / 128; - set_volume(vol, pan); + configuration.pan = float(inValue) / 128; + set_volume(configuration.vol, configuration.pan); break; case 32: // BankSelect LSB - bank = inValue; + configuration.bank = inValue; break; case 64: dexed->setSustain(inValue > 63); @@ -426,31 +416,14 @@ void handleControlChange(byte inChannel, byte inCtrl, byte inValue) } } break; - case 102: // CC 102: filter frequency - effect_filter_frq = map(inValue, 0, 127, 0, ENC_FILTER_FRQ_STEPS); - if (effect_filter_frq == ENC_FILTER_FRQ_STEPS) - { - // turn "off" filter - mixer1.gain(0, 0.0); // filtered signal off - mixer1.gain(3, 1.0); // original signal on - } - else - { - // turn "on" filter - mixer1.gain(0, 1.0); // filtered signal on - mixer1.gain(3, 0.0); // original signal off - } - filter1.frequency(EXP_FUNC((float)map(effect_filter_frq, 0, ENC_FILTER_FRQ_STEPS, 0, 1024) / 150.0) * 10.0 + 80.0); - handle_ui(); - break; case 103: // CC 103: filter resonance effect_filter_resonance = map(inValue, 0, 127, 0, ENC_FILTER_RES_STEPS); - filter1.resonance(EXP_FUNC(mapfloat(effect_filter_resonance, 0, ENC_FILTER_RES_STEPS, 0.7, 5.0)) * 0.044 + 0.61); + dexed->fx.Reso = 1.0 - float(effect_filter_resonance) / ENC_FILTER_RES_STEPS; handle_ui(); break; - case 104: // CC 104: filter octave - effect_filter_octave = map(inValue, 0, 127, 0, ENC_FILTER_OCT_STEPS); - filter1.octaveControl(mapfloat(effect_filter_octave, 0, ENC_FILTER_OCT_STEPS, 0.0, 7.0)); + case 104: // CC 104: filter cutoff + effect_filter_cutoff = map(inValue, 0, 127, 0, ENC_FILTER_CUT_STEPS); + dexed->fx.Cutoff = 1.0 - float(effect_filter_cutoff) / ENC_FILTER_CUT_STEPS; handle_ui(); break; case 105: // CC 105: delay time @@ -502,12 +475,12 @@ void handleProgramChange(byte inChannel, byte inProgram) { if (inProgram < MAX_VOICES) { - load_sysex(bank, inProgram); + load_sysex(configuration.bank, inProgram); handle_ui(); } } -void handleSystemExclusive(byte *sysex, uint len) +void handleSystemExclusive(byte * sysex, uint len) { /* SYSEX MESSAGE: Parameter Change @@ -617,7 +590,7 @@ void handleSystemExclusive(byte *sysex, uint len) Serial.print(F(" = ")); Serial.print(sysex[5], DEC); Serial.print(F(", data_index = ")); - Serial.println(data_index,DEC); + Serial.println(data_index, DEC); #endif } #ifdef DEBUG @@ -696,23 +669,23 @@ void handleSystemReset(void) } /****************************************************************************** - END OF MIDI MESSAGE HANDLER + MIDI HELPER ******************************************************************************/ bool checkMidiChannel(byte inChannel) { // check for MIDI channel - if (midi_channel == MIDI_CHANNEL_OMNI) + if (configuration.midi_channel == MIDI_CHANNEL_OMNI) { return (true); } - else if (inChannel != midi_channel) + else if (inChannel != configuration.midi_channel) { #ifdef DEBUG Serial.print(F("Ignoring MIDI data on channel ")); Serial.print(inChannel); Serial.print(F("(listening on ")); - Serial.print(midi_channel); + Serial.print(configuration.midi_channel); Serial.println(F(")")); #endif return (false); @@ -720,30 +693,27 @@ bool checkMidiChannel(byte inChannel) return (true); } +/****************************************************************************** + VOLUME HELPER + ******************************************************************************/ + void set_volume(float v, float p) { - vol = v; - pan = p; + configuration.vol = v; + configuration.pan = p; #ifdef DEBUG - uint8_t tmp; Serial.print(F("Setting volume: VOL=")); Serial.print(v, DEC); Serial.print(F("[")); - tmp = EEPROM.read(EEPROM_OFFSET + EEPROM_MASTER_VOLUME_ADDR); - Serial.print(tmp, DEC); - Serial.print(F("/")); - Serial.print(float(tmp) / UCHAR_MAX, DEC); + Serial.print(configuration.vol, DEC); Serial.print(F("] PAN=")); Serial.print(F("[")); - tmp = EEPROM.read(EEPROM_OFFSET + EEPROM_PAN_ADDR); - Serial.print(tmp, DEC); - Serial.print(F("/")); - Serial.print(float(tmp) / SCHAR_MAX, DEC); + Serial.print(configuration.pan, DEC); Serial.print(F("] ")); - Serial.print(pow(v * sinf(p * PI / 2), VOLUME_CURVE), 3); + Serial.print(pow(configuration.vol * sinf(configuration.pan * PI / 2), VOLUME_CURVE), 3); Serial.print(F("/")); - Serial.println(pow(v * cosf(p * PI / 2), VOLUME_CURVE), 3); + Serial.println(pow(configuration.vol * cosf( configuration.pan * PI / 2), VOLUME_CURVE), 3); #endif // http://files.csound-tutorial.net/floss_manual/Release03/Cs_FM_03_ScrapBook/b-panning-and-spatialization.html @@ -762,58 +732,58 @@ inline float logvol(float x) return (0.001 * expf(6.908 * x)); } + +/****************************************************************************** + EEPROM HELPER + ******************************************************************************/ + void initial_values_from_eeprom(void) { - uint32_t crc_eeprom = read_eeprom_checksum(); - uint32_t crc = eeprom_crc32(EEPROM_OFFSET, EEPROM_DATA_LENGTH); + uint32_t checksum; + config_t tmp_conf; + + EEPROM_readAnything(EEPROM_START_ADDRESS, tmp_conf); + checksum = crc32((byte*)&tmp_conf + 4, sizeof(tmp_conf) - 4); #ifdef DEBUG Serial.print(F("EEPROM checksum: 0x")); - Serial.print(crc_eeprom, HEX); + Serial.print(tmp_conf.checksum, HEX); Serial.print(F(" / 0x")); - Serial.print(crc, HEX); + Serial.print(checksum, HEX); #endif - if (crc_eeprom != crc) + + if (checksum != tmp_conf.checksum) { #ifdef DEBUG Serial.print(F(" - mismatch -> initializing EEPROM!")); #endif - eeprom_write(EEPROM_UPDATE_BANK + EEPROM_UPDATE_VOICE + EEPROM_UPDATE_VOL + EEPROM_UPDATE_PAN + EEPROM_UPDATE_MIDICHANNEL); + eeprom_update(); } else { - bank = EEPROM.read(EEPROM_OFFSET + EEPROM_BANK_ADDR); - voice = EEPROM.read(EEPROM_OFFSET + EEPROM_VOICE_ADDR); - vol = float(EEPROM.read(EEPROM_OFFSET + EEPROM_MASTER_VOLUME_ADDR)) / UCHAR_MAX; - pan = float(EEPROM.read(EEPROM_OFFSET + EEPROM_PAN_ADDR)) / SCHAR_MAX; - midi_channel = EEPROM.read(EEPROM_OFFSET + EEPROM_MIDICHANNEL_ADDR); - if (midi_channel > 16) - midi_channel = MIDI_CHANNEL_OMNI; + EEPROM_readAnything(EEPROM_START_ADDRESS, configuration); + Serial.print(F(" - OK, loading!")); } #ifdef DEBUG Serial.println(); #endif } -uint32_t read_eeprom_checksum(void) -{ - return (EEPROM.read(EEPROM_CRC32_ADDR) << 24 | EEPROM.read(EEPROM_CRC32_ADDR + 1) << 16 | EEPROM.read(EEPROM_CRC32_ADDR + 2) << 8 | EEPROM.read(EEPROM_CRC32_ADDR + 3)); -} - -void update_eeprom_checksum(void) +void eeprom_write(void) { - write_eeprom_checksum(eeprom_crc32(EEPROM_OFFSET, EEPROM_DATA_LENGTH)); // recalculate crc and write to eeprom + autostore = 0; + eeprom_update_flag = true; } -void write_eeprom_checksum(uint32_t crc) +void eeprom_update(void) { - EEPROM.update(EEPROM_CRC32_ADDR, (crc & 0xff000000) >> 24); - EEPROM.update(EEPROM_CRC32_ADDR + 1, (crc & 0x00ff0000) >> 16); - EEPROM.update(EEPROM_CRC32_ADDR + 2, (crc & 0x0000ff00) >> 8); - EEPROM.update(EEPROM_CRC32_ADDR + 3, crc & 0x000000ff); + eeprom_update_flag = false; + configuration.checksum = crc32((byte*)&configuration + 4, sizeof(configuration) - 4); + EEPROM_writeAnything(EEPROM_START_ADDRESS, configuration); + Serial.println(F("Updating EEPROM with configuration data")); } -uint32_t eeprom_crc32(uint16_t calc_start, uint16_t calc_bytes) // base code from https://www.arduino.cc/en/Tutorial/EEPROMCrc +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] = { @@ -824,97 +794,19 @@ uint32_t eeprom_crc32(uint16_t calc_start, uint16_t calc_bytes) // base code fro }; uint32_t crc = ~0L; - if (calc_start + calc_bytes > EEPROM.length()) - calc_bytes = EEPROM.length() - calc_start; - - for (uint16_t index = calc_start ; index < (calc_start + calc_bytes) ; ++index) + for (byte* index = calc_start ; index < (calc_start + calc_bytes) ; ++index) { - crc = crc_table[(crc ^ EEPROM[index]) & 0x0f] ^ (crc >> 4); - crc = crc_table[(crc ^ (EEPROM[index] >> 4)) & 0x0f] ^ (crc >> 4); + crc = crc_table[(crc ^ *index) & 0x0f] ^ (crc >> 4); + crc = crc_table[(crc ^ (*index >> 4)) & 0x0f] ^ (crc >> 4); crc = ~crc; } return (crc); } -void eeprom_write(uint8_t status) -{ - eeprom_update_status |= status; - if (eeprom_update_status != 0) - autostore = 0; -#ifdef DEBUG - Serial.print(F("Updating EEPROM state to: ")); - Serial.println(eeprom_update_status, DEC); -#endif -} - -void eeprom_update(void) -{ - autostore_value = AUTOSTORE_FAST_MS; - - if (eeprom_update_status & EEPROM_UPDATE_BANK) - { - EEPROM.update(EEPROM_OFFSET + EEPROM_BANK_ADDR, bank); -#ifdef DEBUG - Serial.println(F("Bank written to EEPROM")); -#endif - eeprom_update_status &= ~EEPROM_UPDATE_BANK; - } - else if (eeprom_update_status & EEPROM_UPDATE_VOICE) - { - EEPROM.update(EEPROM_OFFSET + EEPROM_VOICE_ADDR, voice); -#ifdef DEBUG - Serial.println(F("Voice written to EEPROM")); -#endif - eeprom_update_status &= ~EEPROM_UPDATE_VOICE; - } - else if (eeprom_update_status & EEPROM_UPDATE_VOL) - { - EEPROM.update(EEPROM_OFFSET + EEPROM_MASTER_VOLUME_ADDR, uint8_t(vol * UCHAR_MAX)); -#ifdef DEBUG - Serial.println(F("Volume written to EEPROM")); -#endif - eeprom_update_status &= ~EEPROM_UPDATE_VOL; - } - else if (eeprom_update_status & EEPROM_UPDATE_PAN) - { - EEPROM.update(EEPROM_OFFSET + EEPROM_PAN_ADDR, uint8_t(pan * SCHAR_MAX)); -#ifdef DEBUG - Serial.println(F("Panorama written to EEPROM")); -#endif - eeprom_update_status &= ~EEPROM_UPDATE_PAN; - } - else if (eeprom_update_status & EEPROM_UPDATE_MIDICHANNEL ) - { - EEPROM.update(EEPROM_OFFSET + EEPROM_MIDICHANNEL_ADDR, midi_channel); - update_eeprom_checksum(); -#ifdef DEBUG - Serial.println(F("MIDI channel written to EEPROM")); -#endif - eeprom_update_status &= ~EEPROM_UPDATE_MIDICHANNEL; - } - else if (eeprom_update_status & EEPROM_UPDATE_CHECKSUM) - { - update_eeprom_checksum(); -#ifdef DEBUG - Serial.println(F("Checksum written to EEPROM")); -#endif - eeprom_update_status &= ~EEPROM_UPDATE_CHECKSUM; - autostore_value = AUTOSTORE_MS; - return; - } - - if (eeprom_update_status == 0) - eeprom_update_status |= EEPROM_UPDATE_CHECKSUM; -} - -void init_eeprom(void) -{ - for (uint8_t i = 0; i < EEPROM_DATA_LENGTH; i++) - { - EEPROM.update(EEPROM_OFFSET + i, 0); - } -} +/****************************************************************************** + DEBUG HELPER + ******************************************************************************/ #if defined (DEBUG) && defined (SHOW_CPU_LOAD_MSEC) void show_cpu_and_mem_usage(void) @@ -937,6 +829,8 @@ void show_cpu_and_mem_usage(void) Serial.print(peak, DEC); Serial.print(F(" BLOCKSIZE: ")); Serial.print(AUDIO_BLOCK_SAMPLES, DEC); + Serial.print(F(" ACTIVE_VOICES: ")); + Serial.print(active_voices, DEC); Serial.println(); AudioProcessorUsageMaxReset(); AudioMemoryUsageMaxReset(); diff --git a/PluginFx.cpp b/PluginFx.cpp new file mode 100644 index 0000000..2616ffd --- /dev/null +++ b/PluginFx.cpp @@ -0,0 +1,216 @@ +/** + + Copyright (c) 2013-2014 Pascal Gauthier. + Copyright (c) 2013-2014 Filatov Vadim. + + Filter taken from the Obxd project : + https://github.com/2DaT/Obxd + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#define _USE_MATH_DEFINES +#include +#include "PluginFx.h" + +const float dc = 1e-18; + +inline static float tptpc(float& state, float inp, float cutoff) { + float v = (inp - state) * cutoff / (1 + cutoff); + float res = v + state; + state = res + v; + return res; +} + +inline static float tptlpupw(float & state , float inp , float cutoff , float srInv) { + cutoff = (cutoff * srInv) * M_PI; + float v = (inp - state) * cutoff / (1 + cutoff); + float res = v + state; + state = res + v; + return res; +} + +//static float linsc(float param,const float min,const float max) { +// return (param) * (max - min) + min; +//} + +static float logsc(float param, const float min, const float max, const float rolloff = 19.0f) { + return ((expf(param * logf(rolloff + 1)) - 1.0f) / (rolloff)) * (max - min) + min; +} + +PluginFx::PluginFx() { + Cutoff = 1; + Reso = 0; + Gain = 1; +} + +void PluginFx::init(int sr) { + mm = 0; + s1 = s2 = s3 = s4 = c = d = 0; + R24 = 0; + + mmch = (int)(mm * 3); + mmt = mm * 3 - mmch; + + sampleRate = sr; + sampleRateInv = 1 / sampleRate; + float rcrate = sqrt((44000 / sampleRate)); + rcor24 = (970.0 / 44000) * rcrate; + rcor24Inv = 1 / rcor24; + + bright = tanf((sampleRate * 0.5f - 10) * M_PI * sampleRateInv); + + R = 1; + rcor = (480.0 / 44000) * rcrate; + rcorInv = 1 / rcor; + bandPassSw = false; + + pCutoff = -1; + pReso = -1; + + dc_r = 1.0 - (126.0 / sr); + dc_id = 0; + dc_od = 0; +} + +inline float PluginFx::NR24(float sample, float g, float lpc) { + float ml = 1 / (1 + g); + float S = (lpc * (lpc * (lpc * s1 + s2) + s3) + s4) * ml; + float G = lpc * lpc * lpc * lpc; + float y = (sample - R24 * S) / (1 + R24 * G); + return y + 1e-8; +}; + +inline float PluginFx::NR(float sample, float g) { + float y = ((sample - R * s1 * 2 - g * s1 - s2) / (1 + g * (2 * R + g))) + dc; + return y; +} + +void PluginFx::process(float *work, int sampleSize) { + // very basic DC filter + float t_fd = work[0]; + work[0] = work[0] - dc_id + dc_r * dc_od; + dc_id = t_fd; + for (int i = 1; i < sampleSize; i++) { + t_fd = work[i]; + work[i] = work[i] - dc_id + dc_r * work[i - 1]; + dc_id = t_fd; + + } + dc_od = work[sampleSize - 1]; + + if ( Gain != 1 ) { + for (int i = 0; i < sampleSize; i++ ) + work[i] *= Gain; + } + + // don't apply the LPF if the cutoff is to maximum + if ( Cutoff == 1 ) + return; + + if ( Cutoff != pCutoff || Reso != pReso ) { + rReso = (0.991 - logsc(1 - Reso, 0, 0.991)); + R24 = 3.5 * rReso; + + float cutoffNorm = logsc(Cutoff, 60, 19000); + rCutoff = (float)tanf(cutoffNorm * sampleRateInv * M_PI); + + pCutoff = Cutoff; + pReso = Reso; + + R = 1 - rReso; + } + + // THIS IS MY FAVORITE 4POLE OBXd filter + + // maybe smooth this value + float g = rCutoff; + float lpc = g / (1 + g); + + for (int i = 0; i < sampleSize; i++ ) { + float s = work[i]; + s = s - 0.45 * tptlpupw(c, s, 15, sampleRateInv); + s = tptpc(d, s, bright); + + float y0 = NR24(s, g, lpc); + + //first low pass in cascade + float v = (y0 - s1) * lpc; + float res = v + s1; + s1 = res + v; + + //damping + s1 = atanf(s1 * rcor24) * rcor24Inv; + float y1 = res; + float y2 = tptpc(s2, y1, g); + float y3 = tptpc(s3, y2, g); + float y4 = tptpc(s4, y3, g); + float mc = 0.0; + + switch (mmch) { + case 0: + mc = ((1 - mmt) * y4 + (mmt) * y3); + break; + case 1: + mc = ((1 - mmt) * y3 + (mmt) * y2); + break; + case 2: + mc = ((1 - mmt) * y2 + (mmt) * y1); + break; + case 3: + mc = y1; + break; + } + + //half volume comp + work[i] = mc * (1 + R24 * 0.45); + } +} + +/* + + // THIS IS THE 2POLE FILTER + + for(int i=0; i < sampleSize; i++ ) { + float s = work[i]; + s = s - 0.45*tptlpupw(c,s,15,sampleRateInv); + s = tptpc(d,s,bright); + + //float v = ((sample- R * s1*2 - g2*s1 - s2)/(1+ R*g1*2 + g1*g2)); + float v = NR(s,g); + float y1 = v*g + s1; + //damping + s1 = atanf(s1 * rcor) * rcorInv; + + float y2 = y1*g + s2; + s2 = y2 + y1*g; + + float mc; + if(!bandPassSw) + mc = (1-mm)*y2 + (mm)*v; + else + { + + mc =2 * ( mm < 0.5 ? + ((0.5 - mm) * y2 + (mm) * y1): + ((1-mm) * y1 + (mm-0.5) * v) + ); + } + + work[i] = mc; + } + +*/ diff --git a/PluginFx.h b/PluginFx.h new file mode 100644 index 0000000..7946493 --- /dev/null +++ b/PluginFx.h @@ -0,0 +1,72 @@ +/** + + Copyright (c) 2013 Pascal Gauthier. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#ifndef PLUGINFX_H_INCLUDED +#define PLUGINFX_H_INCLUDED + +class PluginFx { + float s1, s2, s3, s4; + float sampleRate; + float sampleRateInv; + float d, c; + float R24; + float rcor24, rcor24Inv; + float bright; + + // 24 db multimode + float mm; + float mmt; + int mmch; + inline float NR24(float sample, float g, float lpc); + + // preprocess values taken the UI + float rCutoff; + float rReso; + float rGain; + + // thread values; if these are different from the UI, + // it needs to be recalculated. + float pReso; + float pCutoff; + float pGain; + + // I am still keeping the 2pole w/multimode filter + inline float NR(float sample, float g); + bool bandPassSw; + float rcor, rcorInv; + int R; + + float dc_id; + float dc_od; + float dc_r; + + public: + PluginFx(); + + // this is set directly by the ui / parameter + float Cutoff; + float Reso; + float Gain; + + void init(int sampleRate); + void process(float *work, int sampleSize); +}; + +#endif // PLUGINFX_H_INCLUDED diff --git a/UI.cpp b/UI.cpp index 64cfaca..da81939 100644 --- a/UI.cpp +++ b/UI.cpp @@ -38,7 +38,7 @@ void handle_ui(void) { if (ui_back_to_main >= UI_AUTO_BACK_MS && (ui_state != UI_MAIN && ui_state != UI_EFFECTS_FILTER && ui_state != UI_EFFECTS_DELAY)) { - enc[0].write(map(vol * 100, 0, 100, 0, ENC_VOL_STEPS)); + enc[0].write(map(configuration.vol * 100, 0, 100, 0, ENC_VOL_STEPS)); enc_val[0] = enc[0].read(); ui_show_main(); } @@ -84,8 +84,8 @@ void handle_ui(void) switch (ui_state) { case UI_MAIN: - ui_main_state = UI_MAIN_FILTER_FRQ; - enc[i].write(effect_filter_frq); + ui_main_state = UI_MAIN_FILTER_RES; + enc[i].write(effect_filter_resonance); enc_val[i] = enc[i].read(); ui_show_effects_filter(); break; @@ -97,7 +97,7 @@ void handle_ui(void) break; case UI_EFFECTS_DELAY: ui_main_state = UI_MAIN_VOICE; - enc[i].write(voice); + enc[i].write(configuration.voice); enc_val[i] = enc[i].read(); ui_show_main(); break; @@ -114,17 +114,17 @@ void handle_ui(void) switch (ui_state) { case UI_MAIN: - enc[i].write(map(vol * 100, 0, 100, 0, ENC_VOL_STEPS)); + enc[i].write(map(configuration.vol * 100, 0, 100, 0, ENC_VOL_STEPS)); enc_val[i] = enc[i].read(); ui_show_volume(); break; case UI_VOLUME: - enc[i].write(midi_channel); + enc[i].write(configuration.midi_channel); enc_val[i] = enc[i].read(); ui_show_midichannel(); break; case UI_MIDICHANNEL: - enc[i].write(map(vol * 100, 0, 100, 0, ENC_VOL_STEPS)); + enc[i].write(map(configuration.vol * 100, 0, 100, 0, ENC_VOL_STEPS)); enc_val[i] = enc[i].read(); ui_show_main(); break; @@ -139,13 +139,13 @@ void handle_ui(void) case UI_MAIN_BANK: case UI_MAIN_BANK_SELECTED: ui_main_state = UI_MAIN_VOICE; - enc[i].write(voice); + enc[i].write(configuration.voice); enc_val[i] = enc[i].read(); break; case UI_MAIN_VOICE: case UI_MAIN_VOICE_SELECTED: ui_main_state = UI_MAIN_BANK; - enc[i].write(bank); + enc[i].write(configuration.bank); enc_val[i] = enc[i].read(); break; } @@ -155,21 +155,15 @@ void handle_ui(void) case UI_EFFECTS_DELAY: switch (ui_main_state) { - case UI_MAIN_FILTER_FRQ: - ui_main_state = UI_MAIN_FILTER_RES; - enc[i].write(effect_filter_resonance); - enc_val[i] = enc[i].read(); - ui_show_effects_filter(); - break; case UI_MAIN_FILTER_RES: - ui_main_state = UI_MAIN_FILTER_OCT; - enc[i].write(effect_filter_octave); + ui_main_state = UI_MAIN_FILTER_CUT; + enc[i].write(effect_filter_cutoff); enc_val[i] = enc[i].read(); ui_show_effects_filter(); break; - case UI_MAIN_FILTER_OCT: - ui_main_state = UI_MAIN_FILTER_FRQ; - enc[i].write(effect_filter_frq); + case UI_MAIN_FILTER_CUT: + ui_main_state = UI_MAIN_FILTER_RES; + enc[i].write(effect_filter_resonance); enc_val[i] = enc[i].read(); ui_show_effects_filter(); break; @@ -217,8 +211,8 @@ void handle_ui(void) enc[i].write(0); else if (enc[i].read() >= ENC_VOL_STEPS) enc[i].write(ENC_VOL_STEPS); - set_volume(float(map(enc[i].read(), 0, ENC_VOL_STEPS, 0, 100)) / 100, pan); - eeprom_write(EEPROM_UPDATE_VOL); + set_volume(float(map(enc[i].read(), 0, ENC_VOL_STEPS, 0, 100)) / 100, configuration.pan); + eeprom_write(); ui_show_volume(); break; case UI_MIDICHANNEL: @@ -226,8 +220,8 @@ void handle_ui(void) enc[i].write(0); else if (enc[i].read() >= 16) enc[i].write(16); - midi_channel = enc[i].read(); - eeprom_write(EEPROM_UPDATE_MIDICHANNEL); + configuration.midi_channel = enc[i].read(); + eeprom_write(); ui_show_midichannel(); break; } @@ -235,6 +229,12 @@ void handle_ui(void) case 1: // right encoder moved switch (ui_state) { + case UI_VOLUME: + ui_state = UI_MAIN; + lcd.clear(); + enc[1].write(configuration.voice); + ui_show_main(); + break; case UI_MAIN: switch (ui_main_state) { @@ -245,39 +245,39 @@ void handle_ui(void) enc[i].write(0); else if (enc[i].read() > max_loaded_banks - 1) enc[i].write(max_loaded_banks - 1); - bank = enc[i].read(); - get_voice_names_from_bank(bank); - load_sysex(bank, voice); - eeprom_write(EEPROM_UPDATE_BANK); + configuration.bank = enc[i].read(); + get_voice_names_from_bank(configuration.bank); + load_sysex(configuration.bank, configuration.voice); + eeprom_write(); break; case UI_MAIN_VOICE: ui_main_state = UI_MAIN_VOICE_SELECTED; case UI_MAIN_VOICE_SELECTED: if (enc[i].read() <= 0) { - if (bank > 0) + if (configuration.bank > 0) { enc[i].write(MAX_VOICES - 1); - bank--; - get_voice_names_from_bank(bank); + configuration.bank--; + get_voice_names_from_bank(configuration.bank); } else enc[i].write(0); } else if (enc[i].read() > MAX_VOICES - 1) { - if (bank < MAX_BANKS - 1) + if (configuration.bank < MAX_BANKS - 1) { enc[i].write(0); - bank++; - get_voice_names_from_bank(bank); + configuration.bank++; + get_voice_names_from_bank(configuration.bank); } else enc[i].write(MAX_VOICES - 1); } - voice = enc[i].read(); - load_sysex(bank, voice); - eeprom_write(EEPROM_UPDATE_VOICE); + configuration.voice = enc[i].read(); + load_sysex(configuration.bank, configuration.voice); + eeprom_write(); break; } ui_show_main(); @@ -285,54 +285,28 @@ void handle_ui(void) case UI_EFFECTS_FILTER: switch (ui_main_state) { - case UI_MAIN_FILTER_FRQ: - if (enc[i].read() <= 0) - enc[i].write(0); - else if (enc[i].read() > ENC_FILTER_FRQ_STEPS) - enc[i].write(ENC_FILTER_FRQ_STEPS); - effect_filter_frq = enc[i].read(); - if (effect_filter_frq == ENC_FILTER_FRQ_STEPS) - { - // turn "off" filter - mixer1.gain(0, 0.0); // filtered signal off - mixer1.gain(3, 1.0); // original signal on - } - else - { - // turn "on" filter - mixer1.gain(0, 1.0); // filtered signal on - mixer1.gain(3, 0.0); // original signal off - } - filter1.frequency(EXP_FUNC((float)map(effect_filter_frq, 0, ENC_FILTER_FRQ_STEPS, 0, 1024) / 150.0) * 10.0 + 80.0); -#ifdef DEBUG - Serial.print(F("Setting filter frequency to: ")); - Serial.println(EXP_FUNC((float)map(effect_filter_frq, 0, ENC_FILTER_FRQ_STEPS, 0, 1024) / 150.0) * 10.0 + 80.0, DEC); -#endif - break; case UI_MAIN_FILTER_RES: if (enc[i].read() <= 0) enc[i].write(0); else if (enc[i].read() > ENC_FILTER_RES_STEPS) enc[i].write(ENC_FILTER_RES_STEPS); effect_filter_resonance = enc[i].read(); - //filter1.resonance(mapfloat(effect_filter_resonance, 0, ENC_FILTER_RES_STEPS, 0.7, 5.0)); - filter1.resonance(EXP_FUNC(mapfloat(effect_filter_resonance, 0, ENC_FILTER_RES_STEPS, 0.7, 5.0)) * 0.044 + 0.61); - + dexed->fx.Reso = 1.0 - float(effect_filter_resonance) / ENC_FILTER_RES_STEPS; #ifdef DEBUG Serial.print(F("Setting filter resonance to: ")); - Serial.println(EXP_FUNC(mapfloat(effect_filter_resonance, 0, ENC_FILTER_RES_STEPS, 0.7, 5.0)) * 0.044 + 0.61, 2); + Serial.println(1.0 - float(effect_filter_resonance) / ENC_FILTER_RES_STEPS, 5); #endif break; - case UI_MAIN_FILTER_OCT: + case UI_MAIN_FILTER_CUT: if (enc[i].read() <= 0) enc[i].write(0); - else if (enc[i].read() > ENC_FILTER_OCT_STEPS) - enc[i].write(ENC_FILTER_OCT_STEPS); - effect_filter_octave = enc[i].read(); - filter1.octaveControl(mapfloat(effect_filter_octave, 0, ENC_FILTER_OCT_STEPS, 0.0, 7.0)); + else if (enc[i].read() > ENC_FILTER_CUT_STEPS) + enc[i].write(ENC_FILTER_CUT_STEPS); + effect_filter_cutoff = enc[i].read(); + dexed->fx.Cutoff = 1.0 - float(effect_filter_cutoff) / ENC_FILTER_CUT_STEPS; #ifdef DEBUG - Serial.print(F("Setting filter octave control to: ")); - Serial.println(mapfloat(effect_filter_octave, 0, ENC_FILTER_OCT_STEPS, 0.0, 7.0), 2); + Serial.print(F("Setting filter cutoff to: ")); + Serial.println(1.0 - float(effect_filter_cutoff) / ENC_FILTER_CUT_STEPS, 5); #endif break; } @@ -405,9 +379,9 @@ void ui_show_main(void) lcd.clear(); } - lcd.show(0, 0, 2, bank); + lcd.show(0, 0, 2, configuration.bank); lcd.show(0, 2, 1, " "); - strip_extension(bank_names[bank], bank_name); + strip_extension(bank_names[configuration.bank], bank_name); if (ui_main_state == UI_MAIN_BANK || ui_main_state == UI_MAIN_BANK_SELECTED) { @@ -422,18 +396,18 @@ void ui_show_main(void) lcd.show(0, 11, 1, " "); } - lcd.show(1, 0, 2, voice + 1); + lcd.show(1, 0, 2, configuration.voice + 1); lcd.show(1, 2, 1, " "); if (ui_main_state == UI_MAIN_VOICE || ui_main_state == UI_MAIN_VOICE_SELECTED) { lcd.show(1, 2, 1, "["); - lcd.show(1, 3, 10, voice_names[voice]); + lcd.show(1, 3, 10, voice_names[configuration.voice]); lcd.show(1, 14, 1, "]"); } else { lcd.show(1, 2, 1, " "); - lcd.show(1, 3, 10, voice_names[voice]); + lcd.show(1, 3, 10, voice_names[configuration.voice]); lcd.show(1, 14, 1, " "); } @@ -450,18 +424,18 @@ void ui_show_volume(void) lcd.show(0, 0, LCD_CHARS, "Volume"); } - lcd.show(0, LCD_CHARS - 3, 3, vol * 100); - if (vol == 0.0) + lcd.show(0, LCD_CHARS - 3, 3, configuration.vol * 100); + if (configuration.vol == 0.0) lcd.show(1, 0, LCD_CHARS , " "); else { - if (vol < (float(LCD_CHARS) / 100)) + if (configuration.vol < (float(LCD_CHARS) / 100)) lcd.show(1, 0, LCD_CHARS, "*"); else { - for (uint8_t i = 0; i < map(vol * 100, 0, 100, 0, LCD_CHARS); i++) + for (uint8_t i = 0; i < map(configuration.vol * 100, 0, 100, 0, LCD_CHARS); i++) lcd.show(1, i, 1, "*"); - for (uint8_t i = map(vol * 100, 0, 100, 0, LCD_CHARS); i < LCD_CHARS; i++) + for (uint8_t i = map(configuration.vol * 100, 0, 100, 0, LCD_CHARS); i < LCD_CHARS; i++) lcd.show(1, i, 1, " "); } } @@ -479,12 +453,12 @@ void ui_show_midichannel(void) lcd.show(0, 0, LCD_CHARS, "MIDI Channel"); } - if (midi_channel == MIDI_CHANNEL_OMNI) + if (configuration.midi_channel == MIDI_CHANNEL_OMNI) lcd.show(1, 0, 4, "OMNI"); else { - lcd.show(1, 0, 2, midi_channel); - if (midi_channel == 1) + lcd.show(1, 0, 2, configuration.midi_channel); + if (configuration.midi_channel == 1) lcd.show(1, 2, 2, " "); } @@ -497,32 +471,12 @@ void ui_show_effects_filter(void) { lcd.clear(); lcd.show(0, 0, LCD_CHARS, "Filter"); - lcd.show(0, 7, 2, "F:"); lcd.show(1, 0, 4, "Res:"); - lcd.show(1, 8, 4, "Oct:"); + lcd.show(1, 8, 4, "Cut:"); } - if (effect_filter_frq == ENC_FILTER_FRQ_STEPS) - { - lcd.show(0, 10, 4, "OFF "); - } - else - { - lcd.show(0, 10, 4, uint16_t(EXP_FUNC((float)map(effect_filter_frq, 0, ENC_FILTER_FRQ_STEPS, 0, 1024) / 150.0) * 10.0 + 80.5)); - } lcd.show(1, 5, 2, map(effect_filter_resonance, 0, ENC_FILTER_RES_STEPS, 0, 99)); - lcd.show(1, 13, 2, map(effect_filter_octave, 0, ENC_FILTER_OCT_STEPS, 0, 80)); - - if (ui_main_state == UI_MAIN_FILTER_FRQ) - { - lcd.show(0, 9, 1, "["); - lcd.show(0, 14, 1, "]"); - } - else - { - lcd.show(0, 9, 1, " "); - lcd.show(0, 14, 1, " "); - } + lcd.show(1, 13, 2, map(effect_filter_cutoff, 0, ENC_FILTER_CUT_STEPS, 0, 99)); if (ui_main_state == UI_MAIN_FILTER_RES) { @@ -535,7 +489,7 @@ void ui_show_effects_filter(void) lcd.show(1, 7, 1, " "); } - if (ui_main_state == UI_MAIN_FILTER_OCT) + if (ui_main_state == UI_MAIN_FILTER_CUT) { lcd.show(1, 12, 1, "["); lcd.show(1, 15, 1, "]"); diff --git a/UI.h b/UI.h index c77003f..c720677 100644 --- a/UI.h +++ b/UI.h @@ -36,29 +36,23 @@ extern Encoder4 enc[2]; extern int32_t enc_val[2]; extern Bounce but[2]; -extern float vol; -extern float pan; extern LiquidCrystalPlus_I2C lcd; -extern uint8_t bank; +extern config_t configuration; extern uint8_t max_loaded_banks; -extern uint8_t voice; extern char bank_name[BANK_NAME_LEN]; extern char voice_name[VOICE_NAME_LEN]; extern uint8_t ui_state; extern uint8_t ui_main_state; -extern uint8_t midi_channel; -extern void eeprom_write(uint8_t status); +extern void eeprom_write(void); extern void set_volume(float v, float pan); extern elapsedMillis autostore; extern elapsedMillis long_button_pressed; -extern uint8_t effect_filter_frq; +extern uint8_t effect_filter_cutoff; extern uint8_t effect_filter_resonance; -extern uint8_t effect_filter_octave; extern uint8_t effect_delay_time; extern uint8_t effect_delay_feedback; extern uint8_t effect_delay_volume; extern bool effect_delay_sync; -extern AudioFilterStateVariable filter1; extern AudioEffectDelay delay1; extern AudioMixer4 mixer1; extern AudioMixer4 mixer2; @@ -72,7 +66,7 @@ void ui_show_effects_delay(void); float mapfloat(float val, float in_min, float in_max, float out_min, float out_max); enum ui_states {UI_MAIN, UI_VOLUME, UI_MIDICHANNEL, UI_EFFECTS_FILTER, UI_EFFECTS_DELAY}; -enum ui_main_states {UI_MAIN_BANK, UI_MAIN_VOICE, UI_MAIN_BANK_SELECTED, UI_MAIN_VOICE_SELECTED, UI_MAIN_FILTER_FRQ, UI_MAIN_FILTER_RES, UI_MAIN_FILTER_OCT, UI_MAIN_DELAY_TIME, UI_MAIN_DELAY_FEEDBACK, UI_MAIN_DELAY_VOLUME}; +enum ui_main_states {UI_MAIN_BANK, UI_MAIN_VOICE, UI_MAIN_BANK_SELECTED, UI_MAIN_VOICE_SELECTED, UI_MAIN_FILTER_RES, UI_MAIN_FILTER_CUT, UI_MAIN_DELAY_TIME, UI_MAIN_DELAY_FEEDBACK, UI_MAIN_DELAY_VOLUME}; class MyEncoder : public Encoder { diff --git a/config.h b/config.h index 77d4b90..0fd77d3 100644 --- a/config.h +++ b/config.h @@ -31,7 +31,7 @@ // ATTENTION! For better latency you have to redefine AUDIO_BLOCK_SAMPLES from // 128 to 64 in /cores/teensy3/AudioStream.h -#define VERSION "0.9.2" +#define VERSION "0.9.4" //************************************************************************************************* //* DEVICE SETTINGS @@ -41,6 +41,7 @@ #define MIDI_DEVICE_DIN Serial1 #define MIDI_DEVICE_USB 1 #define MIDI_DEVICE_USB_HOST 1 +#define MIDI_DEVICE_NUMBER 0 // AUDIO // If nothing is defined PT8211 is used as audio output device! @@ -89,6 +90,11 @@ #endif #define SAMPLE_RATE 44100 +//************************************************************************************************* +//* UI AND DATA-STORE SETTINGS +//************************************************************************************************* +#define CONTROL_RATE_MS 200 +#define TIMER_UI_HANDLING_MS 100 //************************************************************************************************* //* DEBUG OUTPUT SETTINGS @@ -114,13 +120,11 @@ // Encoder with button #define ENC_VOL_STEPS 43 -#define ENC_FILTER_FRQ_STEPS 50 -#define ENC_FILTER_RES_STEPS 35 -#define ENC_FILTER_OCT_STEPS 27 +#define ENC_FILTER_RES_STEPS 100 +#define ENC_FILTER_CUT_STEPS 100 #define ENC_DELAY_TIME_STEPS 50 #define ENC_DELAY_FB_STEPS 35 #define ENC_DELAY_VOLUME_STEPS 50 -#define TIMER_UI_HANDLING_MS 100 #define NUM_ENCODER 2 #define ENC_L_PIN_A 3 #define ENC_L_PIN_B 2 @@ -144,22 +148,7 @@ #define AUTOSTORE_FAST_MS 50 // EEPROM address -#define EEPROM_OFFSET 0 -#define EEPROM_DATA_LENGTH 5 - -#define EEPROM_CRC32_ADDR EEPROM.length()-sizeof(uint32_t)-33 -#define EEPROM_BANK_ADDR 0 -#define EEPROM_VOICE_ADDR 1 -#define EEPROM_MASTER_VOLUME_ADDR 2 -#define EEPROM_PAN_ADDR 3 -#define EEPROM_MIDICHANNEL_ADDR 4 - -#define EEPROM_UPDATE_BANK (1<<0) -#define EEPROM_UPDATE_VOICE (1<<1) -#define EEPROM_UPDATE_VOL (1<<2) -#define EEPROM_UPDATE_PAN (1<<3) -#define EEPROM_UPDATE_MIDICHANNEL (1<<4) -#define EEPROM_UPDATE_CHECKSUM (1<<7) +#define EEPROM_START_ADDRESS 0 #define MAX_BANKS 100 #define MAX_VOICES 32 // voices per bank @@ -193,4 +182,13 @@ #define USE_TEENSY_DSP 1 #define SUM_UP_AS_INT 1 +// struct for holding the current configuration +struct config_t { + uint32_t checksum; + uint8_t bank; + uint8_t voice; + float vol; + float pan; + uint8_t midi_channel; +}; #endif // CONFIG_H_INCLUDED diff --git a/dexed.cpp b/dexed.cpp index 8a0a99e..16a830d 100644 --- a/dexed.cpp +++ b/dexed.cpp @@ -33,6 +33,7 @@ #include "sin.h" #include "freqlut.h" #include "controllers.h" +#include "PluginFx.h" #include #include #ifdef USE_TEENSY_DSP @@ -51,6 +52,7 @@ Dexed::Dexed(int rate) Lfo::init(rate); PitchEnv::init(rate); Env::init_sr(rate); + fx.init(rate); engineMkI = new EngineMkI; engineOpl = new EngineOpl; @@ -63,7 +65,7 @@ Dexed::Dexed(int rate) voices[i].live = false; } - max_notes = 16; + max_notes = MAX_NOTES; currentNote = 0; resetControllers(); controllers.masterTune = 0; @@ -107,41 +109,41 @@ void Dexed::deactivate(void) void Dexed::getSamples(uint16_t n_samples, int16_t* buffer) { uint16_t i; + float sumbuf[n_samples]; - if (refreshVoice) { - for (i = 0; i < max_notes; i++) { + if (refreshVoice) + { + for (i = 0; i < max_notes; i++) + { if ( voices[i].live ) voices[i].dx7_note->update(data, voices[i].midi_note, voices[i].velocity); } + lfo.reset(data + 137); refreshVoice = false; } - for (i = 0; i < n_samples; i += _N_) { + for (i = 0; i < n_samples; i += _N_) + { AlignedBuf audiobuf; -#ifndef SUM_UP_AS_INT - float sumbuf[_N_]; -#endif - for (uint8_t j = 0; j < _N_; ++j) { + for (uint8_t j = 0; j < _N_; ++j) + { audiobuf.get()[j] = 0; -#ifndef SUM_UP_AS_INT - sumbuf[j] = 0.0; -#else - buffer[i + j] = 0; -#endif + sumbuf[i + j] = 0.0; } int32_t lfovalue = lfo.getsample(); int32_t lfodelay = lfo.getdelay(); -#ifdef SUM_UP_AS_INT - int32_t sum; -#endif - for (uint8_t note = 0; note < max_notes; ++note) { - if (voices[note].live) { + for (uint8_t note = 0; note < max_notes; ++note) + { + if (voices[note].live) + { voices[note].dx7_note->compute(audiobuf.get(), lfovalue, lfodelay, &controllers); - for (uint8_t j = 0; j < _N_; ++j) { + + for (uint8_t j = 0; j < _N_; ++j) + { int32_t val = audiobuf.get()[j]; val = val >> 4; #ifdef USE_TEENSY_DSP @@ -149,45 +151,29 @@ void Dexed::getSamples(uint16_t n_samples, int16_t* buffer) #else int32_t clip_val = val < -(1 << 24) ? 0x8000 : val >= (1 << 24) ? 0x7fff : val >> 9; #endif -#ifdef SUM_UP_AS_INT - //sum = buffer[i + j] + (clip_val >> REDUCE_LOUDNESS)*(float(data[DEXED_GLOBAL_PARAMETER_OFFSET+DEXED_VOICE_VOLUME])/255); - sum = buffer[i + j] + (clip_val >> REDUCE_LOUDNESS); - if (buffer[i + j] > 0 && clip_val > 0 && sum < 0) - { - sum = INT_MAX; - overload++; - } - else if (buffer[i + j] < 0 && clip_val < 0 && sum > 0) - { - sum = INT_MIN; - overload++; - } - buffer[i + j] = sum; - audiobuf.get()[j] = 0; -#else - float f = static_cast(clip_val >> REDUCE_LOUDNESS) / 0x8000; - if (f > 1) + + float f = static_cast(clip_val >> REDUCE_LOUDNESS) / 0x7fff; + if (f > 1.0) { - f = 1; + f = 1.0; overload++; } - else if (f < -1) + else if (f < -1.0) { - f = -1; + f = -1.0; overload++; } - sumbuf[j] += f; + sumbuf[i + j] += f; audiobuf.get()[j] = 0; -#endif } } } -#ifndef SUM_UP_AS_INT - for (uint8_t j = 0; j < _N_; ++j) { - buffer[i + j] = static_cast(sumbuf[j] * 0x8000); - } -#endif } + + fx.process(sumbuf, n_samples); + + for (i = 0; i < n_samples; ++i) + buffer[i] = static_cast(sumbuf[i] * 0x7fff); } void Dexed::keydown(uint8_t pitch, uint8_t velo) { @@ -391,6 +377,54 @@ uint8_t Dexed::getMaxNotes(void) return max_notes; } +uint8_t Dexed::getNumNotesPlaying(void) +{ + uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers + uint8_t i; + uint8_t count_playing_voices = 0; + + for (i = 0; i < max_notes; i++) + { + if (voices[i].live == true) + { + uint8_t op_amp = 0; + uint8_t op_carrier_num = 0; + + memset(&voiceStatus, 0, sizeof(VoiceStatus)); + voices[i].dx7_note->peekVoiceStatus(voiceStatus); + + for (uint8_t op = 0; op < 6; op++) + { + if ((op_carrier & (1 << op))) + { + // this voice is a carrier! + op_carrier_num++; + if (voiceStatus.amp[op] <= 1069 && voiceStatus.ampStep[op] == 4) + { + // this voice produces no audio output + op_amp++; + } + } + } + + if (op_amp == op_carrier_num) + { + // all carrier-operators are silent -> disable the voice + voices[i].live = false; + voices[i].sustained = false; + voices[i].keydown = false; +#ifdef DEBUG + Serial.print(F("Shutdown voice: ")); + Serial.println(i, DEC); +#endif + } + else + count_playing_voices++; + } + } + return (count_playing_voices); +} + bool Dexed::loadSysexVoice(uint8_t* new_data) { uint8_t* p_data = data; diff --git a/dexed.h b/dexed.h index 02a7383..6188f1e 100644 --- a/dexed.h +++ b/dexed.h @@ -34,6 +34,7 @@ #include "fm_core.h" #include "EngineMkI.h" #include "EngineOpl.h" +#include "PluginFx.h" #include #include "config.h" @@ -160,9 +161,11 @@ class Dexed void keydown(uint8_t pitch, uint8_t velo); void setSustain(bool sustain); bool getSustain(void); + uint8_t getNumNotesPlaying(void); ProcessorVoice voices[MAX_NOTES]; Controllers controllers; + PluginFx fx; uint8_t data[173] = { 95, 29, 20, 50, 99, 95, 00, 00, 41, 00, 19, 00, 00, 03, 00, 06, 79, 00, 01, 00, 14, // OP6 eg_rate_1-4, level_1-4, kbd_lev_scl_brk_pt, kbd_lev_scl_lft_depth, kbd_lev_scl_rht_depth, kbd_lev_scl_lft_curve, kbd_lev_scl_rht_curve, kbd_rate_scaling, amp_mod_sensitivity, key_vel_sensitivity, operator_output_level, osc_mode, osc_freq_coarse, osc_freq_fine, osc_detune @@ -190,6 +193,7 @@ class Dexed bool monoMode; bool refreshVoice; uint8_t engineType; + VoiceStatus voiceStatus; Lfo lfo; FmCore* engineMsfa; EngineMkI* engineMkI;