diff --git a/MicroDexed.ino b/MicroDexed.ino index 6a7a66b..c49225e 100644 --- a/MicroDexed.ino +++ b/MicroDexed.ino @@ -55,6 +55,9 @@ #include "synth_mda_epiano.h" #include #endif +#if defined(USE_DELAY_8M) +#include "effect_delay_ext8.h" +#endif // Audio engines AudioSynthDexed* MicroDexed[NUM_DEXED]; @@ -70,7 +73,11 @@ AudioFilterBiquad* modchorus_filter[NUM_DEXED]; AudioEffectModulatedDelay* modchorus[NUM_DEXED]; AudioMixer<2>* chorus_mixer[NUM_DEXED]; AudioMixer<2>* delay_fb_mixer[NUM_DEXED]; +#if defined(USE_DELAY_8M) +AudioEffectDelayExternal8* delay_fx[NUM_DEXED]; +#else AudioEffectDelay* delay_fx[NUM_DEXED]; +#endif AudioMixer<2>* delay_mixer[NUM_DEXED]; #endif AudioEffectMonoStereo* mono2stereo[NUM_DEXED]; @@ -289,7 +296,11 @@ FLASHMEM void create_audio_dexed_chain(uint8_t instance_id) { modchorus[instance_id] = new AudioEffectModulatedDelay(); chorus_mixer[instance_id] = new AudioMixer<2>(); delay_fb_mixer[instance_id] = new AudioMixer<2>(); +#if defined(USE_DELAY_8M) + delay_fx[instance_id] = new AudioEffectDelayExternal8(DELAY_MAX_TIME); +#else delay_fx[instance_id] = new AudioEffectDelay(); +#endif delay_mixer[instance_id] = new AudioMixer<2>(); #endif diff --git a/UI.hpp b/UI.hpp index f50746c..6a8bbdb 100644 --- a/UI.hpp +++ b/UI.hpp @@ -43,6 +43,9 @@ #include "synth_mda_epiano.h" #include #endif +#if defined(USE_DELAY_8M) +#include "effect_delay_ext8.h" +#endif #define _LCDML_DISP_cols LCD_cols #define _LCDML_DISP_rows LCD_rows @@ -92,7 +95,11 @@ extern AudioSynthWaveform* chorus_modulator[NUM_DEXED]; extern AudioEffectModulatedDelay* modchorus[NUM_DEXED]; extern AudioMixer<2>* chorus_mixer[NUM_DEXED]; extern AudioMixer<2>* delay_fb_mixer[NUM_DEXED]; +#if defined(USE_DELAY_8M) +extern AudioEffectDelayExternal8* delay_fx[NUM_DEXED]; +#else extern AudioEffectDelay* delay_fx[NUM_DEXED]; +#endif extern AudioMixer<2>* delay_mixer[NUM_DEXED]; #endif extern AudioEffectMonoStereo* mono2stereo[NUM_DEXED]; @@ -4135,7 +4142,7 @@ void UI_func_drums_main_volume(uint8_t param) { configuration.drums.main_vol = constrain(configuration.drums.main_vol - ENCODER[ENC_L].speed(), DRUMS_MAIN_VOL_MIN, DRUMS_MAIN_VOL_MAX); } display_bar_int("DRM Main Vol", configuration.drums.main_vol, 1.0, DRUMS_MAIN_VOL_MIN, DRUMS_MAIN_VOL_MAX, 3, false, false, false); - float tmp_vol=configuration.drums.main_vol/100.0; + float tmp_vol = configuration.drums.main_vol / 100.0; master_mixer_r.gain(MASTER_MIX_CH_DRUMS, tmp_vol); master_mixer_l.gain(MASTER_MIX_CH_DRUMS, tmp_vol); } @@ -6237,65 +6244,60 @@ bool UI_select_name(uint8_t y, uint8_t x, char* edit_string, uint8_t len, bool i } if (LCDML.BT_checkDown() || LCDML.BT_checkUp() || LCDML.BT_checkEnter()) { - switch (edit_mode) { - case true: - edit_value = search_accepted_char(edit_string[edit_pos]); - if (LCDML.BT_checkDown()) { - if (edit_value < sizeof(accepted_chars) - 2) - edit_value++; - } else if (LCDML.BT_checkUp()) { - if (edit_value > 0) - edit_value--; - } else if (LCDML.BT_checkEnter()) { + if (edit_mode) { + edit_value = search_accepted_char(edit_string[edit_pos]); + if (LCDML.BT_checkDown()) { + if (edit_value < sizeof(accepted_chars) - 2) + edit_value++; + } else if (LCDML.BT_checkUp()) { + if (edit_value > 0) + edit_value--; + } else if (LCDML.BT_checkEnter()) { + edit_mode = !edit_mode; + display.setCursor(LCD_cols - 1, 0); + display.print(F(" ")); + } + + edit_string[edit_pos] = accepted_chars[edit_value]; + display.setCursor(x + edit_pos, y); + display.print(edit_string[edit_pos]); + } else { + if (LCDML.BT_checkDown()) { + if (edit_pos < len - 1) + edit_pos++; + } else if (LCDML.BT_checkUp()) { + if (edit_pos > 0) + edit_pos--; + } else if (LCDML.BT_checkEnter()) { + if (edit_pos == len - 1) { // OK pressed + edit_pos = 0; + edit_mode = false; + string_trim(edit_string); + return (true); + } else { edit_mode = !edit_mode; display.setCursor(LCD_cols - 1, 0); - display.print(F(" ")); - } - - edit_string[edit_pos] = accepted_chars[edit_value]; - display.setCursor(x + edit_pos, y); - display.print(edit_string[edit_pos]); - - break; - case false: - if (LCDML.BT_checkDown()) { - if (edit_pos < len - 1) - edit_pos++; - } else if (LCDML.BT_checkUp()) { - if (edit_pos > 0) - edit_pos--; - } else if (LCDML.BT_checkEnter()) { - if (edit_pos == len - 1) { // OK pressed - edit_pos = 0; - edit_mode = false; - string_trim(edit_string); - return (true); - } else { - edit_mode = !edit_mode; - display.setCursor(LCD_cols - 1, 0); - display.write(0); - } - } - - if (edit_pos > len - 2) { - display.noBlink(); - display.setCursor(x - 1, y); - display.print(F(" ")); - display.setCursor(x + len - 1, y); - display.print(F(" ")); - display.setCursor(LCD_cols - 4, y); - display.print(F("[OK]")); - } else if (edit_pos == len - 2) { - display.setCursor(x - 1, y); - display.print(F("[")); - display.setCursor(x + len - 1, y); - display.print(F("]")); - display.setCursor(LCD_cols - 4, y); - display.print(F(" ")); - display.blink(); + display.write(0); } + } - break; + if (edit_pos > len - 2) { + display.noBlink(); + display.setCursor(x - 1, y); + display.print(F(" ")); + display.setCursor(x + len - 1, y); + display.print(F(" ")); + display.setCursor(LCD_cols - 4, y); + display.print(F("[OK]")); + } else if (edit_pos == len - 2) { + display.setCursor(x - 1, y); + display.print(F("[")); + display.setCursor(x + len - 1, y); + display.print(F("]")); + display.setCursor(LCD_cols - 4, y); + display.print(F(" ")); + display.blink(); + } } } diff --git a/config.h b/config.h index c15c916..0e9edc2 100644 --- a/config.h +++ b/config.h @@ -154,11 +154,16 @@ #endif // DELAYTIME +#define USE_DELAY_8M 1 #if NUM_DEXED > 1 #if defined(ARDUINO_TEENSY41) #define DELAY_MAX_TIME 500 #elif defined(ARDUINO_TEENSY41) +#if defined(USE_DELAY_8M) +#define DELAY_MAX_TIME 5000 +#else #define DELAY_MAX_TIME 250 +#endif #else #define DELAY_MAX_TIME 100 #endif diff --git a/effect_delay_ext8.cpp b/effect_delay_ext8.cpp new file mode 100644 index 0000000..2a72c1f --- /dev/null +++ b/effect_delay_ext8.cpp @@ -0,0 +1,171 @@ +/* + MicroDexed + + MicroDexed is a port of the Dexed sound engine + Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android + + 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 + + effect_delay_ext8 is a special delay class for APS6404L-3SQR based memory chips on + a Teensy-4.1. The cod is a combination of the PJRC delay_ext class and suggestions + from https://forum.pjrc.com/threads/29276-Limits-of-delay-effect-in-audio-library/page5?highlight=APS6404L-3SQR + +*/ + +#include +#include "effect_delay_ext8.h" + +#define SPISETTING SPISettings(20000000, MSBFIRST, SPI_MODE0) + +#define SPIRAM_MOSI_PIN 11 //----------------- +#define SPIRAM_MISO_PIN 12 // for Teensy 4.1 +#define SPIRAM_SCK_PIN 13 // +#define SPIRAM_CS_PIN 36 //----------------- + +#define MEMBOARD_CS0_PIN 2 +#define MEMBOARD_CS1_PIN 3 +#define MEMBOARD_CS2_PIN 4 + +void AudioEffectDelayExternal8::update(void) +{ + audio_block_t *block; + uint32_t n, channel, read_offset; + + // grab incoming data and put it into the memory + block = receiveReadOnly(); + if (block) { + if (head_offset + AUDIO_BLOCK_SAMPLES <= memory_length) { + // a single write is enough + write(head_offset, AUDIO_BLOCK_SAMPLES, block->data); + head_offset += AUDIO_BLOCK_SAMPLES; + } else { + // write wraps across end-of-memory + n = memory_length - head_offset; + write(head_offset, n, block->data); + head_offset = AUDIO_BLOCK_SAMPLES - n; + write(0, head_offset, block->data + n); + } + release(block); + } else { + // if no input, store zeros, so later playback will + // not be random garbage previously stored in memory + if (head_offset + AUDIO_BLOCK_SAMPLES <= memory_length) { + zero(head_offset, AUDIO_BLOCK_SAMPLES); + head_offset += AUDIO_BLOCK_SAMPLES; + } else { + n = memory_length - head_offset; + zero(head_offset, n); + head_offset = AUDIO_BLOCK_SAMPLES - n; + zero(0, head_offset); + } + } + + // transmit the delayed outputs + for (channel = 0; channel < 8; channel++) { + if (!(activemask & (1<data); + } else { + // read wraps across end-of-memory + n = memory_length - read_offset; + read(read_offset, n, block->data); + read(0, AUDIO_BLOCK_SAMPLES - n, block->data + n); + } + transmit(block, channel); + release(block); + } +} + +uint32_t AudioEffectDelayExternal8::allocated = 0; + +void AudioEffectDelayExternal8::initialize(uint32_t samples) +{ + uint32_t memsize, avail; + + activemask = 0; + head_offset = 0; + + SPI.setMOSI(SPIRAM_MOSI_PIN); + SPI.setMISO(SPIRAM_MISO_PIN); + SPI.setSCK(SPIRAM_SCK_PIN); + + SPI.setCS(SPIRAM_CS_PIN); // added for Teensy 4.1 + SPI.begin(); + + memsize = (2^23); // 8388608 bytes + pinMode(SPIRAM_CS_PIN, OUTPUT); + digitalWriteFast(SPIRAM_CS_PIN, HIGH); + + avail = memsize - allocated; + if (avail < AUDIO_BLOCK_SAMPLES*2+1) { + return; + } + if (samples > avail) samples = avail; + memory_begin = allocated; + allocated += samples; + memory_length = samples; + + zero(0, memory_length); +} + +void AudioEffectDelayExternal8::read(uint32_t offset, uint32_t count, int16_t *data) +{ + uint32_t addr = memory_begin + offset; + + addr *= 2; + SPI.beginTransaction(SPISETTING); + digitalWriteFast(SPIRAM_CS_PIN, LOW); + SPI.transfer16((0x03 << 8) | (addr >> 16)); + SPI.transfer16(addr & 0xFFFF); + while (count) { + *data++ = (int16_t)(SPI.transfer16(0)); + count--; + } + digitalWriteFast(SPIRAM_CS_PIN, HIGH); + SPI.endTransaction(); +} + +void AudioEffectDelayExternal8::write(uint32_t offset, uint32_t count, const int16_t *data) +{ + uint32_t addr = memory_begin + offset; + + addr *= 2; + + SPI.beginTransaction(SPISETTING); + digitalWriteFast(SPIRAM_CS_PIN, LOW); + SPI.transfer(0x06); //write-enable before every write + digitalWriteFast(SPIRAM_CS_PIN, HIGH); + asm volatile ("NOP\n NOP\n NOP\n NOP\n NOP\n NOP\n"); + digitalWriteFast(SPIRAM_CS_PIN, LOW); + SPI.transfer16((0x02 << 8) | (addr >> 16)); + SPI.transfer16(addr & 0xFFFF); + while (count) { + int16_t w = 0; + if (data) w = *data++; + SPI.transfer16(w); + count--; + } + digitalWriteFast(SPIRAM_CS_PIN, HIGH); + SPI.endTransaction(); +} diff --git a/effect_delay_ext8.h b/effect_delay_ext8.h new file mode 100644 index 0000000..9dd1ec7 --- /dev/null +++ b/effect_delay_ext8.h @@ -0,0 +1,80 @@ +/* + MicroDexed + + MicroDexed is a port of the Dexed sound engine + Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android + + 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 + + effect_delay_ext8 is a special delay class for APS6404L-3SQR based memory chips on + a Teensy-4.1. The cod is a combination of the PJRC delay_ext class and suggestions + from https://forum.pjrc.com/threads/29276-Limits-of-delay-effect-in-audio-library/page5?highlight=APS6404L-3SQR + +*/ + +#ifndef effect_delay_ext8_h_ +#define effect_delay_ext8_h_ +#include "Arduino.h" +#include "AudioStream.h" +#include "spi_interrupt.h" + +class AudioEffectDelayExternal8 : public AudioStream +{ +public: + AudioEffectDelayExternal8() : AudioStream(1, inputQueueArray) { + initialize(4194304); + } + AudioEffectDelayExternal8(float milliseconds=1e6) + : AudioStream(1, inputQueueArray) { + uint32_t n = (milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f; + initialize(n); + } + + void delay(uint8_t channel, float milliseconds) { + if (channel >= 8) return; + if (milliseconds < 0.0f) milliseconds = 0.0f; + uint32_t n = (milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0f))+0.5f; + n += AUDIO_BLOCK_SAMPLES; + if (n > memory_length - AUDIO_BLOCK_SAMPLES) + n = memory_length - AUDIO_BLOCK_SAMPLES; + delay_length[channel] = n; + uint8_t mask = activemask; + if (activemask == 0) AudioStartUsingSPI(); + activemask = mask | (1<= 8) return; + uint8_t mask = activemask & ~(1<