├── ESP32_SD_Sampler ├── ESP32_SD_Sampler.ino ├── README.md ├── adsr.h ├── adsr.ino ├── buttons.ino ├── config.h ├── fx_reverb.h ├── i2s_audio.ino ├── midi_config.h ├── midi_handler.ino ├── misc.h ├── sampler.h ├── sampler.ino ├── sampler_ini.ino ├── sdmmc.h ├── sdmmc.ino ├── sdmmc_file.h ├── sdmmc_file.ino ├── sdmmc_types.h ├── src │ ├── midiusb │ │ └── src │ │ │ ├── MIDIUSB_Defs.h │ │ │ ├── MIDIUSB_ESP32.cpp │ │ │ └── MIDIUSB_ESP32.h │ └── usbmidi │ │ └── src │ │ ├── USB-MIDI.h │ │ ├── USB-MIDI_Namespace.h │ │ └── USB-MIDI_defs.h ├── voice.h └── voice.ino ├── LICENSE ├── README.md ├── media ├── 2024-05-05 12-37-57.JPG ├── LOLIN S3 PRO mod.JPG └── README.md └── sampler_ini_syntax.md /ESP32_SD_Sampler/ESP32_SD_Sampler.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * ESP32-S3 SD Sampler is a polyphonic music synthesizer, which can play PCM WAV samples directly from an SD (microSD) 3 | * card connected to an ESP32-S3. Simple: one directory = one sample set. Plain text "sampler.ini" manages how samples 4 | * to be spread over the keyboard. The main difference, comparing to the projects available on the net, is that this 5 | * sampler WON'T try to preload all the stuff into the RAM/PSRAM to play it on demand. So it's not limited in this way 6 | * by the size of the memory chip and can take really huge (per-note true sampled multi-velocity several gigabytes) 7 | * sample sets. It only requires that the card is freshly formatted FAT32 and has no or very few bad blocks (actually 8 | * it requires that the WAV files are written with little or no fragmentation at all). On start it analyzes existing 9 | * file allocation table (FAT) and forms it's own sample lookup table to be able to access data immediately, using 10 | * SDMMC with a 4-bit wide bus. 11 | * 12 | * 13 | * Libraries used: 14 | * Fixed string library https://github.com/fatlab101/FixedString 15 | * Arduino MIDI library https://github.com/FortySevenEffects/arduino_midi_library 16 | * If you want to use RGB LEDs, then also FastLED library is needed https://github.com/FastLED/FastLED 17 | * 18 | * (c) Copych 2024, License: MIT https://github.com/copych/ESP32_S3_Sampler?tab=MIT-1-ov-file#readme 19 | * 20 | * More info: 21 | * https://github.com/copych/ESP32_S3_Sampler 22 | */ 23 | #pragma GCC optimize ("Os") 24 | 25 | #include 26 | #include "config.h" 27 | #include "misc.h" 28 | #include "sdmmc.h" 29 | #include 30 | #include "sampler.h" 31 | #include "fx_reverb.h" 32 | #include 33 | 34 | 35 | #ifdef RGB_LED 36 | #include "FastLED.h" 37 | CRGB leds[1]; 38 | #endif 39 | 40 | //SET_LOOP_TASK_STACK_SIZE(READ_BUF_SECTORS * BYTES_PER_SECTOR + 20000); 41 | //SET_LOOP_TASK_STACK_SIZE(80000); 42 | 43 | // =============================================================== MIDI interfaces =============================================================== 44 | #ifdef MIDI_USB_DEVICE 45 | #include "src/usbmidi/src/USB-MIDI.h" 46 | USBMIDI_CREATE_INSTANCE(0, MIDI_usbDev); 47 | #endif 48 | 49 | #ifdef MIDI_VIA_SERIAL 50 | 51 | struct CustomBaudRateSettings : public MIDI_NAMESPACE::DefaultSettings { 52 | static const long BaudRate = 115200; 53 | static const bool Use1ByteParsing = false; 54 | }; 55 | 56 | MIDI_NAMESPACE::SerialMIDI serialMIDI(MIDI_PORT); 57 | MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); 58 | 59 | #endif 60 | 61 | #ifdef MIDI_VIA_SERIAL2 62 | // MIDI port on UART2, pins 16 (RX) and 17 (TX) prohibited on ESP32, as they are used for PSRAM 63 | struct Serial2MIDISettings : public midi::DefaultSettings { 64 | static const long BaudRate = 31250; 65 | static const int8_t RxPin = MIDIRX_PIN; 66 | static const int8_t TxPin = MIDITX_PIN; 67 | static const bool Use1ByteParsing = false; 68 | }; 69 | MIDI_NAMESPACE::SerialMIDI Serial2MIDI2(Serial2); 70 | MIDI_NAMESPACE::MidiInterface> MIDI2((MIDI_NAMESPACE::SerialMIDI&)Serial2MIDI2); 71 | #endif 72 | 73 | 74 | // =============================================================== MAIN oobjects =============================================================== 75 | SDMMC_FAT32 Card; 76 | SamplerEngine Sampler; 77 | FxReverb Reverb; 78 | 79 | // =============================================================== GLOBALS =============================================================== 80 | TaskHandle_t SynthTask; 81 | TaskHandle_t ControlTask; 82 | static volatile int DRAM_ATTR WORD_ALIGNED_ATTR out_buf_id = 0; 83 | static volatile int DRAM_ATTR WORD_ALIGNED_ATTR gen_buf_id = 1; 84 | static float DRAM_ATTR WORD_ALIGNED_ATTR sampler_l[2][DMA_BUF_LEN]; // sampler L buffer 85 | static float DRAM_ATTR WORD_ALIGNED_ATTR sampler_r[2][DMA_BUF_LEN]; // sampler R buffer 86 | static float DRAM_ATTR WORD_ALIGNED_ATTR mix_buf_l[2][DMA_BUF_LEN]; // mix L channel 87 | static float DRAM_ATTR WORD_ALIGNED_ATTR mix_buf_r[2][DMA_BUF_LEN]; // mix R channel 88 | static int16_t DRAM_ATTR WORD_ALIGNED_ATTR out_buf[2][DMA_BUF_LEN * 2]; // i2s L+R output buffer 89 | 90 | 91 | // =============================================================== forward declarations =============================================================== 92 | static void IRAM_ATTR mixer() ; 93 | static void IRAM_ATTR i2s_output(); 94 | static void IRAM_ATTR sampler_generate_buf(); 95 | 96 | // =============================================================== PER CORE TASKS =============================================================== 97 | static void IRAM_ATTR audio_task(void *userData) { // core 0 task 98 | DEBUG ("core 0 audio task run"); 99 | vTaskDelay(20); 100 | volatile uint32_t WORD_ALIGNED_ATTR t1,t2,t3,t4; 101 | out_buf_id = 0; 102 | gen_buf_id = 1; 103 | 104 | while (true) { 105 | #ifdef DEBUG_CORE_TIME 106 | t1=micros(); 107 | #endif 108 | 109 | sampler_generate_buf(); 110 | 111 | #ifdef DEBUG_CORE_TIME 112 | t2=micros(); 113 | #endif 114 | 115 | mixer(); 116 | 117 | #ifdef DEBUG_CORE_TIME 118 | t3=micros(); 119 | #endif 120 | 121 | i2s_output(); 122 | 123 | #ifdef DEBUG_CORE_TIME 124 | t4=micros(); 125 | DEBF("gen=%d, mix=%d, output=%d, total=%d\r\n", t2-t1, t3-t2, t4-t3, t4-t1); 126 | #endif 127 | 128 | out_buf_id = 1; 129 | gen_buf_id = 0; 130 | 131 | #ifdef DEBUG_CORE_TIME 132 | t1=micros(); 133 | #endif 134 | 135 | sampler_generate_buf(); 136 | 137 | #ifdef DEBUG_CORE_TIME 138 | t2=micros(); 139 | #endif 140 | 141 | mixer(); 142 | 143 | #ifdef DEBUG_CORE_TIME 144 | t3=micros(); 145 | #endif 146 | 147 | i2s_output(); 148 | 149 | #ifdef DEBUG_CORE_TIME 150 | t4=micros(); 151 | DEBF("gen=%d, mix=%d, output=%d, total=%d\r\n", t2-t1, t3-t2, t4-t3, t4-t1); 152 | #endif 153 | 154 | out_buf_id = 0; 155 | gen_buf_id = 1; 156 | } 157 | } 158 | 159 | static void IRAM_ATTR control_task(void *userData) { // core 1 task 160 | byte hue=0; 161 | DEBUG ("core 1 control task run"); 162 | vTaskDelay(20); 163 | uint32_t WORD_ALIGNED_ATTR passby = 0; 164 | 165 | while (true) { 166 | 167 | Sampler.freeSomeVoices(); 168 | 169 | Sampler.fillBuffer(); 170 | 171 | #ifdef MIDI_VIA_SERIAL 172 | MIDI.read(); 173 | #endif 174 | 175 | #ifdef MIDI_VIA_SERIAL2 176 | MIDI2.read(); 177 | #endif 178 | 179 | #ifdef MIDI_USB_DEVICE 180 | MIDI_usbDev.read(); 181 | #endif 182 | 183 | passby++; 184 | 185 | if (passby%256 == 0 ) { 186 | 187 | processButtons(); 188 | taskYIELD(); 189 | 190 | #ifdef RGB_LED 191 | hue++; 192 | leds[0].setHue(hue); 193 | FastLED.show(1); 194 | #endif 195 | 196 | // DEBF("Active voices %d of %d \r\n", Sampler.getActiveVoices(), MAX_POLYPHONY); 197 | // DEBF("ControlTask unused stack size = %d bytes\r\n", uxTaskGetStackHighWaterMark(ControlTask)); 198 | // DEBF("SynthTask unused stack size = %d bytes\r\n", uxTaskGetStackHighWaterMark(SynthTask)); 199 | } 200 | } 201 | } 202 | 203 | // =============================================================== SETUP() =============================================================== 204 | void setup() { 205 | 206 | #ifdef DEBUG_ON 207 | DEBUG_PORT.begin(115200); 208 | #endif 209 | 210 | #ifdef RGB_LED 211 | FastLED.addLeds(leds, 1); 212 | FastLED.setBrightness(1); 213 | #endif 214 | 215 | #ifdef RGB_LED 216 | leds[0].setHue(HUE_RED); //green 217 | FastLED.show(1); 218 | #endif 219 | 220 | delay(2000); 221 | 222 | #ifdef RGB_LED 223 | leds[0].setHue(HUE_PURPLE); //green 224 | FastLED.show(1); 225 | #endif 226 | 227 | DEBUG("I2S: INIT"); 228 | i2sInit(); 229 | 230 | DEBUG("MIDI: INIT"); 231 | MidiInit(); 232 | 233 | DEBUG("CARD: BEGIN"); 234 | Card.begin(); 235 | 236 | //delay(1000); 237 | // Card.testReadSpeed(READ_BUF_SECTORS,8); 238 | 239 | DEBUG("REVERB: INIT"); 240 | Reverb.Init(); 241 | 242 | 243 | DEBUG("SAMPLER: INIT"); 244 | Sampler.init(&Card); 245 | 246 | Sampler.setCurrentFolder(1); 247 | 248 | 249 | initButtons(); 250 | 251 | xTaskCreatePinnedToCore( audio_task, "SynthTask", 4000, NULL, 20, &SynthTask, 0 ); 252 | 253 | xTaskCreatePinnedToCore( control_task, "ControlTask", 9000, NULL, 3, &ControlTask, 1 ); 254 | 255 | Reverb.SetLevel(0.5f); 256 | Reverb.SetTime(0.7f); 257 | Sampler.setReverbSendLevel(0.5f); 258 | 259 | c_major(); 260 | 261 | DEBUG ("Setup() DONE"); 262 | // heap_caps_print_heap_info(MALLOC_CAP_8BIT); 263 | 264 | #ifdef RGB_LED 265 | leds[0].setHue(HUE_GREEN); //green 266 | FastLED.show(1); 267 | #endif 268 | } 269 | 270 | 271 | void loop() { 272 | 273 | 274 | vTaskDelete(NULL); 275 | } 276 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/README.md: -------------------------------------------------------------------------------- 1 | This is an Arduino project folder 2 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/adsr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** Distinct stages that the phase of the envelope can be located in. 4 | - IDLE = located at phase location 0, and not currently running 5 | - ATTACK = First segment of envelope where phase moves from 0 to 1 6 | - DECAY = Second segment of envelope where phase moves from 1 to SUSTAIN value 7 | - RELEASE = Fourth segment of envelop where phase moves from SUSTAIN to 0 8 | */ 9 | 10 | // note that ADSR_SEG_SUSTAIN value is not used in this implementation and the GetCurrentSegment() will return ADSR_SEG_DECAY eventhough ADSR_SEG_SUSTAIN would be correct 11 | // if you need it 12 | 13 | /** adsr envelope module 14 | Original author(s) : Paul Batchelor 15 | Ported from Soundpipe by Ben Sergentanis, May 2020 16 | Remake by Steffan DIedrichsen, May 2021 17 | Modified by Copych, Jan-Jun 2024 18 | Added fast and semi-fast releases 19 | */ 20 | 21 | 22 | class Adsr 23 | { 24 | public: 25 | enum eSegment_t { ADSR_SEG_IDLE, ADSR_SEG_ATTACK, ADSR_SEG_DECAY, ADSR_SEG_SUSTAIN, ADSR_SEG_RELEASE, ADSR_SEG_SEMI_FAST_RELEASE, ADSR_SEG_FAST_RELEASE }; 26 | enum eEnd_t { END_REGULAR, END_SEMI_FAST, END_FAST, END_NOW }; 27 | Adsr() {} 28 | ~Adsr() {} 29 | 30 | /** Initializes the Adsr module. 31 | \param sample_rate - The sample rate of the audio engine being run. 32 | */ 33 | void init(float sample_rate, int blockSize = 1); 34 | 35 | /** 36 | \function Retrigger forces the envelope back to attack phase 37 | \param hard resets the history to zero, results in a click. 38 | */ 39 | void retrigger(eEnd_t hardness); 40 | 41 | /** 42 | \function End forces the envelope to idle phase 43 | \param hard resets the history to zero, results in a click. 44 | */ 45 | void end(eEnd_t hardness); 46 | 47 | /** Processes one sample through the filter and returns one sample. 48 | */ 49 | float process(); 50 | 51 | 52 | /** Sets time 53 | Set time per segment in seconds 54 | */ 55 | void setTime(int seg, float time); 56 | void setAttackTime(float timeInS, float shape = 0.0f); 57 | void setDecayTime(float timeInS); 58 | void setReleaseTime(float timeInS); 59 | void setFastReleaseTime(float timeInS); 60 | void setSemiFastReleaseTime(float timeInS); 61 | 62 | private: 63 | void setTimeConstant(float timeInS, float& time, float& coeff); 64 | 65 | public: 66 | /** Sustain level 67 | \param sus_level - sets sustain level, 0...1.0 68 | */ 69 | inline void setSustainLevel(float sus_level) 70 | { 71 | sus_level = (sus_level <= 0.f) ? -0.001f // forces envelope into idle 72 | : (sus_level > 1.f) ? 1.f : sus_level; 73 | sus_level_ = sus_level; 74 | } 75 | /** get the current envelope segment 76 | \return the segment of the envelope that the phase is currently located in. 77 | */ 78 | inline eSegment_t getCurrentSegment() ; 79 | /** Tells whether envelope is active 80 | \return true if the envelope is currently in any stage apart from idle. 81 | */ 82 | inline bool isRunning() const { return mode_ != ADSR_SEG_IDLE; } 83 | /** Tells whether envelope is active 84 | \return true if the envelope is currently in any stage apart from idle. 85 | */ 86 | inline bool isIdle() const { return mode_ == ADSR_SEG_IDLE; } 87 | 88 | private: 89 | float sus_level_{0.f}; 90 | volatile float x_{0.f}; 91 | volatile float target_{0.f}; 92 | volatile float D0_{0.f}; 93 | float attackShape_{-1.f}; 94 | float attackTarget_{0.0f}; 95 | float attackTime_{-1.0f}; 96 | float decayTime_{-1.0f}; 97 | float releaseTime_{-1.0f}; 98 | float fastReleaseTime_{-1.0f}; 99 | float semiFastReleaseTime_{-1.0f}; 100 | float attackD0_{0.f}; 101 | float decayD0_{0.f}; 102 | float releaseD0_{0.f}; 103 | float fastReleaseD0_{0.f}; 104 | float semiFastReleaseD0_{0.f}; 105 | int sample_rate_; 106 | volatile eSegment_t mode_{ADSR_SEG_IDLE}; 107 | bool gate_{false}; 108 | }; 109 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/adsr.ino: -------------------------------------------------------------------------------- 1 | #include "adsr.h" 2 | #include 3 | 4 | void Adsr::init(float sample_rate, int blockSize) { 5 | sample_rate_ = sample_rate / blockSize; 6 | attackShape_ = -1.f; 7 | attackTarget_ = 0.0f; 8 | attackTime_ = -1.f; 9 | decayTime_ = -1.f; 10 | releaseTime_ = -1.f; 11 | fastReleaseTime_ = -1.f; 12 | semiFastReleaseTime_ = -1.f; 13 | sus_level_ = 1.0f; 14 | x_ = 0.0f; 15 | gate_ = false; 16 | mode_ = ADSR_SEG_IDLE; 17 | 18 | setTime(ADSR_SEG_ATTACK, 0.0f); 19 | setTime(ADSR_SEG_DECAY, 0.0f); 20 | setTime(ADSR_SEG_RELEASE, 0.05f); 21 | setTime(ADSR_SEG_FAST_RELEASE, 0.0002f); // a few samples fade, trying to avoid clicks on polyphony overrun 22 | setTime(ADSR_SEG_SEMI_FAST_RELEASE, 0.02f); // for exclusive note groups voice stealing 23 | } 24 | 25 | 26 | void Adsr::retrigger(eEnd_t hardness) { 27 | gate_ = true; 28 | mode_ = ADSR_SEG_ATTACK; 29 | switch (hardness) { 30 | case END_NOW: 31 | x_ = 0.0f; 32 | D0_ = attackD0_; 33 | break; 34 | case END_FAST: 35 | case END_SEMI_FAST: 36 | case END_REGULAR: 37 | default: 38 | D0_ = attackD0_; 39 | } 40 | } 41 | 42 | 43 | void Adsr::end(eEnd_t hardness) { 44 | gate_ = false; 45 | target_ = -0.1f; 46 | switch (hardness) { 47 | case END_NOW:{ 48 | mode_ = ADSR_SEG_IDLE; 49 | D0_ = attackD0_; 50 | x_ = 0.f; 51 | break; 52 | } 53 | case END_FAST:{ 54 | mode_ = ADSR_SEG_FAST_RELEASE; 55 | D0_ = fastReleaseD0_; 56 | break; 57 | } 58 | case END_SEMI_FAST:{ 59 | mode_ = ADSR_SEG_SEMI_FAST_RELEASE; 60 | D0_ = semiFastReleaseD0_; 61 | break; 62 | } 63 | case END_REGULAR: 64 | default:{ 65 | mode_ = ADSR_SEG_RELEASE; 66 | D0_ = releaseD0_; 67 | } 68 | } 69 | } 70 | 71 | 72 | inline Adsr::eSegment_t Adsr::getCurrentSegment() { 73 | Adsr::eSegment_t ret = mode_; 74 | if (gate_ && (x_ == sus_level_)) { 75 | ret = ADSR_SEG_SUSTAIN; 76 | } 77 | return ret; 78 | } 79 | 80 | 81 | void Adsr::setTime(int seg, float time) { 82 | switch (seg) { 83 | case ADSR_SEG_ATTACK: 84 | { 85 | setAttackTime(time, 0.0f); 86 | break; 87 | } 88 | case ADSR_SEG_DECAY: 89 | { 90 | setTimeConstant(time, decayTime_, decayD0_); 91 | } 92 | break; 93 | case ADSR_SEG_RELEASE: 94 | { 95 | setTimeConstant(time, releaseTime_, releaseD0_); 96 | } 97 | break; 98 | case ADSR_SEG_SEMI_FAST_RELEASE: 99 | { 100 | setTimeConstant(time, semiFastReleaseTime_, semiFastReleaseD0_); 101 | } 102 | break; 103 | case ADSR_SEG_FAST_RELEASE: 104 | { 105 | setTimeConstant(time, fastReleaseTime_, fastReleaseD0_); 106 | } 107 | break; 108 | default: return; 109 | } 110 | } 111 | 112 | 113 | void Adsr::setAttackTime(float timeInS, float shape) { 114 | if ((timeInS != attackTime_) || (shape != attackShape_)) { 115 | attackTime_ = timeInS; 116 | attackShape_ = shape; 117 | float x = shape; 118 | float target = 9.f * powf(x, 10.f) + 0.3f * x + 1.01f; 119 | attackTarget_ = target; 120 | float logTarget = logf(1.f - (1.f / target)); // -1 for decay 121 | if (timeInS > 0.f) { 122 | attackD0_ = 1.f - expf(logTarget / (timeInS * sample_rate_)); 123 | } else 124 | attackD0_ = 1.f; // instant change 125 | } 126 | } 127 | 128 | 129 | void Adsr::setDecayTime(float timeInS) { 130 | setTimeConstant(timeInS, decayTime_, decayD0_); 131 | } 132 | 133 | void Adsr::setReleaseTime(float timeInS) { 134 | setTimeConstant(timeInS, releaseTime_, releaseD0_); 135 | } 136 | 137 | void Adsr::setFastReleaseTime(float timeInS) { 138 | setTimeConstant(timeInS, fastReleaseTime_, fastReleaseD0_); 139 | } 140 | 141 | void Adsr::setSemiFastReleaseTime(float timeInS) { 142 | setTimeConstant(timeInS, semiFastReleaseTime_, semiFastReleaseD0_); 143 | } 144 | 145 | 146 | void Adsr::setTimeConstant(float timeInS, float& time, float& coeff) { 147 | if (timeInS != time) { 148 | time = timeInS; 149 | if (time > 0.f) { 150 | const float target = -0.4f; // ~log(1/e) 151 | coeff = 1.f - expf(target / (time * sample_rate_)); 152 | } else 153 | coeff = 1.f; // instant change 154 | } 155 | } 156 | 157 | 158 | float Adsr::process() { 159 | float out = 0.0f; 160 | switch (mode_) { 161 | case ADSR_SEG_IDLE: 162 | out = 0.0f; 163 | break; 164 | case ADSR_SEG_ATTACK: 165 | x_ += (float)D0_ * ((float)attackTarget_ - (float)x_); 166 | out = x_; 167 | if (out > 1.f) { 168 | mode_ = ADSR_SEG_DECAY; 169 | x_ = out = 1.f; 170 | target_ = sus_level_; 171 | D0_ = decayD0_; 172 | } 173 | break; 174 | case ADSR_SEG_DECAY: 175 | case ADSR_SEG_RELEASE: 176 | x_ += (float)D0_ * ((float)target_ - (float)x_); 177 | out = x_; 178 | if (out < 0.0f) { 179 | mode_ = ADSR_SEG_IDLE; 180 | x_ = out = 0.f; 181 | target_ = -0.1f; 182 | D0_ = attackD0_; 183 | } 184 | break; 185 | case ADSR_SEG_FAST_RELEASE: 186 | case ADSR_SEG_SEMI_FAST_RELEASE: 187 | x_ += (float)D0_ * ((float)target_ - (float)x_); 188 | out = x_; 189 | if (out < 0.0f) { 190 | mode_ = ADSR_SEG_IDLE; 191 | x_ = out = 0.f; 192 | target_ = -0.1f; 193 | D0_ = attackD0_; 194 | } 195 | break; 196 | default: 197 | break; 198 | } 199 | return out; 200 | } 201 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/buttons.ino: -------------------------------------------------------------------------------- 1 | 2 | #define MUXED_BUTTONS 0 3 | #define GPIO_BUTTONS 1 4 | #define TOTAL_BUTTONS 1 5 | #define LOGICAL_ON LOW 6 | const int buttonGPIOs[]{ 0 }; 7 | 8 | bool autoFireEnabled = false; // should the buttons generate continious clicks when pressed longer than a longPressThreshold 9 | bool lateClickEnabled = false; // enable registering click after a longPress call 10 | const unsigned long longPressThreshold = 800; // the threshold (in milliseconds) before a long press is detected 11 | const unsigned long autoFireDelay = 500; // the threshold (in milliseconds) between clicks if autofire is enabled 12 | const unsigned long riseThreshold = 20; // the threshold (in milliseconds) for a button press to be confirmed (i.e. debounce, not "noise") 13 | const unsigned long fallThreshold = 10; // debounce, not "noise", also this is the time, while new "touches" won't be registered 14 | 15 | 16 | void readButtonsState(uint32_t &activeFlags) { 17 | activeFlags=0; 18 | #if MUXED_BUTTONS > 0 19 | // assuming that first 16 buttons are connected via 16 ch multiplexer 20 | for (int i = 0; i < MUXED_BUTTONS; i++) { 21 | // if (readMuxed(i)) {bitSet(activeFlags, i);} else {bitClear(activeFlags, i);} 22 | bitWrite(activeFlags, i, readMuxed(i)); 23 | } 24 | #endif 25 | // all the rest are just GPIO buttons 26 | for (int i = 0; i < GPIO_BUTTONS; i++) { 27 | bitWrite(activeFlags, i + MUXED_BUTTONS, digitalRead(buttonGPIOs[i]) == LOGICAL_ON); 28 | } 29 | } 30 | 31 | void initButtons() { 32 | for (int i = 0; i < GPIO_BUTTONS; i++) { 33 | #if LOGICAL_ON == HIGH 34 | pinMode(buttonGPIOs[i], INPUT_PULLDOWN); 35 | #else 36 | pinMode(buttonGPIOs[i], INPUT_PULLUP); 37 | #endif 38 | } 39 | } 40 | 41 | #if MUXED_BUTTONS>0 42 | bool readMuxed(int channel) { 43 | // select the multiplexer channel 44 | digitalWrite(MUX_S0, bitRead(channel, 0)); 45 | digitalWrite(MUX_S1, bitRead(channel, 1)); 46 | digitalWrite(MUX_S2, bitRead(channel, 2)); 47 | digitalWrite(MUX_S3, bitRead(channel, 3)); 48 | delayMicroseconds(1); 49 | // read channel: LOGICAL_ON should be defined as HIGH or LOW depending on hardware settings (PullHigh or PullLow) 50 | return (digitalRead(MUX_Z) == LOGICAL_ON); 51 | } 52 | #endif 53 | 54 | #ifdef ECN_CLK 55 | void processEncoder() { // idea was taken from Alex Gyver's examples 56 | static int newState = 0; 57 | static int oldState = 0; 58 | // static const int8_t stepIncrement[16] = {0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0}; // Half-step encoder 59 | static const int stepIncrement[16] = {0, 0, 0, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, 0, 0, 0}; // Full-step encoder 60 | 61 | unsigned int clk = digitalRead(ENC_CLK)==LOGICAL_ON; 62 | unsigned int dt = digitalRead(ENC_DT)==LOGICAL_ON; 63 | DEB(clk); 64 | DEB("\t"); 65 | DEBUG(dt); 66 | newState = (clk | dt << 1); 67 | if (newState != oldState) { 68 | int stateMux = newState | (oldState << 2); 69 | int rotation = stepIncrement[stateMux]; 70 | oldState = newState; 71 | if (rotation != 0) { 72 | encoderMove(rotation); 73 | } 74 | } 75 | } 76 | #endif 77 | 78 | void processButtons() { 79 | static uint32_t buttonsState = 0; // current readings 80 | static uint32_t oldButtonsState = 0; // previous readings 81 | static uint32_t activeButtons = 0; // activity bitmask 82 | static unsigned long currentMillis; // not to call repeatedly within loop 83 | static unsigned long riseTimer[TOTAL_BUTTONS]; // stores the time that the button was pressed (relative to boot time) 84 | static unsigned long fallTimer[TOTAL_BUTTONS]; // stores the duration (in milliseconds) that the button was pressed/held down for 85 | static unsigned long longPressTimer[TOTAL_BUTTONS]; // stores the duration (in milliseconds) that the button was pressed/held down for 86 | static unsigned long autoFireTimer[TOTAL_BUTTONS]; // milliseconds before firing autoclick 87 | static bool buttonActive[TOTAL_BUTTONS]; // it's true since first touch till the click is confirmed 88 | static bool pressActive[TOTAL_BUTTONS]; // indicates if the button has been pressed and debounced 89 | static bool longPressActive[TOTAL_BUTTONS]; // indicates if the button has been long-pressed 90 | 91 | readButtonsState(buttonsState); // call a reading function 92 | 93 | for (int i = 0 ; i < TOTAL_BUTTONS; i++) { 94 | if ( bitRead(buttonsState, i) != bitRead(oldButtonsState, i) ) { 95 | // it changed 96 | if ( bitRead(buttonsState, i) == 1 ) { // rise (the beginning) 97 | // launch rise timer to debounce 98 | if (buttonActive[i]) { // it's active, but rises again 99 | // it might be noise, we check the state and timers 100 | if (pressActive[i]) { 101 | // it has passed the rise check, so it may be the ending phase 102 | // or it may be some noise during pressing 103 | } else { 104 | // we are still checking the rise for purity 105 | if (millis() - riseTimer[i] > riseThreshold) { 106 | // not a good place to confirm but we have to 107 | pressActive[i] = true; 108 | } 109 | } 110 | } else { 111 | // it wasn't active lately, now it's time 112 | buttonActive[i] = true; // fun begins for this button 113 | riseTimer[i] = millis(); 114 | longPressTimer[i] = millis(); // workaround for random longPresses 115 | autoFireTimer[i] = millis(); // workaround 116 | bitWrite(activeButtons, i, 1); 117 | onTouch(i, activeButtons); // I am the first 118 | } 119 | } else { // fall (is it a click or just an end?) 120 | // launch fall timer to debounce 121 | fallTimer[i] = millis(); 122 | // pendingClick[i] = true; 123 | } 124 | } else { // no change for this button 125 | if ( bitRead(buttonsState, i) == 1 ) { // the button reading is "active" 126 | // someone's pushing our button 127 | if (!pressActive[i] && (millis() - riseTimer[i] > riseThreshold)) { 128 | pressActive[i] = true; 129 | longPressTimer[i] = millis(); 130 | onPress(i, activeButtons); 131 | } 132 | if (pressActive[i] && !longPressActive[i] && (millis() - longPressTimer[i] > longPressThreshold)) { 133 | longPressActive[i] = true; 134 | onLongPress(i, activeButtons); 135 | } 136 | if (autoFireEnabled && longPressActive[i]) { 137 | if (millis() - autoFireDelay > autoFireTimer[i]) { 138 | autoFireTimer[i] = millis(); 139 | onAutoClick(i, activeButtons); // function to execute on autoClick event 140 | } 141 | } 142 | } else { // the button reading is "inactive" 143 | // ouch! a click? 144 | if (buttonActive[i] && (millis() - fallTimer[i] > fallThreshold)) { 145 | // yes, this is a click 146 | buttonActive[i] = false; // bye-bye 147 | pressActive[i] = false; 148 | if (!longPressActive[i] || lateClickEnabled) { 149 | onClick(i, activeButtons); 150 | } 151 | longPressActive[i] = false; 152 | onRelease(i, activeButtons); 153 | } 154 | } 155 | } 156 | bitWrite(activeButtons, i, buttonActive[i]); 157 | } 158 | oldButtonsState = buttonsState; 159 | } 160 | 161 | 162 | // buttonNumber - variable that indicates the button caused the event 163 | // activeButtonsBitmask flags all the active buttons 164 | // each binary digit contains a state: 0 = inactive, 1 = active 165 | void onTouch (uint8_t buttonNumber, uint32_t activeButtonsBitmask) { 166 | DEB("Flags: "); 167 | DEB(activeButtonsBitmask); 168 | DEB(" Touch: "); 169 | DEBUG(buttonNumber); 170 | } 171 | 172 | void onPress (uint8_t buttonNumber, uint32_t activeButtonsBitmask) { 173 | DEB("Flags: "); 174 | DEB(activeButtonsBitmask); 175 | DEB(" Press: "); 176 | DEBUG(buttonNumber); 177 | Sampler.setNextFolder(); 178 | 179 | } 180 | 181 | void onClick (uint8_t buttonNumber, uint32_t activeButtonsBitmask) { 182 | DEB("Flags: "); 183 | DEB(activeButtonsBitmask); 184 | DEB(" Click: "); 185 | DEBUG(buttonNumber); 186 | } 187 | 188 | void onLongPress (uint8_t buttonNumber, uint32_t activeButtonsBitmask) { 189 | DEB("Flags: "); 190 | DEB(activeButtonsBitmask); 191 | DEB(" Long Press: "); 192 | DEBUG(buttonNumber); 193 | } 194 | 195 | void onAutoClick (uint8_t buttonNumber, uint32_t activeButtonsBitmask) { 196 | DEB("Flags: "); 197 | DEB(activeButtonsBitmask); 198 | DEB(" AutoFire: "); 199 | DEBUG(buttonNumber); 200 | } 201 | 202 | void onRelease (uint8_t buttonNumber, uint32_t activeButtonsBitmask) { 203 | DEB("Flags: "); 204 | DEB(activeButtonsBitmask); 205 | DEB(" Release: "); 206 | DEBUG(buttonNumber); 207 | 208 | } 209 | #ifdef ENC_CLK 210 | void encoderMove (int8_t rotation) { 211 | DEB("Encoder: "); 212 | DEBUG(rotation); 213 | // Check Rotary-Encoder-Movements 214 | 215 | } 216 | #endif 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | //******************************************************* DEBUG ********************************************** 4 | // #define DEBUG_ON 5 | //#define DEBUG_CORE_TIME 6 | //#define C_MAJOR_ON_START // play C major chord on startup (testing) and on folder change 7 | 8 | //******************************************************* SYSTEM ********************************************** 9 | //#define BOARD_HAS_UART_CHIP 10 | #define SAMPLE_RATE 44100 // audio output sampling rate 11 | 12 | //#define MIDI_VIA_SERIAL // use this option to enable Hairless MIDI on Serial port @115200 baud (USB connector), THIS WILL BLOCK SERIAL DEBUGGING 13 | #define MIDI_VIA_SERIAL2 // use this option if you want to operate by standard MIDI @31250baud, UART2 (Serial2), 14 | // #define MIDI_USB_DEVICE // use this option if you want to operate via USB with the sampler seen as a MIDI device (-50 kBytes of available RAM) 15 | 16 | #define RECEIVE_MIDI_CHAN 1 17 | 18 | //******************************************************* FILESYSTEM ********************************************** 19 | #define INI_FILE "sampler.ini" 20 | #define ROOT_FOLDER "/" // only is supported yet 21 | #define READ_BUF_SECTORS 7 // that many sectors (assume 512 Bytes) per read operation, the more, the faster it reads 22 | 23 | 24 | //******************************************************* SAMPLER ********************************************** 25 | #define MAX_POLYPHONY 17 // empiric : MAX_POLYPHONY * READ_BUF_SECTORS <= 156 26 | #define SACRIFY_VOICES 1 // voices used for smooth transisions to avoid clicks 27 | #define MAX_SAME_NOTES 2 // number of voices allowed playing the same note 28 | #define MAX_VELOCITY_LAYERS 16 29 | #define MAX_NOTES_PER_GROUP 3 // exclusive groups: e.g. Closed hat, Pedal hat and Open hat -- only one of them can play at a time 30 | #define MAX_GROUPS_CROSSES 1 // max possible exclusive groups interleaving (common is 1, meaning no interleavings) 31 | #define MAX_DISTANCE_STRETCH 2 // max distance in semitones to search for an absent sample by changing speed of neighbour files 32 | //#define ADSR_LIVE_UPDATE // if you set this param, the notes being played will get the updates along with CC changes (may produce some hisses) 33 | 34 | //******************************************************* PINS ********************************************** 35 | #if defined(CONFIG_IDF_TARGET_ESP32S3) 36 | // ESP32 S3 37 | // #define RGB_LED 38 // RGB LED as a vital sign 38 | #define MIDIRX_PIN 4 // this pin is used for input when MIDI_VIA_SERIAL2 defined (note that default pin 17 won't work with PSRAM) 39 | #define MIDITX_PIN 9 // this pin will be used for output (not implemented yet) when MIDI_VIA_SERIAL2 defined 40 | #define I2S_BCLK_PIN 5 // I2S BIT CLOCK pin (BCL BCK CLK) 41 | #define I2S_DOUT_PIN 6 // to I2S DATA IN pin (DIN D DAT) 42 | #define I2S_WCLK_PIN 7 // I2S WORD CLOCK pin (WCK WCL LCK) 43 | 44 | // ESP32-S3 allows using SD_MMC.setPins(SDMMC_CLK, SDMMC_CMD, SDMMC_D0, SDMMC_D1, SDMMC_D2, SDMMC_D3) 45 | /* 46 | // default SDMMC GPIOs for S3: 47 | #define SDMMC_CMD 35 48 | #define SDMMC_CLK 36 49 | #define SDMMC_D0 37 50 | #define SDMMC_D1 38 51 | #define SDMMC_D2 33 52 | #define SDMMC_D3 34 53 | */ 54 | 55 | // TYPICAL dev board with two usb type-c connectors doesn't have GPIO 33 and 34 56 | /* 57 | #define SDMMC_CMD 3 58 | #define SDMMC_CLK 46 59 | #define SDMMC_D0 9 60 | #define SDMMC_D1 10 61 | #define SDMMC_D2 11 62 | #define SDMMC_D3 12 63 | */ 64 | 65 | // LOLIN S3 PRO for example has a microSD socket, but D1 and D2 are not connected, 66 | // so if you dare you may solder these ones to GPIOs of your choice (please, refer 67 | // to the docs choosing GPIOs, as they may have been used by some device already) 68 | // https://www.wemos.cc/en/latest/_static/files/sch_s3_pro_v1.0.0.pdf 69 | // LOLIN S3 PRO version (S3 allows configuring these ones): 70 | // 71 | // DON'T YOU set LOLIN S3 PRO as a target board in Arduino IDE, or you may have problems with MIDI. 72 | // SET generic ESP32S3 Dev Module as your target 73 | 74 | #define SDMMC_CMD 11 // LOLIN PCB hardlink 75 | #define SDMMC_CLK 12 // PCB hardlink 76 | #define SDMMC_D0 13 // PCB hardlink 77 | #define SDMMC_D1 18 // my choice // was 8 before 78 | #define SDMMC_D2 10 // my choice 79 | #define SDMMC_D3 46 // PCB hardlink 80 | 81 | #elif defined(CONFIG_IDF_TARGET_ESP32) 82 | // ESP32 83 | #define MIDIRX_PIN 22 // this pin is used for input when MIDI_VIA_SERIAL2 defined (note that default pin 17 won't work with PSRAM) 84 | #define MIDITX_PIN 23 // this pin will be used for output (not implemented yet) when MIDI_VIA_SERIAL2 defined 85 | #define I2S_BCLK_PIN 5 // I2S BIT CLOCK pin (BCL BCK CLK) 86 | #define I2S_DOUT_PIN 18 // to I2S DATA IN pin (DIN D DAT) 87 | #define I2S_WCLK_PIN 19 // I2S WORD CLOCK pin (WCK WCL LCK) 88 | 89 | // these pins require 10K pull-up, but if GPIO12 is pulled up on boot, we get a bootloop. 90 | // try using gpio_pullup_en(GPIO_NUM_12) or leave D2 as is 91 | // GPIO2 pulled up probably won't let you switch to download mode, so disconnect it 92 | // while uploading your sketch 93 | 94 | // General ESP32 (changing these pins is NOT possible) 95 | #define SDMMC_D0 2 96 | #define SDMMC_D1 4 97 | #define SDMMC_D2 12 98 | #define SDMMC_D3 13 99 | #define SDMMC_CLK 14 100 | #define SDMMC_CMD 15 101 | #endif 102 | 103 | 104 | #if ESP_ARDUINO_VERSION_MAJOR < 3 105 | #define BYTE_ALIGN 106 | #else 107 | #define BYTE_ALIGN 4, 108 | #endif 109 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/fx_reverb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * reverb stuff 3 | * 4 | * This is a port of the public code from YetAnotherElectronicsChannel 5 | * Based on main.c I've created this module 6 | * 7 | * src: https://github.com/YetAnotherElectronicsChannel/STM32_DSP_Reverb/blob/master/code/Src/main.c 8 | * 9 | * The explanation of the original module can be found here: https://youtu.be/nRLXNmLmHqM 10 | * 11 | * Changes: 12 | * - optimized for buffer processing 13 | * - added interface to set the level 14 | * 15 | */ 16 | 17 | #ifdef BOARD_HAS_PSRAM 18 | #define REV_MULTIPLIER 1.8f 19 | #define MALLOC_CAP MALLOC_CAP_SPIRAM 20 | #else 21 | #define REV_MULTIPLIER 0.35f 22 | #define MALLOC_CAP MALLOC_CAP_INTERNAL 23 | #endif 24 | 25 | #define COMB_BUF_LEN_0 (int)( 3604.0 * REV_MULTIPLIER) 26 | #define COMB_BUF_LEN_1 (int)( 3112.0 * REV_MULTIPLIER) 27 | #define COMB_BUF_LEN_2 (int)( 4044.0 * REV_MULTIPLIER) 28 | #define COMB_BUF_LEN_3 (int)( 4492.0 * REV_MULTIPLIER) 29 | #define ALLPASS_BUF_LEN_0 (int)( 500.0 * REV_MULTIPLIER) 30 | #define ALLPASS_BUF_LEN_1 (int)( 168.0 * REV_MULTIPLIER) 31 | #define ALLPASS_BUF_LEN_2 (int)( 48.0 * REV_MULTIPLIER) 32 | 33 | //rev_time 0.0 <-> 1.0 34 | //rev_delay 0.0 <-> 1.0 35 | 36 | class FxReverb { 37 | public: 38 | FxReverb() { 39 | 40 | } 41 | 42 | inline void Process( float *signal_l, float *signal_r ){ 43 | 44 | float inSample; 45 | 46 | // create mono sample 47 | inSample = *signal_l + *signal_r; // it may cause unwanted audible effects 48 | inSample *= 0.5f; 49 | 50 | // float newsample = (Do_Comb0(inSample) + Do_Comb1(inSample) + Do_Comb2(inSample) + Do_Comb3(inSample)) / 4.0f; 51 | float newsample = (Do_Comb0(inSample) + Do_Comb1(inSample) + Do_Comb2(inSample) + Do_Comb3(inSample)) * 0.25f; 52 | newsample = Do_Allpass0(newsample); 53 | newsample = Do_Allpass1(newsample); 54 | newsample = Do_Allpass2(newsample); 55 | 56 | // apply reverb level 57 | newsample *= rev_level; 58 | 59 | *signal_l += newsample; 60 | *signal_r += newsample; 61 | 62 | } 63 | 64 | inline void Init() { 65 | 66 | combBuf0 = (float*)heap_caps_malloc( sizeof(float) * COMB_BUF_LEN_0 , MALLOC_CAP ); 67 | if( combBuf0 == NULL){ 68 | DEBUG("No more RAM for reverb combBuf0!"); 69 | } else { 70 | DEB("REVERB: combBuf0 : "); 71 | DEBF("%d Bytes RAM allocated for reverb buffer, &=%#010x\r\n", sizeof(float) * COMB_BUF_LEN_0 , combBuf0); 72 | memset(combBuf0, 0, sizeof(float) * COMB_BUF_LEN_0); 73 | } 74 | combBuf1 = (float*)heap_caps_malloc( sizeof(float) * COMB_BUF_LEN_1 , MALLOC_CAP ); 75 | if( combBuf1 == NULL){ 76 | DEBUG("No more RAM for reverb combBuf1!"); 77 | } else { 78 | DEB("REVERB: combBuf1 : "); 79 | DEBF("%d Bytes RAM allocated for reverb buffer, &=%#010x\r\n", sizeof(float) * COMB_BUF_LEN_1 , combBuf1); 80 | memset(combBuf1, 0, sizeof(float) * COMB_BUF_LEN_1); 81 | } 82 | combBuf2 = (float*)heap_caps_malloc( sizeof(float) * COMB_BUF_LEN_2 , MALLOC_CAP ); 83 | if( combBuf2 == NULL){ 84 | DEBUG("No more RAM for reverb combBuf2!"); 85 | } else { 86 | DEB("REVERB: combBuf2 : "); 87 | DEBF("%d Bytes RAM allocated for reverb buffer, &=%#010x\r\n", sizeof(float) * COMB_BUF_LEN_2 , combBuf2); 88 | memset(combBuf2, 0, sizeof(float) * COMB_BUF_LEN_2); 89 | } 90 | combBuf3 = (float*)heap_caps_malloc( sizeof(float) * COMB_BUF_LEN_3 , MALLOC_CAP ); 91 | if( combBuf3 == NULL){ 92 | DEBUG("No more RAM for reverb combBuf2!"); 93 | } else { 94 | DEB("REVERB: combBuf3 : "); 95 | DEBF("%d Bytes RAM allocated for reverb buffer, &=%#010x\r\n", sizeof(float) * COMB_BUF_LEN_3 , combBuf3); 96 | memset(combBuf3, 0, sizeof(float) * COMB_BUF_LEN_3); 97 | } 98 | allPassBuf0 = (float*)heap_caps_malloc( sizeof(float) * ALLPASS_BUF_LEN_0 , MALLOC_CAP ); 99 | if( allPassBuf0 == NULL){ 100 | DEBUG("No more RAM for reverb allPassBuf0!"); 101 | } else { 102 | DEB("REVERB: allPassBuf0 : "); 103 | DEBF("%d Bytes RAM allocated for reverb buffer, &=%#010x\r\n", sizeof(float) * ALLPASS_BUF_LEN_0 , allPassBuf0); 104 | memset(allPassBuf0, 0, sizeof(float) * ALLPASS_BUF_LEN_0); 105 | } 106 | allPassBuf1 = (float*)heap_caps_malloc( sizeof(float) * ALLPASS_BUF_LEN_1 , MALLOC_CAP ); 107 | if( allPassBuf1 == NULL){ 108 | DEBUG("No more RAM for reverb allPassBuf1!"); 109 | } else { 110 | DEB("REVERB: allPassBuf1 : "); 111 | DEBF("%d Bytes RAM allocated for reverb buffer, &=%#010x\r\n", sizeof(float) * ALLPASS_BUF_LEN_1, allPassBuf1); 112 | memset(allPassBuf1, 0, sizeof(float) * ALLPASS_BUF_LEN_1); 113 | } 114 | allPassBuf2 = (float*)heap_caps_malloc( sizeof(float) * ALLPASS_BUF_LEN_2 , MALLOC_CAP ); 115 | if( allPassBuf2 == NULL){ 116 | DEBUG("No more RAM for reverb allPassBuf2!"); 117 | } else { 118 | DEB("REVERB: allPassBuf2 : "); 119 | DEBF("%d Bytes RAM allocated for reverb buffer, &=%#010x\r\n", sizeof(float) * ALLPASS_BUF_LEN_2, allPassBuf2); 120 | memset(allPassBuf2, 0, sizeof(float) * ALLPASS_BUF_LEN_2); 121 | } 122 | 123 | SetLevel( 1.0f ); 124 | SetTime( 0.5f ); 125 | } 126 | 127 | inline void SetTime( float value ){ 128 | rev_time = 0.92f * value + 0.02f ; 129 | cf0_lim = (int)(rev_time * (float)(COMB_BUF_LEN_0)); 130 | cf1_lim = (int)(rev_time * (float)(COMB_BUF_LEN_1)); 131 | cf2_lim = (int)(rev_time * (float)(COMB_BUF_LEN_2)); 132 | cf3_lim = (int)(rev_time * (float)(COMB_BUF_LEN_3)); 133 | ap0_lim = (int)(rev_time * (float)(ALLPASS_BUF_LEN_0)); 134 | ap1_lim = (int)(rev_time * (float)(ALLPASS_BUF_LEN_1)); 135 | ap2_lim = (int)(rev_time * (float)(ALLPASS_BUF_LEN_2)); 136 | #ifdef DEBUG_FX 137 | DEBF("reverb time: %0.3f\n", value); 138 | #endif 139 | } 140 | 141 | inline void SetLevel( float value ){ 142 | rev_level = value; 143 | #ifdef DEBUG_FX 144 | DEBF("reverb level: %0.3f\n", value); 145 | #endif 146 | } 147 | 148 | private: 149 | float rev_time = 0.5f; 150 | float rev_level = 0.5f; 151 | 152 | float* combBuf0 = nullptr; 153 | float* combBuf1 = nullptr; 154 | float* combBuf2 = nullptr; 155 | float* combBuf3 = nullptr; 156 | float* allPassBuf0 = nullptr; 157 | float* allPassBuf1 = nullptr; 158 | float* allPassBuf2 = nullptr; 159 | 160 | //define pointer limits = delay time 161 | int cf0_lim, cf1_lim, cf2_lim, cf3_lim, ap0_lim, ap1_lim, ap2_lim; 162 | 163 | 164 | inline float Do_Comb0( float inSample ){ 165 | static int cf0_p = 0; 166 | static float cf0_g = 0.805f; 167 | 168 | float readback = combBuf0[cf0_p]; 169 | float newV = readback * cf0_g + inSample; 170 | combBuf0[cf0_p] = newV; 171 | cf0_p++; 172 | if( cf0_p >= cf0_lim ){ 173 | cf0_p = 0; 174 | } 175 | return readback; 176 | } 177 | 178 | inline float Do_Comb1( float inSample ){ 179 | 180 | static int cf1_p = 0; 181 | static float cf1_g = 0.827f; 182 | 183 | float readback = combBuf1[cf1_p]; 184 | float newV = readback * cf1_g + inSample; 185 | combBuf1[cf1_p] = newV; 186 | cf1_p++; 187 | if( cf1_p >= cf1_lim ){ 188 | cf1_p = 0; 189 | } 190 | return readback; 191 | } 192 | 193 | inline float Do_Comb2( float inSample ){ 194 | static int cf2_p = 0; 195 | static float cf2_g = 0.783f; 196 | 197 | float readback = combBuf2[cf2_p]; 198 | float newV = readback * cf2_g + inSample; 199 | combBuf2[cf2_p] = newV; 200 | cf2_p++; 201 | if( cf2_p >= cf2_lim ){ 202 | cf2_p = 0; 203 | } 204 | return readback; 205 | } 206 | 207 | inline float Do_Comb3( float inSample ){ 208 | static int cf3_p = 0; 209 | static float cf3_g = 0.764f; 210 | 211 | float readback = combBuf3[cf3_p]; 212 | float newV = readback * cf3_g + inSample; 213 | combBuf3[cf3_p] = newV; 214 | cf3_p++; 215 | if( cf3_p >= cf3_lim ){ 216 | cf3_p = 0; 217 | } 218 | return readback; 219 | } 220 | 221 | 222 | inline float Do_Allpass0( float inSample ){ 223 | static int ap0_p = 0; 224 | static float ap0_g = 0.7f; 225 | 226 | float readback = allPassBuf0[ap0_p]; 227 | readback += (-ap0_g) * inSample; 228 | float newV = readback * ap0_g + inSample; 229 | allPassBuf0[ap0_p] = newV; 230 | ap0_p++; 231 | if( ap0_p >= ap0_lim ){ 232 | ap0_p = 0; 233 | } 234 | return readback; 235 | } 236 | 237 | inline float Do_Allpass1( float inSample ){ 238 | static int ap1_p = 0; 239 | static float ap1_g = 0.7f; 240 | 241 | float readback = allPassBuf1[ap1_p]; 242 | readback += (-ap1_g) * inSample; 243 | float newV = readback * ap1_g + inSample; 244 | allPassBuf1[ap1_p] = newV; 245 | ap1_p++; 246 | if( ap1_p >= ap1_lim ){ 247 | ap1_p = 0; 248 | } 249 | return readback; 250 | } 251 | 252 | inline float Do_Allpass2( float inSample ){ 253 | static int ap2_p = 0; 254 | static float ap2_g = 0.7f; 255 | 256 | float readback = allPassBuf2[ap2_p]; 257 | readback += (-ap2_g) * inSample; 258 | float newV = readback * ap2_g + inSample; 259 | allPassBuf2[ap2_p] = newV; 260 | ap2_p++; 261 | if( ap2_p >= ap2_lim ){ 262 | ap2_p = 0; 263 | } 264 | return readback; 265 | } 266 | 267 | 268 | }; 269 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/i2s_audio.ino: -------------------------------------------------------------------------------- 1 | 2 | 3 | //#define DEBUG_MASTER_OUT 4 | 5 | #if ESP_ARDUINO_VERSION_MAJOR < 3 6 | 7 | #include "driver/i2s.h" 8 | // Arduino cores prior to 3.0.0 9 | const i2s_port_t i2s_num = I2S_NUM_0; // i2s port number 10 | 11 | void i2sInit() { 12 | pinMode(I2S_BCLK_PIN, OUTPUT); 13 | pinMode(I2S_DOUT_PIN, OUTPUT); 14 | pinMode(I2S_WCLK_PIN, OUTPUT); 15 | i2s_config_t i2s_config = { 16 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX ), 17 | .sample_rate = SAMPLE_RATE, 18 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 19 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 20 | .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S ), 21 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, 22 | .dma_buf_count = DMA_NUM_BUF, 23 | .dma_buf_len = DMA_BUF_LEN, 24 | .use_apll = true, 25 | }; 26 | 27 | i2s_pin_config_t i2s_pin_config = { 28 | .bck_io_num = I2S_BCLK_PIN, 29 | .ws_io_num = I2S_WCLK_PIN, 30 | .data_out_num = I2S_DOUT_PIN 31 | }; 32 | 33 | i2s_driver_install(i2s_num, &i2s_config, 0, NULL); 34 | 35 | i2s_set_pin(i2s_num, &i2s_pin_config); 36 | i2s_zero_dma_buffer(i2s_num); 37 | 38 | DEBF("I2S is started: BCK %d, WCK %d, DAT %d\r\n", I2S_BCLK_PIN, I2S_WCLK_PIN, I2S_DOUT_PIN); 39 | } 40 | 41 | 42 | void i2sDeinit() { 43 | i2s_zero_dma_buffer(i2s_num); 44 | i2s_driver_uninstall(i2s_num); 45 | } 46 | 47 | 48 | 49 | static void i2s_output () { 50 | // now out_buf is ready, output 51 | size_t bytes_written; 52 | 53 | 54 | for (int i=0; i < DMA_BUF_LEN; i++) { 55 | out_buf[out_buf_id][i*2] = (float)0x7fff * mix_buf_l[out_buf_id][i]; 56 | out_buf[out_buf_id][i*2+1] = (float)0x7fff * mix_buf_r[out_buf_id][i]; 57 | // if (i%4==0) DEBUG(out_buf[out_buf_id][i*2]); 58 | // if (out_buf[out_buf_id][i*2]) DEBF(" %d\r\n ", out_buf[out_buf_id][i*2]); 59 | } 60 | i2s_write(i2s_num, out_buf[out_buf_id], sizeof(out_buf[out_buf_id]), &bytes_written, portMAX_DELAY); 61 | 62 | } 63 | 64 | #else 65 | // Arduino core 3.0.0 and up 66 | #include 67 | const i2s_port_t i2s_num = I2S_NUM_0; // i2s port number 68 | 69 | I2SClass I2S; 70 | 71 | void i2sInit() { 72 | pinMode(I2S_BCLK_PIN, OUTPUT); 73 | pinMode(I2S_DOUT_PIN, OUTPUT); 74 | pinMode(I2S_WCLK_PIN, OUTPUT); 75 | I2S.setPins(I2S_BCLK_PIN, I2S_WCLK_PIN, I2S_DOUT_PIN); //SCK, WS, SDOUT, SDIN, MCLK 76 | I2S.begin(I2S_MODE_STD, SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO); 77 | 78 | DEBF("I2S is started: BCK %d, WCK %d, DAT %d\r\n", I2S_BCLK_PIN, I2S_WCLK_PIN, I2S_DOUT_PIN); 79 | } 80 | 81 | void i2sDeinit() { 82 | I2S.end(); 83 | } 84 | 85 | static void i2s_output () { 86 | // now out_buf is ready, output 87 | for (int i=0; i < DMA_BUF_LEN; i++) { 88 | out_buf[out_buf_id][i*2] = (float)0x7fff * mix_buf_l[out_buf_id][i]; 89 | out_buf[out_buf_id][i*2+1] = (float)0x7fff * mix_buf_r[out_buf_id][i]; 90 | // if (i%4==0) DEBUG(out_buf[out_buf_id][i*2]); 91 | 92 | //if (out_buf[out_buf_id][i*2]) DEBF(" %d\r\n ", out_buf[out_buf_id][i*2]); 93 | } 94 | I2S.write((uint8_t*)out_buf[out_buf_id], sizeof(out_buf[out_buf_id])); 95 | } 96 | 97 | #endif 98 | 99 | 100 | static void mixer() { // sum buffers 101 | #ifdef DEBUG_MASTER_OUT 102 | float meter = 0.0f; 103 | #endif 104 | const float attenuator = 0.5f; 105 | float sampler_out_l, sampler_out_r; 106 | float mono_mix; 107 | float dly_l, dly_r; 108 | float rvb_l, rvb_r; 109 | 110 | for (int i=0; i < DMA_BUF_LEN; i++) { 111 | 112 | sampler_out_l = sampler_l[out_buf_id][i] * attenuator; 113 | sampler_out_r = sampler_r[out_buf_id][i] * attenuator; 114 | 115 | // DJFilter.Process(&sampler_out_l, &sampler_out_r); 116 | 117 | // Drive.Process(&sampler_out_l, &sampler_out_r); // overdrive // make it stereo firstly 118 | // Distortion.Process(&sampler_out_l, &sampler_out_r); // distortion // make it stereo firstly 119 | /* 120 | dly_l = sampler_out_l * Sampler.getDelaySendLevel(); // delay bus 121 | dly_r = sampler_out_r * Sampler.getDelaySendLevel(); 122 | Delay.Process( &dly_l, &dly_r ); 123 | 124 | sampler_out_l += dly_l; 125 | sampler_out_r += dly_r; 126 | */ 127 | 128 | 129 | rvb_l = sampler_out_l * Sampler.getReverbSendLevel(); // reverb bus 130 | rvb_r = sampler_out_r * Sampler.getReverbSendLevel(); 131 | Reverb.Process( &rvb_l, &rvb_r ); 132 | 133 | sampler_out_l += rvb_l; 134 | sampler_out_r += rvb_r; 135 | 136 | 137 | mono_mix = 0.5f * (sampler_out_l + sampler_out_r); 138 | 139 | // Comp.Process( mono_mix * 0.25f); // calc compressor gain, may be side-chain driven 140 | 141 | // mix_buf_l[out_buf_id][i] = Comp.Apply(sampler_out_l); 142 | // mix_buf_r[out_buf_id][i] = Comp.Apply(sampler_out_r); 143 | mix_buf_l[out_buf_id][i] = 0.6f* (sampler_out_l); 144 | mix_buf_r[out_buf_id][i] = 0.6f* (sampler_out_r); 145 | 146 | #ifdef DEBUG_MASTER_OUT 147 | if ( i % 16 == 0) meter = meter * 0.95f + fabs( mono_mix); 148 | #endif 149 | 150 | // if none of the following limitters is engaged, digital clipping can occur 151 | 152 | mix_buf_l[out_buf_id][i] = fclamp(mix_buf_l[out_buf_id][i] , -1.0f, 1.0f); // clipper 153 | mix_buf_r[out_buf_id][i] = fclamp(mix_buf_r[out_buf_id][i] , -1.0f, 1.0f); 154 | 155 | // mix_buf_l[out_buf_id][i] = fast_shape( mix_buf_l[out_buf_id][i]); // soft limitter/saturator 156 | // mix_buf_r[out_buf_id][i] = fast_shape( mix_buf_r[out_buf_id][i]); 157 | } 158 | 159 | #ifdef DEBUG_MASTER_OUT 160 | meter *= 0.95f; 161 | meter += fabs(mono_mix); 162 | DEBF("out= %0.5f\r\n", meter); 163 | #endif 164 | } 165 | 166 | static void sampler_generate_buf() { 167 | for (uint32_t i=0; i < DMA_BUF_LEN; i++){ 168 | Sampler.getSample(sampler_l[gen_buf_id][i], sampler_r[gen_buf_id][i]) ; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/midi_config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Global 4 | 5 | 6 | /* reminder 7 | RPNs 8 | 0x0000 – Pitch bend range 9 | 0x0001 – Fine tuning 10 | 0x0002 – Coarse tuning 11 | 0x0003 – Tuning program change 12 | 0x0004 – Tuning bank select 13 | 0x0005 – Modulation depth range 14 | 15 | undefined MIDI CCs: 16 | CC 3, 9, 14, 15 17 | CC 20 - 31 18 | CC 85 - 87 19 | CC 89, 90 20 | CC 102 - 119 21 | 22 | */ 23 | 24 | #define CC_PORTATIME 5 25 | #define CC_VOLUME 7 26 | #define CC_PAN 10 27 | #define CC_SUSTAIN 64 // + 28 | #define CC_PORTAMENTO 65 29 | 30 | #define CC_WAVEFORM 70 31 | #define CC_RESO 71 32 | #define CC_CUTOFF 74 33 | 34 | #define CC_ENV_ATTACK 73 // + 35 | #define CC_ENV_RELEASE 72 // + 36 | #define CC_ENV_DECAY 75 // + 37 | #define CC_ENV_SUSTAIN 76 // + 38 | 39 | #define CC_DELAY_TIME 84 40 | #define CC_DELAY_FB 85 41 | #define CC_DELAY_LVL 86 42 | #define CC_REVERB_TIME 87 // + 43 | #define CC_REVERB_LVL 88 // + 44 | 45 | #define CC_REVERB_SEND 91 // + 46 | #define CC_DELAY_SEND 92 47 | #define CC_OVERDRIVE 93 48 | #define CC_DISTORTION 94 49 | #define CC_COMPRESSOR 95 50 | 51 | #define CC_RESET_CCS 121 52 | #define CC_NOTES_OFF 123 53 | #define CC_SOUND_OFF 120 54 | 55 | #define NRPN_LSB_NUM 98 56 | #define NRPN_MSB_NUM 99 57 | #define RPN_LSB_NUM 100 58 | #define RPN_MSB_NUB 101 59 | #define DATA_ENTRY_MSB 6 60 | #define DATA_ENTRY_LSB 38 61 | 62 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/midi_handler.ino: -------------------------------------------------------------------------------- 1 | 2 | #include "midi_config.h" 3 | 4 | inline void MidiInit() { 5 | 6 | #ifdef MIDI_VIA_SERIAL 7 | #ifdef BOARD_HAS_UART_CHIP 8 | MIDI_PORT.begin( 115200, SERIAL_8N1 ); // midi port 9 | #endif 10 | MIDI.setHandleNoteOn(handleNoteOn); 11 | MIDI.setHandleNoteOff(handleNoteOff); 12 | MIDI.setHandleControlChange(handleCC); 13 | MIDI.setHandlePitchBend(handlePitchBend); 14 | MIDI.setHandleProgramChange(handleProgramChange); 15 | MIDI.begin(RECEIVE_MIDI_CHAN); 16 | #endif 17 | 18 | #ifdef MIDI_VIA_SERIAL2 19 | pinMode( MIDIRX_PIN , INPUT_PULLDOWN); 20 | pinMode( MIDITX_PIN , OUTPUT); 21 | Serial2.begin( 31250, SERIAL_8N1, MIDIRX_PIN, MIDITX_PIN ); // midi port 22 | MIDI2.setHandleNoteOn(handleNoteOn); 23 | MIDI2.setHandleNoteOff(handleNoteOff); 24 | MIDI2.setHandleControlChange(handleCC); 25 | MIDI2.setHandlePitchBend(handlePitchBend); 26 | MIDI2.setHandleProgramChange(handleProgramChange); 27 | MIDI2.begin(RECEIVE_MIDI_CHAN); 28 | #endif 29 | 30 | #ifdef MIDI_USB_DEVICE 31 | // /* Change USB Device Descriptor Parameter 32 | USB.VID(0x1209); 33 | USB.PID(0x1304); 34 | USB.productName("ESP32S3 SD sampler"); 35 | USB.manufacturerName("copych"); 36 | //USB.serialNumber("0000"); 37 | //USB.firmwareVersion(0x0000); 38 | USB.usbVersion(0x0200); 39 | USB.usbClass(TUSB_CLASS_AUDIO); 40 | USB.usbSubClass(0x00); 41 | USB.usbProtocol(0x00); 42 | USB.usbAttributes(0x80); 43 | // */ 44 | 45 | MIDI_usbDev.setHandleNoteOn(handleNoteOn); 46 | MIDI_usbDev.setHandleNoteOff(handleNoteOff); 47 | MIDI_usbDev.setHandleControlChange(handleCC); 48 | MIDI_usbDev.setHandlePitchBend(handlePitchBend); 49 | MIDI_usbDev.setHandleProgramChange(handleProgramChange); 50 | MIDI_usbDev.begin(RECEIVE_MIDI_CHAN); 51 | DEBUG("USB device started"); 52 | #endif 53 | } 54 | 55 | inline float midiToExpTime(uint8_t midiCC) { // converts 0-127 range to 0.0 - 1e30 exponential range with slow start and ~infinite growth at 127 (1=0.0002s, 64=2s, 92=10s, 126=80s) 56 | if (midiCC==0) {return 0.0;} 57 | if (midiCC==127) {return 1e30;} 58 | return (float)(0.059970546f * exp(0.058580705f*(float)midiCC - 0.184582266f) - 0.052670842f); 59 | } 60 | 61 | inline void handleNoteOn(uint8_t inChannel, uint8_t inNote, uint8_t inVelocity) { 62 | /* 63 | #ifdef RGB_LED 64 | leds[0].setHue(random(255)); 65 | FastLED.setBrightness(10); 66 | FastLED.show(); 67 | #endif 68 | */ 69 | Sampler.noteOn(inNote, inVelocity); 70 | } 71 | 72 | inline void handleNoteOff(uint8_t inChannel, uint8_t inNote, uint8_t inVelocity) { 73 | /* 74 | #ifdef RGB_LED 75 | FastLED.setBrightness(1); 76 | FastLED.show(); 77 | #endif 78 | */ 79 | Sampler.noteOff(inNote); 80 | } 81 | 82 | 83 | void handleCC(uint8_t inChannel, uint8_t cc_number, uint8_t cc_value) { 84 | bool onoff = 0; 85 | float scaled; 86 | switch (cc_number) { // global parameters yet set via ANY channel CCs 87 | case CC_SUSTAIN: 88 | onoff = cc_value >> 6; 89 | Sampler.setSustain(onoff); 90 | break; 91 | /* 92 | case CC_RESO: 93 | scaled = (float)cc_value * MIDI_NORM; 94 | DJFilter.SetResonance(scaled); 95 | DEBF("Set Resonance %f\r\n", scaled); 96 | break; 97 | case CC_CUTOFF: 98 | scaled = (float)cc_value * MIDI_NORM; 99 | DJFilter.SetCutoff(scaled); 100 | DEBF("Set Cutoff %f\r\n", scaled); 101 | break; 102 | case CC_COMPRESSOR: 103 | scaled = 3.0f + cc_value * 0.307081f; 104 | Comp.SetRatio(scaled); 105 | DEBF("Set Comp Ratio %f\r\n", scaled); 106 | break; 107 | */ 108 | case CC_ENV_DECAY: 109 | scaled = midiToExpTime(cc_value); 110 | Sampler.setDecayTime(scaled); 111 | DEBF("SAMPLER: MIDI: Env Set Decay Time %f s\r\n", scaled); 112 | break; 113 | case CC_ENV_ATTACK: 114 | scaled = midiToExpTime(cc_value); 115 | Sampler.setAttackTime(scaled); 116 | DEBF("SAMPLER: MIDI: Env Set Attack Time %f s\r\n", scaled); 117 | break; 118 | case CC_ENV_RELEASE: 119 | scaled = midiToExpTime(cc_value); 120 | Sampler.setReleaseTime(scaled); 121 | DEBF("SAMPLER: MIDI: Env Set Release Time %f s\r\n", scaled); 122 | break; 123 | case CC_ENV_SUSTAIN: 124 | scaled = (float)cc_value * MIDI_NORM; 125 | Sampler.setSustainLevel(scaled); 126 | DEBF("SAMPLER: MIDI: Env Set Sustain Level %f\r\n", scaled); 127 | break; 128 | case CC_REVERB_TIME: 129 | scaled = (float)cc_value * MIDI_NORM; 130 | Reverb.SetTime(scaled); 131 | DEBF("SAMPLER: MIDI: Reverb Set Time %f s\r\n", scaled); 132 | break; 133 | case CC_REVERB_LVL: 134 | scaled = (float)cc_value * MIDI_NORM; 135 | Reverb.SetLevel(scaled); 136 | DEBF("SAMPLER: MIDI: Reverb Set Level %f s\r\n", scaled); 137 | break; 138 | /* 139 | case CC_DELAY_TIME: 140 | scaled = (float)cc_value * MIDI_NORM; 141 | Delay.SetLength(scaled); 142 | DEBF("SAMPLER: MIDI: Delay Set Time %f s\r\n", scaled); 143 | break; 144 | case CC_DELAY_FB: 145 | scaled = (float)cc_value * MIDI_NORM; 146 | Delay.SetFeedback(scaled); 147 | DEBF("SAMPLER: MIDI: Delay Set Feedback %f s\r\n", scaled); 148 | break; 149 | case CC_DELAY_LVL: 150 | scaled = (float)cc_value * MIDI_NORM; 151 | Delay.SetLevel(scaled); 152 | DEBF("SAMPLER: MIDI: Deleay Set Level %f s\r\n", scaled); 153 | break; 154 | case CC_DELAY_SEND: 155 | scaled = (float)cc_value * MIDI_NORM; 156 | Sampler.setDelaySendLevel(scaled); 157 | DEBF("SAMPLER: MIDI: Set Send To Delay %f s\r\n", scaled); 158 | break; 159 | */ 160 | case CC_REVERB_SEND: 161 | scaled = (float)cc_value * MIDI_NORM; 162 | Sampler.setReverbSendLevel(scaled); 163 | DEBF("SAMPLER: MIDI: Set Send To Reverb %f s\r\n", scaled); 164 | break; 165 | case CC_RESET_CCS: 166 | case CC_NOTES_OFF: 167 | case CC_SOUND_OFF: 168 | //Sampler.endAll(); 169 | break; 170 | } 171 | } 172 | 173 | void handleProgramChange(uint8_t inChannel, uint8_t number) { 174 | } 175 | 176 | inline void handlePitchBend(uint8_t inChannel, int number) { 177 | Sampler.setPitch(number); 178 | } 179 | 180 | inline void c_major() { 181 | 182 | #ifdef C_MAJOR_ON_START 183 | 184 | delay(10); 185 | Sampler.noteOn(60,100); 186 | Sampler.noteOn(64,100); 187 | Sampler.noteOn(67,100); 188 | delay(1000); 189 | Sampler.noteOff(60, Adsr::END_REGULAR); 190 | Sampler.noteOff(64, Adsr::END_REGULAR); 191 | Sampler.noteOff(67, Adsr::END_REGULAR); 192 | #endif 193 | } -------------------------------------------------------------------------------- /ESP32_SD_Sampler/misc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define DMA_NUM_BUF 2 // number of I2S buffers, 2 should be enough 4 | #define DMA_BUF_LEN 64 // length of I2S buffers use 8 samples or more 5 | #define FASTLED_INTERNAL // remove annoying pragma messages 6 | 7 | const float MIDI_NORM = (1.0f / 127.0f); 8 | const float DIV_128 = (1.0f / 128.0f); 9 | const float TWO_DIV_16383 = (2.0f / 16383.0f); 10 | 11 | const float DIV_SAMPLE_RATE = 1.0f/(float)(SAMPLE_RATE); 12 | 13 | #if (defined ARDUINO_LOLIN_S3_PRO) 14 | #undef BOARD_HAS_UART_CHIP 15 | #endif 16 | 17 | 18 | #ifdef MIDI_VIA_SERIAL || MIDI_USB_DEVICE 19 | //#undef DEBUG_ON 20 | #endif 21 | 22 | #if (defined BOARD_HAS_UART_CHIP) 23 | #define MIDI_PORT_TYPE HardwareSerial 24 | #define MIDI_PORT Serial 25 | #define DEBUG_PORT Serial 26 | #else 27 | #if (ESP_ARDUINO_VERSION_MAJOR < 3) 28 | #define MIDI_PORT_TYPE HWCDC 29 | #define MIDI_PORT USBSerial 30 | #define DEBUG_PORT Serial 31 | #else 32 | #define MIDI_PORT_TYPE HardwareSerial 33 | #define MIDI_PORT Serial 34 | #define DEBUG_PORT Serial 35 | #endif 36 | #endif 37 | 38 | 39 | // debug macros 40 | #ifdef DEBUG_ON 41 | #define DEB(...) DEBUG_PORT.print(__VA_ARGS__) 42 | #define DEBF(...) DEBUG_PORT.printf(__VA_ARGS__) 43 | #define DEBUG(...) DEBUG_PORT.println(__VA_ARGS__) 44 | #else 45 | #define DEB(...) 46 | #define DEBF(...) 47 | #define DEBUG(...) 48 | #endif 49 | 50 | // lookup tables 51 | #define TABLE_BIT 5UL // bits per index of lookup tables. 10 bit means 2^10 = 1024 samples. Values from 5 to 11 are OK. Choose the most appropriate. 52 | #define TABLE_SIZE (1<4 57 | #define SHAPER_LOOKUP_COEF ((float)TABLE_SIZE / SHAPER_LOOKUP_MAX) 58 | #define TWOPI 6.2831853f 59 | #define ONE_DIV_TWOPI 0.159154943f 60 | 61 | // 1.0594630943592952645618252949463 // is a 12th root of 2 (pitch increase per semitone) 62 | // 1.05952207969042122905182367802396 // stretched tuning (plus 60 cents per 7 octaves) 63 | 64 | // converts semitones to speed: -12.0 semitones becomes 0.5, +12.0 semitones becomes 2.0 65 | static __attribute__((always_inline)) inline float semitones2speed(float semitones) { 66 | // return powf(2.0f, semitones * 0.08333333f); 67 | return powf(1.059463f, semitones); 68 | } 69 | 70 | // fast but inaccurate approximation ideas taken from here: 71 | // https://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/ 72 | 73 | static __attribute__((always_inline)) inline float fastPow(float a, float b) { 74 | // int32_t mc = 1072632447; 75 | int16_t mc = 16256; 76 | float ex = fabs(fabs((float)b - (int)b) - 0.5f ) - 0.5f; 77 | union { 78 | float f; 79 | int16_t x[2]; 80 | } u = { a }; 81 | u.x[1] = (int16_t)(b * ((float)u.x[1] - (float)mc) + (float)mc); 82 | u.x[0] = 0; 83 | u.f *= 1.0f + 0.138f * (float)ex ; 84 | return u.f; 85 | } 86 | 87 | static __attribute__((always_inline)) inline float fast_semitones2speed(float semitones) { 88 | return fastPow(2.0f, semitones * 0.08333333f); 89 | } 90 | 91 | static __attribute__((always_inline)) inline float fclamp(float in, float min, float max){ 92 | return fmin(fmax(in, min), max); 93 | } 94 | 95 | static __attribute__((always_inline)) inline float one_div(float a) { 96 | float result; 97 | asm volatile ( 98 | "wfr f1, %1" "\n\t" 99 | "recip0.s f0, f1" "\n\t" 100 | "const.s f2, 1" "\n\t" 101 | "msub.s f2, f1, f0" "\n\t" 102 | "maddn.s f0, f0, f2" "\n\t" 103 | "const.s f2, 1" "\n\t" 104 | "msub.s f2, f1, f0" "\n\t" 105 | "maddn.s f0, f0, f2" "\n\t" 106 | "rfr %0, f0" "\n\t" 107 | : "=r" (result) 108 | : "r" (a) 109 | : "f0","f1","f2" 110 | ); 111 | return result; 112 | } 113 | 114 | int strpos(char *hay, char *needle, int offset) 115 | { 116 | char haystack[strlen(hay)]; 117 | strncpy(haystack, hay+offset, strlen(hay)-offset); 118 | char *p = strstr(haystack, needle); 119 | if (p) 120 | return p - haystack+offset; 121 | return -1; 122 | } 123 | 124 | 125 | 126 | static const float sin_tbl[TABLE_SIZE+1] = { 127 | 0.000000000f, 0.195090322f, 0.382683432f, 0.555570233f, 0.707106781f, 0.831469612f, 0.923879533f, 0.980785280f, 128 | 1.000000000f, 0.980785280f, 0.923879533f, 0.831469612f, 0.707106781f, 0.555570233f, 0.382683432f, 0.195090322f, 129 | 0.000000000f, -0.195090322f, -0.382683432f, -0.555570233f, -0.707106781f, -0.831469612f, -0.923879533f, -0.980785280f, 130 | -1.000000000f, -0.980785280f, -0.923879533f, -0.831469612f, -0.707106781f, -0.555570233f, -0.382683432f, -0.195090322f, 0.000000000f }; 131 | 132 | static const float shaper_tbl[TABLE_SIZE+1] { 133 | 0.000000000f, 0.154990730f, 0.302709729f, 0.437188785f, 0.554599722f, 0.653423588f, 0.734071520f, 0.798242755f, 134 | 0.848283640f, 0.886695149f, 0.915824544f, 0.937712339f, 0.954045260f, 0.966170173f, 0.975136698f, 0.981748725f, 135 | 0.986614298f, 0.990189189f, 0.992812795f, 0.994736652f, 0.996146531f, 0.997179283f, 0.997935538f, 0.998489189f, 136 | 0.998894443f, 0.999191037f, 0.999408086f, 0.999566912f, 0.999683128f, 0.999768161f, 0.999830378f, 0.999875899f , 0.999909204f }; 137 | 138 | inline float lookupTable(const float (&table)[TABLE_SIZE+1], float index ) { // lookup value in a table by float index, using linear interpolation 139 | float v1, v2, res; 140 | int32_t i; 141 | float f; 142 | i = (int32_t)index; 143 | f = (float)index - i; 144 | v1 = (table)[i]; 145 | v2 = (table)[i+1]; 146 | res = (float)f * (float)(v2-v1) + v1; 147 | return res; 148 | } 149 | 150 | inline float fast_shape(float x) { 151 | float sign = 1.0f; 152 | if (x < 0) { 153 | x = -x; 154 | sign = -1.0f; 155 | } 156 | if (x >= 4.95f) { 157 | return sign; // tanh(x) ~= 1, when |x| > 4 158 | } 159 | return (float)sign * (float)lookupTable(shaper_tbl, ((float)x * (float)SHAPER_LOOKUP_COEF)); // lookup table contains tanh(x), 0 <= x <= 5 160 | } 161 | 162 | inline void tend_to(float &x, const float &target, float rate) { 163 | x = (float)x * (float)rate + (1.0f - (float)rate) * ((float)target - (float)x); 164 | } 165 | 166 | inline float fast_sin(const float x) { 167 | const float argument = ((x * ONE_DIV_TWOPI) * TABLE_SIZE); 168 | const float res = lookupTable(sin_tbl, CYCLE_INDEX(argument)+((float)argument-(int32_t)argument)); 169 | return res; 170 | } 171 | 172 | inline float fast_cos(const float x) { 173 | const float argument = ((x * ONE_DIV_TWOPI + 0.25f) * TABLE_SIZE); 174 | const float res = lookupTable(sin_tbl, CYCLE_INDEX(argument)+((float)argument-(int32_t)argument)); 175 | return res; 176 | } 177 | 178 | inline void fast_sincos(const float x, float* sinRes, float* cosRes){ 179 | *sinRes = fast_sin(x); 180 | *cosRes = fast_cos(x); 181 | } 182 | 183 | // taken from here: https://www.esp32.com/viewtopic.php?t=10540#p43343 184 | // probably it's fixed already, so keep it here just in case 185 | inline float divsf(float a, float b) { 186 | float result; 187 | asm volatile ( 188 | "wfr f0, %1\n" 189 | "wfr f1, %2\n" 190 | "div0.s f3, f1 \n" 191 | "nexp01.s f4, f1 \n" 192 | "const.s f5, 1 \n" 193 | "maddn.s f5, f4, f3 \n" 194 | "mov.s f6, f3 \n" 195 | "mov.s f7, f1 \n" 196 | "nexp01.s f8, f0 \n" 197 | "maddn.s f6, f5, f3 \n" 198 | "const.s f5, 1 \n" 199 | "const.s f2, 0 \n" 200 | "neg.s f9, f8 \n" 201 | "maddn.s f5,f4,f6 \n" 202 | "maddn.s f2, f9, f3 \n" /* Original was "maddn.s f2, f0, f3 \n" */ 203 | "mkdadj.s f7, f0 \n" 204 | "maddn.s f6,f5,f6 \n" 205 | "maddn.s f9,f4,f2 \n" 206 | "const.s f5, 1 \n" 207 | "maddn.s f5,f4,f6 \n" 208 | "maddn.s f2,f9,f6 \n" 209 | "neg.s f9, f8 \n" 210 | "maddn.s f6,f5,f6 \n" 211 | "maddn.s f9,f4,f2 \n" 212 | "addexpm.s f2, f7 \n" 213 | "addexp.s f6, f7 \n" 214 | "divn.s f2,f9,f6\n" 215 | "rfr %0, f2\n" 216 | :"=r"(result):"r"(a), "r"(b) 217 | ); 218 | return result; 219 | } 220 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/sampler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WAV_CHANNELS 2 // do not change, not implemented 4 | #define MAX_CONFIG_LINE_LEN 256 // 4 < x < 256 , must be divisible by 4, no need to change this 5 | #define STR_LEN MAX_CONFIG_LINE_LEN 6 | 7 | #include 8 | #include 9 | #include "voice.h" 10 | #include "sdmmc.h" 11 | 12 | enum eVoiceAlloc_t { VA_OLDEST, VA_MOST_QUIET, VA_PERCEPTUAL, VA_NUMBER }; // not implemented 13 | enum eVeloCurve_t { VC_LINEAR, VC_CUSTOM, VC_SOFT1, VC_SOFT2, VC_SOFT3, VC_HARD1, VC_HARD2, VC_HARD3, VC_CONST, VC_NUMBER }; // VC_LINEAR, VC_CUSTOM implemented 14 | enum eItem_t { P_NUMBER, P_NAME, P_MIDINOTE, P_OCTAVE, P_SEPARATOR, P_VELO, P_INSTRUMENT }; // filename template elements 15 | enum eInstr_t { SMP_MELODIC, SMP_PERCUSSIVE }; 16 | enum eSection_t { S_NONE, S_SAMPLESET, S_FILENAME, S_NOTE, S_RANGE, S_GROUP }; 17 | 18 | using str8_t = FixedString<8>; 19 | using str20_t = FixedString<20>; 20 | using str64_t = FixedString<64>; 21 | using str256_t = FixedString; 22 | using variants_t = std::vector; 23 | 24 | typedef struct { 25 | uint8_t first = 0 ; // lowest midi note in range 26 | uint8_t last = 127 ; // highest midi note in range 27 | str20_t instr = ""; 28 | bool noteoff = true; 29 | float speed = 1.0f; 30 | int velo_layer = -1; 31 | int limit_same = 2; 32 | float attack_time = 0.0f; 33 | float decay_time = 0.5f; 34 | float sustain_level = 1.0f; 35 | float release_time = 12.0f; 36 | bool loop = false; 37 | inline void clear(eInstr_t t) { 38 | first = 0; 39 | last = 127; 40 | instr = ""; 41 | velo_layer = -1; 42 | noteoff = (t != SMP_PERCUSSIVE) ; 43 | speed = 1.0f; 44 | limit_same = 2; 45 | attack_time = 0.0f; 46 | decay_time = 0.5f; 47 | sustain_level = 1.0f; 48 | release_time = 12.0f; 49 | loop = false; 50 | } 51 | } ini_range_t; 52 | 53 | typedef struct { 54 | str8_t name[2] ; // sharp (#) and flat (b) variants 55 | int octave ; // octave number (-1 to 9) 56 | float freq ; // calculated frequency for each midi note (A4=440Hz) 57 | float tuning = 1.0f; // 0.9 = 10% lower, 1.1 = 10% higher 58 | int transpose = 0; // +/- semitones 59 | bool noteoff = true; // should it handle note off messages 60 | int group = -1; 61 | int limit_same = 2; 62 | float attack_time = 0.0f; 63 | float decay_time = 0.01f; 64 | float sustain_level = 1.0f; 65 | float release_time = 0.05f; 66 | } midikey_t; 67 | 68 | typedef struct { 69 | eItem_t item_type; 70 | str20_t item_str; 71 | } template_item_t; 72 | 73 | 74 | const str8_t notes[2][12]= { 75 | {"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"}, 76 | {"C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B"} 77 | }; 78 | 79 | class SamplerEngine { 80 | 81 | public: 82 | SamplerEngine() {}; 83 | void init(SDMMC_FAT32* Card); 84 | void initKeyboard(); 85 | void fadeOut(int id); 86 | void getSample(float& sampleL, float& sampleR); 87 | fname_t getFolderName(int id) { return _folders[id]; } 88 | fname_t getCurrentFolder() { return _currentFolder; } 89 | int getActiveVoices(); 90 | void freeSomeVoices(); 91 | int scanRootFolder(); // scans root folder for sample directories, returns count of valid sample folders, -1 if error 92 | void loadInstrument(uint8_t noteLow=0, uint8_t noteHigh=127); // loads config and prepares samples from the current folder 93 | inline void setSampleRate(unsigned int sr) { if (sr > 1) _sampleRate = sr; } 94 | inline void setRootFolder(const fname_t& rf) { _rootFolder = rf; } 95 | inline void setMaxVoices(byte mv) { _maxVoices = constrain(mv, 1, MAX_POLYPHONY); } 96 | inline void setVoiceAllocMethod(eVoiceAlloc_t va) { _voiceAllocMethod = va ; } 97 | inline void setCurrentFolder(int folder_id); // sets current folder to the desired path[folder_id] 98 | inline void setNextFolder(); // sets current folder to the next dir which was found during scanFolders() 99 | inline void setPrevFolder(); // sets current folder to the previous dir which was found during scanFolders() 100 | inline void setSustain(bool onoff) ; 101 | inline void setPitch(int num) ; // -8192 .. 8191 102 | inline void setDelaySendLevel(float val) {_sendDelay = val;} 103 | inline void setReverbSendLevel(float val) {_sendReverb = val;} 104 | inline void setVolume(float val) {_amp = val;} 105 | inline void setPano(float val) {_pano = val;} 106 | inline float getDelaySendLevel() {return _sendDelay;} 107 | inline float getReverbSendLevel() {return _sendReverb;} 108 | inline float getVolume() {return _amp;} 109 | inline float getPano() {return _pano;} 110 | 111 | void storeGroup( variants_t& vars ); 112 | void setSustainLevel(float seconds); 113 | void setAttackTime(float seconds); 114 | void setDecayTime(float seconds); 115 | void setReleaseTime(float seconds); 116 | inline void noteOn(uint8_t midiNote, uint8_t velocity); 117 | inline void noteOff(uint8_t midiNote, Adsr::eEnd_t end_type = Adsr::END_REGULAR); 118 | void fillBuffer(); 119 | 120 | private: 121 | SDMMC_FAT32* _Card; 122 | inline int assignVoice(byte midi_note, byte midi_velocity); // returns id of a slot to use for a new note 123 | void parseIni(); // loads config from current folder, determining how wav files spread over the notes/velocities 124 | bool parseFilenameTemplate(str256_t& line); 125 | void processNameParser(entry_t* entry); 126 | eSection_t parseSection( str256_t& val ); 127 | bool parseBoolValue( str256_t& val ); 128 | float parseFloatValue( str256_t& val ); 129 | int parseIntValue( str256_t& val ); 130 | eInstr_t parseInstrType( str256_t& val ); 131 | variants_t parseVariants( str256_t& val); 132 | void parseLimits( str256_t& val); 133 | void parseWavHeader(entry_t* entry, sample_t& smp); 134 | void applyRange(ini_range_t& range); 135 | void finalizeMapping(); 136 | void buildVeloCurve(); 137 | uint8_t mapVelo(uint8_t velo); 138 | uint8_t unMapVelo(uint8_t mappedVelo); 139 | void resetSamples(); 140 | uint8_t midiNoteByName(str8_t noteName); 141 | void printMapping(); 142 | sample_t _sampleMap[128][MAX_VELOCITY_LAYERS]; 143 | midikey_t _keyboard[128]; 144 | uint8_t _groups[128][ ( ( MAX_NOTES_PER_GROUP - 1 ) * MAX_GROUPS_CROSSES ) ]; // each of 128 elements contains notes to shoot when it starts 145 | float _ampCurve[128]; // velocity to amplification mapping [0.0 ... 1.0] to make seamless velocity response curve 146 | eVeloCurve_t _veloCurve = VC_LINEAR; 147 | uint8_t _veloMap[128]; 148 | uint8_t _veloLayers = 1; 149 | float _divVeloLayers = 1.0f; 150 | int _sampleRate = SAMPLE_RATE; 151 | uint8_t _sampleChannels = WAV_CHANNELS; // 2 = stereo, 1 = mono : use or not stereo data in sample files 152 | uint8_t _maxVoices = MAX_POLYPHONY; 153 | int _limitSameNotes = MAX_SAME_NOTES; 154 | fname_t _rootFolder ; 155 | volatile int _currentFolderId = 0; 156 | fname_t _currentFolder ; 157 | int _sampleSetsCount = 0; 158 | float _sendDelay = 0.0f; 159 | float _sendReverb = 0.0f; 160 | float _amp = 0.9f; 161 | float _pano = 0.5f; 162 | float _attackTime = 0.0f; 163 | float _decayTime = 0.05f; 164 | float _releaseTime = 8.0f; 165 | float _sustainLevel = 1.0f; 166 | float _speed = 1.0f; 167 | int _pitchBendSemitones = 2; 168 | eVoiceAlloc_t _voiceAllocMethod = VA_OLDEST; // not implemented, VA_PERCEPTUAL is gonna be the only one 169 | int _parser_i = 0; 170 | bool _normalized = false; 171 | bool _sustain = false; 172 | eInstr_t _type = SMP_MELODIC; 173 | str64_t _title = ""; 174 | Voice Voices[MAX_POLYPHONY] ; 175 | variants_t _veloVars ; 176 | std::vector _folders ; 177 | std::vector _template; 178 | std::vector _ranges ; 179 | }; 180 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/sampler.ino: -------------------------------------------------------------------------------- 1 | #include "sampler.h" 2 | #include "sdmmc_file.h" 3 | 4 | void SamplerEngine::init(SDMMC_FAT32* Card){ 5 | _Card = Card; 6 | _rootFolder = ROOT_FOLDER; 7 | _limitSameNotes = MAX_SAME_NOTES; 8 | _maxVoices = MAX_POLYPHONY; 9 | int num_sets = scanRootFolder(); 10 | for (int i = 0; i < num_sets; i++) { 11 | DEBF("Folder %d : %s\r\n" , i ,_folders[i].c_str()); 12 | } 13 | DEBF("Total %d folders with samples found\r\n", num_sets); 14 | initKeyboard(); 15 | for (int i = 0 ; i < MAX_POLYPHONY ; i++) { 16 | DEBF("Voice %d: ", i); 17 | // sustain is global and needed for every voice, so we just pass a pointer to it. 18 | Voices[i].init(Card, &_sustain, &_normalized); 19 | Voices[i].my_id = i; 20 | } 21 | if (num_sets > 0) { 22 | setSampleRate(SAMPLE_RATE); 23 | DEBUG("+++"); 24 | } else { 25 | while(true) { 26 | // loop forever 27 | delay(1000); 28 | DEBUG("--- no samples found"); 29 | } 30 | } 31 | setReverbSendLevel(0.2f); 32 | setDelaySendLevel(0.0f); 33 | } 34 | 35 | int SamplerEngine::scanRootFolder() { 36 | fpath_t dirname; 37 | DEBUG("SAMPLER: Scanning root folder"); 38 | SDMMC_FileReader Reader(_Card); 39 | _rootFolder = ROOT_FOLDER; 40 | _folders.clear(); 41 | _Card->setCurrentDir(_rootFolder); 42 | int index = 0; 43 | while (true) { 44 | entry_t* entry = _Card->nextEntry(); 45 | if (entry->is_end) break; 46 | if (entry->is_dir) { 47 | point_t p = _Card->getCurrentPoint(); 48 | dirname = entry->name; 49 | DEBUG(dirname.c_str()); 50 | _Card->setCurrentDir(dirname); 51 | if (Reader.open(INI_FILE) == ESP_OK ) { 52 | Reader.close(); 53 | _folders.push_back(dirname); 54 | index++; 55 | } 56 | _Card->setCurrentPoint(p); 57 | } 58 | } 59 | _sampleSetsCount = index; 60 | return index; 61 | } 62 | 63 | inline int SamplerEngine::assignVoice(byte midi_note, byte velo){ 64 | float maxVictimScore = 0.0; 65 | float minAmp = 1.0e30; 66 | int id = 0; 67 | 68 | for (int i = 0 ; i < _maxVoices ; i++) { 69 | if (!Voices[i].isActive()){ 70 | // DEBUG("SAMPLER: First vacant voice"); 71 | return (int)i; 72 | } 73 | } 74 | 75 | for (int i = 0 ; i < _maxVoices ; i++) { 76 | if (Voices[i].getKillScore() > maxVictimScore){ 77 | maxVictimScore = Voices[i].getKillScore(); 78 | id = i; 79 | } 80 | } 81 | DEBUG("SAMPLER: No free slot: Steal a voice"); 82 | return id; 83 | } 84 | 85 | inline void SamplerEngine::noteOn(uint8_t midiNote, uint8_t velo){ 86 | int i = assignVoice(midiNote, velo); 87 | sample_t smp = _sampleMap[midiNote][mapVelo(velo)]; 88 | for (int n = 0; n < ( ( MAX_NOTES_PER_GROUP - 1 ) * MAX_GROUPS_CROSSES ); n++ ) { 89 | if (_groups[midiNote][n] == 255) break; // terminate 90 | DEBF("SAMPLER: GROUP KILL: %d\r\n", _groups[midiNote][n]); 91 | noteOff(_groups[midiNote][n], Adsr::END_SEMI_FAST); // provide exclusivity 92 | } 93 | if (smp.channels > 0) { 94 | // DEBF("SAMPLER: voice %d note %d velo %d\r\n", i, midiNote, velo); 95 | Voices[i].setStarted(false); 96 | Voices[i].setAttackTime(_keyboard[midiNote].attack_time); 97 | Voices[i].setDecayTime(_keyboard[midiNote].decay_time); 98 | Voices[i].setReleaseTime(_keyboard[midiNote].release_time); 99 | Voices[i].setSustainLevel(_keyboard[midiNote].sustain_level); 100 | Voices[i].start(_sampleMap[midiNote][mapVelo(velo)], midiNote, velo); 101 | } else { 102 | DEBUG("SAMPLER: no sample assigned"); 103 | return; 104 | } 105 | } 106 | 107 | inline void SamplerEngine::noteOff(uint8_t midiNote, Adsr::eEnd_t end_type ){ 108 | if (_keyboard[midiNote].noteoff || end_type!= Adsr::END_REGULAR) { 109 | for (int i = 0 ; i < MAX_POLYPHONY ; i++) { 110 | if (Voices[i].getMidiNote() == midiNote && Voices[i].isActive()) { 111 | // DEBF("SAMPLER: NOTE OFF Voice %d note %d \r\n", i, midiNote); 112 | Voices[i].setPressed(false); 113 | Voices[i].end(end_type); 114 | } 115 | } 116 | } 117 | } 118 | 119 | 120 | inline void SamplerEngine::setSustain(bool onoff) { 121 | _sustain = onoff; 122 | DEBF("SAMPLER: sustain: %d\r\n", onoff); 123 | if (!onoff) { 124 | for (int i = 0 ; i < MAX_POLYPHONY ; i++) { 125 | if (_keyboard[Voices[i].getMidiNote()].noteoff && Voices[i].isActive() ) { 126 | Voices[i].end(Adsr::END_REGULAR); 127 | } 128 | } 129 | } 130 | } 131 | 132 | 133 | void SamplerEngine::setAttackTime(float val) { 134 | for (int i = 0 ; i < 128; i++ ) { 135 | _keyboard[i].attack_time = val; 136 | } 137 | #ifdef ADSR_LIVE_UPDATE 138 | for (int i = 0 ; i < MAX_POLYPHONY ; i++) { 139 | Voices[i].setAttackTime(val); 140 | } 141 | #endif 142 | } 143 | 144 | void SamplerEngine::setDecayTime(float val) { 145 | for (int i = 0; i < 128; i++) { 146 | _keyboard[i].decay_time = val; 147 | } 148 | #ifdef ADSR_LIVE_UPDATE 149 | for (int i = 0 ; i < MAX_POLYPHONY ; i++) { 150 | Voices[i].setDecayTime(val); 151 | } 152 | #endif 153 | } 154 | 155 | void SamplerEngine::setReleaseTime(float val) { 156 | for (int i = 0; i < 128; i++) { 157 | _keyboard[i].release_time = val; 158 | } 159 | #ifdef ADSR_LIVE_UPDATE 160 | for (int i = 0 ; i < MAX_POLYPHONY ; i++) { 161 | Voices[i].setReleaseTime(val); 162 | } 163 | #endif 164 | } 165 | 166 | void SamplerEngine::setSustainLevel(float val) { 167 | for (int i = 0; i < 128; i++) { 168 | _keyboard[i].sustain_level = val; 169 | } 170 | #ifdef ADSR_LIVE_UPDATE 171 | for (int i = 0 ; i < MAX_POLYPHONY ; i++) { 172 | Voices[i].setSustainLevel(val); 173 | } 174 | #endif 175 | } 176 | 177 | uint8_t SamplerEngine::mapVelo(uint8_t velo) { 178 | switch(_veloCurve) { 179 | case VC_LINEAR: 180 | return (uint8_t)((float)velo * (float)_veloLayers * DIV_128); 181 | case VC_CUSTOM: 182 | return _veloMap[velo]; 183 | default: 184 | return _veloLayers-1; 185 | return 0; 186 | } 187 | } 188 | 189 | uint8_t SamplerEngine::unMapVelo(uint8_t mappedVelo) { 190 | switch(_veloCurve) { 191 | case VC_LINEAR: 192 | return (uint8_t)(127.0 * (float)mappedVelo * _divVeloLayers) ; 193 | default: 194 | return 90; 195 | return 0; 196 | } 197 | } 198 | 199 | void IRAM_ATTR SamplerEngine::fillBuffer() { 200 | // search and fill the most hungry buffer 201 | static uint32_t hungerMax; 202 | static uint32_t hunger; 203 | static int iToFeed; 204 | hunger = hungerMax = 0; 205 | iToFeed = 0; 206 | for (int i=0; i<_maxVoices; i++) { 207 | hunger = Voices[i].hunger(); 208 | if (hunger > hungerMax) { 209 | hungerMax = hunger; 210 | iToFeed = i; 211 | } 212 | } 213 | Voices[iToFeed].feed(); 214 | } 215 | 216 | 217 | inline void SamplerEngine::setCurrentFolder(int folder_id) { 218 | resetSamples(); 219 | folder_id = constrain(folder_id, 0, _sampleSetsCount-1); // just in case 220 | _currentFolder = _folders[folder_id]; 221 | _currentFolderId = folder_id; 222 | _Card->setCurrentDir(_rootFolder); 223 | DEB(folder_id); 224 | DEB(": "); 225 | DEBUG(_folders[folder_id].c_str()); 226 | _Card->setCurrentDir(_folders[folder_id]); 227 | initKeyboard(); // it resets _keyboard[] which holds key-specific parameters 228 | parseIni(); // this will read the sampler.ini file and prepare name template along with other parameters 229 | _Card->rewindDir(); 230 | while (true) { // iterate thru the selected directory 231 | entry_t* entry = _Card->nextEntry(); 232 | if (entry->is_end) break; 233 | if (!entry->is_dir) { 234 | processNameParser(entry); // parse filenames basing on a prepared template 235 | } 236 | } 237 | //printMapping(); 238 | finalizeMapping(); // fill the gaps when we don't have dedicated samples for some pitches or velocity layers 239 | printMapping(); 240 | } 241 | 242 | 243 | inline void SamplerEngine::setNextFolder() { 244 | _currentFolderId++; 245 | if (_currentFolderId > _sampleSetsCount-1) _currentFolderId = 0; 246 | setCurrentFolder(_currentFolderId); 247 | } 248 | 249 | inline void SamplerEngine::setPrevFolder() { 250 | _currentFolderId--; 251 | if (_currentFolderId < 0) _currentFolderId = _sampleSetsCount - 1; 252 | setCurrentFolder(_currentFolderId); 253 | } 254 | 255 | void SamplerEngine::initKeyboard() { 256 | for (int i=0; i<128; ++i) { 257 | _keyboard[i].freq = (440.0f / 32.0f) * pow(2, ((float)(i - 9) / 12.0f)); 258 | _keyboard[i].octave = (i / 12) -1; 259 | _keyboard[i].name[0] = notes[0][i%12]; 260 | _keyboard[i].name[1] = notes[1][i%12]; 261 | _keyboard[i].transpose = 0; 262 | _keyboard[i].noteoff = true; 263 | //_keyboard[i].velo_layer = 1; 264 | _keyboard[i].tuning = 1.0f; 265 | // DEBF("%d:\t%s\t%s\t%d\t%7.3f\r\n", i, _keyboard[i].name[0].c_str(), _keyboard[i].name[1].c_str(), _keyboard[i].octave, _keyboard[i].freq); 266 | } 267 | } 268 | 269 | void SamplerEngine::resetSamples() { 270 | _amp = 1.0; 271 | _attackTime = 0.0f; 272 | _decayTime = 0.1f; 273 | _sustainLevel = 1.0f; 274 | _releaseTime = 8.0f; 275 | for (int i = 0 ; i < MAX_POLYPHONY ; i++) { 276 | Voices[i].end(Adsr::END_FAST); 277 | } 278 | for (int i = 0; i _keyboard[midi_note].limit_same) { // if we have limit overrun, find the best candidate 336 | for (int j = 0 ; j < MAX_POLYPHONY ; j++) { 337 | if (Voices[j].getMidiNote() == midi_note) { 338 | score = Voices[j].getKillScore(); 339 | if (score > maxSameKillScore) { 340 | maxSameKillScore = score; 341 | id = j; 342 | } 343 | } 344 | } 345 | Voices[id].end(Adsr::END_FAST); 346 | return; 347 | } 348 | score = Voices[i].getKillScore(); 349 | if (score > maxKillScore) { 350 | maxKillScore = score; 351 | id = i; 352 | } 353 | } 354 | } 355 | if ( ( n + SACRIFY_VOICES ) > MAX_POLYPHONY ) { 356 | Voices[id].end(Adsr::END_FAST); 357 | return; 358 | } 359 | } 360 | 361 | 362 | inline void SamplerEngine::setPitch(int number) { 363 | float speedModifier = ((((float)number + 8191.5f) * (float)TWO_DIV_16383 ) - 1.0f ) * (float)_pitchBendSemitones; 364 | speedModifier = fast_semitones2speed(speedModifier); 365 | for (int i=0; i<_maxVoices; i++) { 366 | Voices[i].setPitch(speedModifier); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/sampler_ini.ino: -------------------------------------------------------------------------------- 1 | #include "sampler.h" 2 | #include "sdmmc_file.h" 3 | 4 | void SamplerEngine::parseIni() { 5 | eSection_t section = S_NONE; 6 | _veloCurve = VC_LINEAR; 7 | ini_range_t range; 8 | _template.clear(); 9 | _ranges.clear(); 10 | variants_t grps; 11 | for (int i = 0 ; i < 128; i++) { 12 | for (int j = 0; j < ( ( MAX_NOTES_PER_GROUP - 1 ) * MAX_GROUPS_CROSSES ); j++) { 13 | _groups[i][j] = 255; // empty groups 14 | } 15 | } 16 | _Card->rewindDir(); 17 | int i, a, b; 18 | str256_t iniStr; 19 | str20_t tok; 20 | _parser_i = 0; 21 | DEBUG("Reading ini file"); 22 | SDMMC_FileReader Reader(_Card); 23 | Reader.open(INI_FILE); 24 | while (Reader.available()) { 25 | Reader.read_line(iniStr); 26 | DEBUG(iniStr.c_str()); 27 | iniStr.trim(); 28 | iniStr.toUpperCase(); 29 | if (iniStr.empty()) continue; 30 | else if (iniStr.startsWith("#")) continue; // comment 31 | else if (iniStr.startsWith(";")) continue; // comment 32 | if (iniStr.startsWith("[") && iniStr.endsWith("]")) { // new section 33 | if (section==S_NOTE || section==S_RANGE) applyRange(range); // save parsed range/note section 34 | range.clear(_type); 35 | section = parseSection(iniStr); 36 | //DEBUG(section); 37 | continue; 38 | } 39 | int s1 = iniStr.indexOf('='); // leftmost of ":" or "=" will be treated as left and right parts delimeter 40 | int s2 = iniStr.indexOf(':'); 41 | if ( s1<=0 && s2<=0 ) continue; 42 | if ( s1>0 && s2>0 ) { 43 | if ( s10 52 | tok = iniStr.substring(0, s1); 53 | iniStr.remove(0, s1+1); 54 | } 55 | if ( s1<=0 ) { // s2>0 56 | tok = iniStr.substring(0, s2); 57 | iniStr.remove(0, s2+1); 58 | } 59 | tok.trim(); 60 | iniStr.trim(); 61 | switch (section) { 62 | case S_FILENAME: 63 | // template 64 | if (tok == "TEMPLATE") { parseFilenameTemplate(iniStr) ; continue; } 65 | // veloVariants 66 | if (tok == "VELOVARIANTS" || tok == "VELO_VARIANTS") {_veloVars = parseVariants(iniStr); continue; } 67 | // veloLimits 68 | if (tok == "VELOLIMITS" || tok == "VELO_LIMITS") { 69 | _veloCurve = VC_CUSTOM; 70 | parseLimits(iniStr); continue; 71 | } 72 | break; 73 | case S_NOTE: 74 | case S_RANGE: 75 | if (tok == "NAME") {range.first = midiNoteByName(iniStr); range.last = range.first; continue;} 76 | if (tok == "FIRST" || tok == "FIRST_NOTE" || tok == "FIRSTNOTE") {range.first = midiNoteByName(iniStr); continue;} 77 | if (tok == "LAST" || tok == "LAST_NOTE" || tok == "LASTNOTE") {range.last = midiNoteByName(iniStr); continue;} 78 | if (tok == "INSTR") {range.instr = iniStr; continue;} 79 | if (tok == "NOTEOFF" || tok == "NOTE_OFF") {range.noteoff = parseBoolValue(iniStr); continue;} 80 | if (tok == "SPEED") {range.speed = parseFloatValue(iniStr); continue;} 81 | if (tok == "LIMIT_SAME_NOTES" || tok == "LIMIT_SAME_NOTE" || tok == "LIMITSAMENOTE" || tok == "LIMITSAMENOTES") { 82 | range.limit_same = min(MAX_POLYPHONY, parseIntValue(iniStr)); 83 | if (range.limit_same == 0 ) range.limit_same = MAX_POLYPHONY; 84 | continue; 85 | } 86 | if (tok == "ATTACKTIME" || tok == "ATTACK_TIME") {range.attack_time = parseFloatValue(iniStr); continue;} 87 | if (tok == "DECAYTIME" || tok == "DECAY_TIME") {range.decay_time = parseFloatValue(iniStr); continue;} 88 | if (tok == "RELEASETIME" || tok == "RELEASE_TIME") {range.release_time = parseFloatValue(iniStr); continue;} 89 | if (tok == "SUSTAINLEVEL" || tok == "SUSTAIN_LEVEL") {range.sustain_level = parseFloatValue(iniStr); continue;} 90 | if (tok == "LOOP" || tok == "AUTOREPEAT" || tok == "REPEAT" || tok == "CYCLE" ) {range.loop = parseBoolValue(iniStr); continue;} 91 | break; 92 | case S_GROUP: 93 | if (tok == "NOTES") {grps = parseVariants(iniStr); storeGroup(grps); continue; } 94 | break; 95 | case S_NONE: 96 | case S_SAMPLESET: 97 | default: 98 | if (tok == "TITLE") {_title = iniStr; continue;} 99 | if (tok == "TYPE") { 100 | _type = parseInstrType(iniStr); 101 | for (int i=0; i<128; ++i) { 102 | _keyboard[i].noteoff = (_type!=SMP_PERCUSSIVE); 103 | } 104 | continue; 105 | } 106 | if (tok == "NORMALIZED") {_normalized = parseBoolValue(iniStr); continue;} 107 | if (tok == "AMP" || tok == "AMPLIFY") {_amp = parseFloatValue(iniStr); continue;} 108 | if (tok == "LIMIT_SAME_NOTES" || tok == "LIMIT_SAME_NOTE" || tok == "LIMITSAMENOTE" || tok == "LIMITSAMENOTES") { 109 | _limitSameNotes = min(MAX_POLYPHONY, parseIntValue(iniStr)); 110 | if (_limitSameNotes == 0) _limitSameNotes = MAX_POLYPHONY; 111 | for (int i=0; i<128; ++i) { 112 | _keyboard[i].limit_same = _limitSameNotes; 113 | } 114 | continue; 115 | } 116 | if (tok == "MAX_VOICES" || tok == "MAX_POLYPHONY" || tok == "MAXPOLYPHONY" || tok == "MAXVOICES" || tok == "POLYPHONY") {_maxVoices = min(MAX_POLYPHONY, parseIntValue(iniStr)); continue;} 117 | if (tok == "ATTACKTIME" || tok == "ATTACK_TIME") {_attackTime = parseFloatValue(iniStr); setAttackTime(_attackTime); continue;} 118 | if (tok == "DECAYTIME" || tok == "DECAY_TIME") {_decayTime = parseFloatValue(iniStr); setDecayTime(_decayTime); continue;} 119 | if (tok == "RELEASETIME" || tok == "RELEASE_TIME") {_releaseTime = parseFloatValue(iniStr); setReleaseTime(_releaseTime); continue;} 120 | if (tok == "SUSTAINLEVEL" || tok == "SUSTAIN_LEVEL") {_sustainLevel = parseFloatValue(iniStr); setSustainLevel(_sustainLevel); continue;} 121 | break; 122 | } 123 | } 124 | // save parsed section 125 | if (section==S_NOTE || section==S_RANGE) applyRange(range); 126 | Reader.close(); 127 | DEBUG("SAMPLER: INI: PARSING COMPLETE"); 128 | //delay(1000); 129 | } 130 | 131 | eSection_t SamplerEngine::parseSection( str256_t& val ) { 132 | //DEB(val.c_str()); 133 | int len = val.length(); 134 | val.remove(len-1); // remove closing "]" 135 | val.remove(0,1); // remove opening "[" 136 | val.trim(); // trim the inner string 137 | if (val == "SAMPLESET") return S_SAMPLESET; 138 | if (val == "FILENAME") return S_FILENAME; 139 | if (val == "NOTE") return S_NOTE; 140 | if (val == "RANGE") return S_RANGE; 141 | if (val == "GROUP") return S_GROUP; 142 | return S_NONE; 143 | } 144 | 145 | bool SamplerEngine::parseBoolValue( str256_t& val ) { 146 | val.trim(); 147 | val.toUpperCase(); 148 | bool var = false; 149 | if (val=="TRUE" || val=="YES" || val=="Y" || val=="1") var = true; 150 | if (val=="FALSE" || val=="NO" || val=="NONE" || val=="N" || val=="0") var = false; 151 | return var; 152 | } 153 | 154 | 155 | float SamplerEngine::parseFloatValue( str256_t& val ) { 156 | val.trim(); 157 | val.toUpperCase(); 158 | float f = val.toFloat(); 159 | DEBF("INI: float %f\r\n", f); 160 | return f; 161 | } 162 | 163 | 164 | int SamplerEngine::parseIntValue( str256_t& val ) { 165 | val.trim(); 166 | val.toUpperCase(); 167 | int i = val.toInt(); 168 | DEBF("INI: int %d\r\n", i); 169 | return i; 170 | } 171 | 172 | 173 | variants_t SamplerEngine::parseVariants( str256_t& val ) { 174 | DEBUG("INI: Parsing variants"); 175 | variants_t vars; 176 | vars.clear(); 177 | val.trim(); 178 | val.toUpperCase(); 179 | int j = 0; 180 | int k = 0; 181 | while (true) { 182 | str20_t s1; 183 | k = val.indexOf("," , j); 184 | if (k == -1 || k < j) k = val.length(); 185 | s1 = val.substring(j, k); 186 | s1.trim(); 187 | vars.push_back(s1); 188 | DEBF("Adding %s\r\n" , s1.c_str()); 189 | if (k == val.length()) break; 190 | j = k + 1; 191 | } 192 | return vars; 193 | } 194 | 195 | 196 | void SamplerEngine::storeGroup( variants_t& vars ) { 197 | uint8_t midi_note[MAX_NOTES_PER_GROUP]; 198 | int i=0; 199 | uint8_t a_note; 200 | for (auto & str: vars) { 201 | midi_note[i] = midiNoteByName(str); 202 | i++; 203 | } 204 | DEBUG(); 205 | for (int n = 0 ; n < i; n++) { 206 | a_note = midi_note[n]; 207 | // DEBF("INI: Placing group: midi_note %d\r\n", a_note); 208 | for (int m = 0 ; m < ( ( MAX_NOTES_PER_GROUP - 1 ) * MAX_GROUPS_CROSSES ); m++) { 209 | if (_groups[a_note][m] == 255) { // found an empty element 210 | for (int j = 0; j < i; j++) { 211 | if (midi_note[j] != a_note) { 212 | // DEBF("------- adding %d\r\n", midi_note[j]); 213 | _groups[a_note][m] = midi_note[j]; 214 | m++; 215 | } 216 | } 217 | break; 218 | } 219 | } 220 | } 221 | } 222 | 223 | 224 | void SamplerEngine::parseLimits( str256_t& val) { 225 | DEBUG("INI: Parsing velo limits"); 226 | variants_t vars; 227 | vars.clear(); 228 | val.trim(); 229 | int j = 0; 230 | int k = 0; 231 | int lim1 = 0; 232 | int lim2 = 0; 233 | int layer = 0; 234 | while (true) { 235 | str20_t s1; 236 | k = val.indexOf("," , j); 237 | if (k == -1 || k < j) k = val.length(); 238 | s1 = val.substring(j, k); 239 | s1.trim(); 240 | lim2 = s1.toInt(); 241 | for (int i = lim1; i <= lim2; i++) { _veloMap[i] = layer; } 242 | DEBF("Adding %s\r\n" , s1.c_str()); 243 | layer++; 244 | lim1 = lim2 + 1; 245 | if (k == val.length()) break; 246 | if (lim1 > 127) break; 247 | j = k + 1; 248 | } 249 | _veloCurve = VC_CUSTOM; 250 | } 251 | 252 | eInstr_t SamplerEngine::parseInstrType(str256_t& val) { 253 | val.trim(); 254 | val.toUpperCase(); 255 | if (val == "MELODIC") return SMP_MELODIC; 256 | if (val == "PERCUSSIVE") return SMP_PERCUSSIVE; 257 | return SMP_MELODIC; // by default 258 | } 259 | 260 | 261 | void SamplerEngine::applyRange(ini_range_t& range) { 262 | _ranges.push_back(range); 263 | for (int i=range.first; i<=range.last; i++) { 264 | _keyboard[i].noteoff = range.noteoff; 265 | _keyboard[i].tuning = range.speed; 266 | _keyboard[i].limit_same = range.limit_same; 267 | _keyboard[i].attack_time = range.attack_time; 268 | _keyboard[i].decay_time = range.decay_time; 269 | _keyboard[i].sustain_level = range.sustain_level; 270 | _keyboard[i].release_time = range.release_time; 271 | } 272 | DEBF("INI: adding range for %s\r\n", range.instr.c_str()); 273 | } 274 | 275 | 276 | bool SamplerEngine::parseFilenameTemplate(str256_t& line) { 277 | DEBF("Parse filename template:[[[%s]]]\r\n", line.c_str()); 278 | int a, b, i; 279 | str256_t str; 280 | template_item_t item; 281 | i = 0; 282 | while (true) { 283 | a = line.indexOf("<",i); 284 | b = line.indexOf(">",i); 285 | if (a < 0 || b < 0) break; 286 | if (b < a) return false; 287 | if (a > i && b > a) { // at the start of separator 288 | str = line.substring(i, a ); 289 | DEBF ("Parsing filename template: String separator: [%s]\r\n", str.c_str()); 290 | item.item_type = P_SEPARATOR; 291 | item.item_str = str; 292 | _template.push_back(item); 293 | i = a; 294 | } 295 | if (a == i) { // at the start of token 296 | str = line.substring(a + 1, b ); 297 | DEBF ("Parsing filename template: Token: [%s]\r\n", str.c_str()); 298 | str = str.substring(0, 4); 299 | if (str == "NUMB" ) { 300 | item.item_type = P_NUMBER; 301 | item.item_str = ""; 302 | _template.push_back(item); 303 | } 304 | else if (str == "NAME" ) { 305 | item.item_type = P_NAME; 306 | item.item_str = ""; 307 | _template.push_back(item); 308 | } 309 | else if (str == "MIDI" ) { 310 | item.item_type = P_MIDINOTE; 311 | item.item_str = ""; 312 | _template.push_back(item); 313 | } 314 | else if (str == "OCTA" ) { 315 | item.item_type = P_OCTAVE; 316 | item.item_str = ""; 317 | _template.push_back(item); 318 | } 319 | else if (str == "VELO" ) { 320 | item.item_type = P_VELO; 321 | item.item_str = ""; 322 | _template.push_back(item); 323 | } 324 | else if (str == "INST" ) { 325 | item.item_type = P_INSTRUMENT; 326 | item.item_str = ""; 327 | _template.push_back(item); 328 | } 329 | } 330 | i = b + 1; 331 | if (i >= line.length()) return true; 332 | } 333 | return true; 334 | } 335 | 336 | 337 | void SamplerEngine::processNameParser(entry_t* entry) { 338 | int pos = 0; 339 | int i = 0; 340 | int range_i = 0; 341 | int len = 0; 342 | int oct = -2; 343 | int velo = 0; 344 | int note_num = -1; 345 | int midi_note_num = -1; 346 | std::vector rng_i; // affected indices 347 | int match_weight = 0; 348 | str20_t instr = ""; 349 | str20_t s; 350 | sample_t smp; 351 | fname_t fname = entry->name; 352 | fname.toUpperCase(); 353 | if (fname.endsWith(".WAV")) { 354 | //DEBF("Parsing name: <%s>\r\n", fname.c_str()); 355 | } else { 356 | DEBF("Skipping name: <%s>\r\n", fname.c_str()); 357 | return; 358 | } 359 | for (auto tpl: _template) { 360 | switch ((int)tpl.item_type) { 361 | case P_NAME: // note name 362 | match_weight = 0; 363 | for (int j = 0; j < 2 ; j++) { 364 | for (int k = 0 ; k < 12; k++) { 365 | str8_t a = notes[j][k]; 366 | len = a.length(); 367 | str8_t b = fname.substring(pos, pos + len); 368 | // DEBF("compare: <%s> <%s>\r\n", a.c_str() , b.c_str()); 369 | if (a.equalsIgnoreCase(b) && len > match_weight) { 370 | match_weight = len; 371 | note_num = k; 372 | } 373 | } 374 | } 375 | if ( note_num >= 0 ) { 376 | pos += match_weight; 377 | } 378 | else { 379 | DEBF("Failed to determine note name in <%s>\r\n", fname.c_str()); 380 | } 381 | // DEBF("Note: %s\r\n", notes[0][note_num].c_str()); 382 | break; 383 | case P_NUMBER: 384 | s = ""; 385 | while (true) { 386 | if (fname.charAt(pos) >='0' && fname.charAt(pos) <='9' ) { 387 | s += fname.charAt(pos); 388 | pos++; 389 | } else { 390 | // DEBF("Number: %s\r\n", s.c_str()); 391 | break; 392 | } 393 | } 394 | break; 395 | case P_MIDINOTE: 396 | s = ""; 397 | while (true) { 398 | if (fname.charAt(pos) >='0' && fname.charAt(pos) <='9' ) { 399 | s += fname.charAt(pos); 400 | pos++; 401 | } else { 402 | break; 403 | } 404 | } 405 | //DEBF("Midi Note Number: %s\r\n", s.c_str()); 406 | if (s>"") midi_note_num = s.toInt(); 407 | break; 408 | case P_INSTRUMENT: { 409 | // DEBUG("Filename: Parsing P_INSTRUMENT"); 410 | i = 0; 411 | match_weight = 0; 412 | for (auto rng: _ranges) { // first pass: find max match_weight 413 | len = rng.instr.length(); 414 | instr = fname.substring(pos, pos + len); 415 | // DEBF("INI: TEST instr: [%s] \r\n", instr.c_str()); 416 | if (len>match_weight && instr.equalsIgnoreCase(rng.instr)) { 417 | match_weight = len; 418 | } 419 | } 420 | i = 0; 421 | rng_i.clear(); 422 | for (auto rng: _ranges) { // second pass: we can have more than 1 match 423 | len = rng.instr.length(); 424 | if (len == match_weight) { 425 | instr = fname.substring(pos, pos + len); 426 | if (instr.equalsIgnoreCase(rng.instr)) { 427 | // range matches by instrument 428 | rng_i.push_back(i); 429 | DEBF("INI: Matched instr: %s, %d \r\n", instr.c_str(), i); 430 | } 431 | } 432 | i++; 433 | } 434 | if (match_weight>0) { 435 | pos += match_weight; 436 | } else { 437 | DEBF("INI: Couldn't match instrument name [%s] in %s\r\n", instr.c_str(), fname.c_str()); 438 | } 439 | } 440 | case P_OCTAVE: 441 | s = fname.substring(pos, pos+1); 442 | oct = s.toInt(); 443 | // DEBF("Octave: %d\r\n" , oct); 444 | pos++; 445 | break; 446 | case P_VELO: 447 | // DEBUG("Filename: Parsing P_VELO"); 448 | i = 0; 449 | match_weight = 0; 450 | for(auto var: _veloVars) { 451 | len = var.length(); 452 | s = fname.substring(pos, pos + len); 453 | if (len>match_weight && s.equalsIgnoreCase(var)) { 454 | match_weight = len; 455 | velo = i; 456 | } 457 | i++; 458 | } 459 | // DEBF("Velocity: %d\r\n", velo); 460 | pos += match_weight; 461 | break; 462 | case P_SEPARATOR: 463 | s = tpl.item_str; 464 | len = s.length(); 465 | // DEBF("Skipping separator %s\r\n", s.c_str()); 466 | pos += len; 467 | break; 468 | default: 469 | //impossible: 470 | break; 471 | } 472 | } 473 | // if we have note name in filename 474 | if (note_num>=0 && oct>=-1) { 475 | midi_note_num = note_num + (oct+1)*12; // midi note number 476 | } 477 | if (midi_note_num>=0) { 478 | if ( velo >= 0 ) { 479 | sample_t smp; 480 | smp.orig_velo_layer = velo; 481 | smp.sectors = entry->sectors; 482 | smp.size = entry->size; 483 | smp.native_freq = true; 484 | // smp.name = (entry->name); 485 | parseWavHeader(entry, smp); 486 | _sampleMap[midi_note_num][velo] = smp; 487 | } 488 | } 489 | 490 | // if we have [ranges] or [notes] in ini 491 | if ( !rng_i.empty() ) { 492 | if (velo < 0) velo = 0; 493 | for (auto ix: rng_i) { 494 | for (int midi_note = _ranges[ix].first; midi_note<=_ranges[ix].last; midi_note++) { 495 | sample_t smp; 496 | smp.orig_velo_layer = velo; 497 | smp.sectors = entry->sectors; 498 | smp.size = entry->size; 499 | smp.native_freq = true; 500 | // smp.name = (entry->name); 501 | parseWavHeader(entry, smp); 502 | _sampleMap[midi_note][velo] = smp; 503 | } 504 | } 505 | } 506 | } 507 | 508 | 509 | void SamplerEngine::parseWavHeader(entry_t* wav_entry, sample_t& smp){ 510 | char* buf = reinterpret_cast(_Card->readFirstSector(wav_entry)); // massive long headers won't pass here ( 511 | wav_header_t* wav = reinterpret_cast(buf); 512 | smp.sample_rate = wav->sampleRate; 513 | smp.bit_depth = wav->bitsPerSample; 514 | smp.channels = wav->numberOfChannels; 515 | smp.speed = (float)(wav->sampleRate) * DIV_SAMPLE_RATE; 516 | int res = -1; 517 | for (int i = 0 ; i < BYTES_PER_SECTOR-4; i++) { 518 | if (buf[i]=='d' && buf[i+1]=='a' && buf[i+2]=='t' && buf[i+3]=='a') { 519 | res = i; 520 | break; 521 | } 522 | } 523 | if (res >= 0 ) { 524 | smp.byte_offset = res + 8; 525 | smp.data_size = *(reinterpret_cast(&buf[res+4])); 526 | } 527 | } 528 | 529 | 530 | // fill gaps 531 | void SamplerEngine::finalizeMapping() { 532 | int x, y, dx, dy, xc, yc, s; 533 | int max_v = 0; 534 | int n = 1 + 2 * MAX_DISTANCE_STRETCH; 535 | sample_t smp; 536 | _veloLayers = 1; 537 | // first pass: determine the number of velocity layers used 538 | for (int i = 0; i < MAX_VELOCITY_LAYERS; i++) { 539 | for (int j = 0; j < 128; j++) { 540 | if (_sampleMap[j][i].bit_depth > 0) { 541 | _veloLayers = i + 1; 542 | break; 543 | } 544 | } 545 | } 546 | _divVeloLayers = 1.0f / (float)_veloLayers; 547 | switch((int)_type) { 548 | case SMP_PERCUSSIVE: 549 | for (int i = 0; i < 128; i++) { 550 | smp.bit_depth = 0; 551 | for (int j = 0; j < _veloLayers; j++) { 552 | if (_sampleMap[i][j].bit_depth > 0) { 553 | smp = _sampleMap[i][j]; 554 | } else { 555 | if (smp.bit_depth > 0) { 556 | _sampleMap[i][j] = smp; 557 | } 558 | } 559 | } 560 | for (int j = _veloLayers-1; j >=0; j--) { 561 | if (_sampleMap[i][j].bit_depth > 0) { 562 | smp = _sampleMap[i][j]; 563 | } else { 564 | if (smp.bit_depth > 0) { 565 | _sampleMap[i][j] = smp; 566 | } 567 | } 568 | } 569 | } 570 | break; 571 | case SMP_MELODIC: 572 | default: { 573 | // second pass 574 | for (int i = 0; i < _veloLayers; i++) { 575 | // going up 576 | for (int j = 0; j < 128; j++) { 577 | if (_sampleMap[j][i].speed == 0.0f) { 578 | // search in _sampleMap by spiral 579 | s = -1; 580 | x = j; 581 | y = unMapVelo(i) ; 582 | for (int k = 1; k <= n; k++) { 583 | dx = s; 584 | dy = 0; 585 | for (int m = 0 ; m < k; m++) { 586 | x += dx; 587 | y += dy; 588 | xc = constrain(x, 0, 127); 589 | yc = mapVelo(constrain(y, 0, 127)); 590 | if (_sampleMap[xc][yc].speed > 0.0f ) { 591 | _sampleMap[j][i] = _sampleMap[xc][yc]; 592 | _sampleMap[j][i].speed = _sampleMap[xc][yc].speed * _keyboard[j].freq / _keyboard[x].freq; 593 | _sampleMap[j][i].native_freq = false; 594 | break; 595 | } 596 | } 597 | s = -s; 598 | dx = 0; 599 | dy = s; 600 | for (int m = 0 ; m < k; m++) { 601 | x += dx; 602 | y += dy; 603 | xc = constrain(x, 0, 127); 604 | yc = mapVelo(constrain(y, 0, 127)); 605 | if (_sampleMap[xc][yc].native_freq) { 606 | _sampleMap[j][i] = _sampleMap[xc][yc]; 607 | _sampleMap[j][i].speed = _sampleMap[xc][yc].speed * _keyboard[j].freq / _keyboard[x].freq; 608 | _sampleMap[j][i].native_freq = false; 609 | break; 610 | } 611 | } 612 | } 613 | } 614 | } 615 | } 616 | } 617 | } 618 | // propagate params to individual samples 619 | for (int i = 0; i < _veloLayers; i++) { 620 | for (int j = 0 ; j < 128; j++ ) { 621 | _sampleMap[j][i].speed *= _keyboard[j].tuning; 622 | _sampleMap[j][i].amp *= _amp; 623 | /* 624 | _sampleMap[j][i].attack_time = _keyboard[j].attack_time; 625 | _sampleMap[j][i].decay_time = _keyboard[j].decay_time; 626 | _sampleMap[j][i].sustain_level = _keyboard[j].sustain_level; 627 | _sampleMap[j][i].release_time = _keyboard[j].release_time; 628 | */ 629 | } 630 | } 631 | } 632 | 633 | 634 | uint8_t SamplerEngine::midiNoteByName(str8_t noteNameOct) { 635 | int match_weight = 0; 636 | int len, note_num = -1; 637 | int oct = -3; 638 | for (int j = 0; j < 2 ; j++) { 639 | for (int k = 0 ; k < 12; k++) { 640 | str8_t a = notes[j][k]; 641 | len = a.length(); 642 | str8_t b = noteNameOct.substring(0,len); 643 | if (a.equalsIgnoreCase(b) && len > match_weight) { 644 | match_weight = len; 645 | note_num = k; 646 | } 647 | } 648 | } 649 | str8_t b = noteNameOct.substring(match_weight, match_weight+2); 650 | // DEBF("INI: midiNoteByName string: [%s] note: [%d] oct: [%s] \r\n", noteNameOct.c_str(), note_num, b.c_str()); 651 | oct = b.toInt(); 652 | if (oct>-2) { 653 | note_num+=(oct+1)*12; 654 | } 655 | // DEBUG(note_num); 656 | return note_num; 657 | } 658 | 659 | 660 | void SamplerEngine::printMapping() { 661 | for (int i = 0; i < 128; i++) { 662 | DEBF("%s%d\t", notes[1][i%12].c_str(), i/12 -1 ); 663 | } 664 | DEBUG(","); 665 | for (int i = 0; i < _veloLayers; i++) { 666 | for (int j = 0 ; j<128; j++ ) { 667 | if (!_sampleMap[j][i].sectors.empty()) { 668 | DEBF("%d %3.2f\t",_sampleMap[j][i].native_freq, _sampleMap[j][i].speed ); 669 | // DEBF("%s\t", _sampleMap[j][i].name.c_str() ); 670 | } else { 671 | DEBF( "%d\t", 0 ); 672 | } 673 | } 674 | DEBUG("."); 675 | } 676 | 677 | for (int j = 0; j < ( ( MAX_NOTES_PER_GROUP - 1 ) * MAX_GROUPS_CROSSES ); j++) { 678 | for (int i = 0 ; i < 128; i++) { 679 | DEBF("%d\t", _groups[i][j] ); 680 | } 681 | DEBUG("."); 682 | } 683 | 684 | 685 | } 686 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/sdmmc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * Simplified READ-ONLY FAT32 class for fast accessing and reading of WAV files. 5 | * Cluster chains are pre-cached for the current directory files, and stored in memory as 6 | * a collection of linear sector chains boundaries. Ideally, when no fragmentation appears, 7 | * only a single pair of sectors (beginning/end) per file needed. 8 | */ 9 | /* 10 | Just for your information 11 | Of what I have tested with some random 16GB card: 12 | 13 | sectors per read | reading speed, MB/s 14 | ------------------+--------------------- 15 | 1 | 0.90 16 | 2 | 1.57 17 | 4 | 2.82 18 | 8 | 5.00 19 | 16 | 7.79 20 | 32 | 11.12 21 | 64 | 13.85 22 | 128 | 15.75 23 | 24 | * SPI access is SIGNIFICANTLY SLOWER than SD_MMC (like 1/3 of SD_MMC speed), 25 | * so 4-bit MMC is the only possible variant for playing back samples from SD_MMC. 26 | * Arduino ESP32 core libs are able of using 4-bit bus, and it's a good thing. 27 | * Thus D1 and D2 are mandatory to have a good speed 28 | * Be aware that the vast majority of micro-SD breakout boards are produced 29 | * with D1 and D2 NOT CONNECTED 30 | * 31 | * pin 1 - D2 | Micro SD card | 32 | * pin 2 - D3 | / 33 | * pin 3 - CMD | |__ 34 | * pin 4 - VDD (3.3V) | | 35 | * pin 5 - CLK | 8 7 6 5 4 3 2 1 / 36 | * pin 6 - VSS (GND) | - - - - - - - - / 37 | * pin 7 - D0 | - - - - - - - - | 38 | * pin 8 - D1 |_________________| 39 | * ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ 40 | * г=======- ¦ ¦ ¦ ¦ ¦ ¦ L=========¬ 41 | * ¦ ¦ ¦ ¦ ¦ ¦ L======¬ ¦ 42 | * ¦ г=====- ¦ ¦ ¦ L=====¬ ¦ ¦ 43 | * Connections for ¦ ¦ г===¦=¦=¦===¬ ¦ ¦ ¦ 44 | * full-sized ¦ ¦ ¦ г=- ¦ ¦ ¦ ¦ ¦ 45 | * SD card ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ 46 | * ESP32-S3 DevKit | 21 47 GND 39 3V3 GND 40 41 42 | 47 | * ESP32-S3-USB-OTG | 38 37 GND 36 3V3 GND 35 34 33 | 48 | * ESP32 | 4 2 GND 14 3V3 GND 15 13 12 | 49 | * Pin name | D1 D0 VSS CLK VDD VSS CMD D3 D2 | 50 | * SD pin number | 8 7 6 5 4 3 2 1 9 / 51 | * | =/ 52 | * |__=___=___=___=___=___=___=___=___/ 53 | * WARNING: ALL data pins must be pulled up to 3.3V with an external 10k Ohm resistor! 54 | * Note to ESP32 pin 2 (D0): Add a 1K Ohm pull-up resistor to 3.3V after flashing 55 | * 56 | * SD Card | ESP32 57 | * D2 12 58 | * D3 13 59 | * CMD 15 60 | * VSS GND 61 | * VDD 3.3V 62 | * CLK 14 63 | * VSS GND 64 | * D0 2 (add 1K pull up after flashing) 65 | * D1 4 66 | * 67 | * For more info see file README.md in this library or on URL: 68 | * https://github.com/espressif/arduino-esp32/tree/master/libraries/SD_MMC 69 | * 70 | * 71 | */ 72 | #include "sdmmc_types.h" 73 | //#define USE_MUTEX 74 | 75 | 76 | /* 77 | * Simplified read-only SD_MMC FAT32 (for wav sampler) 78 | */ 79 | 80 | class SDMMC_FAT32 { 81 | public: 82 | SDMMC_FAT32() {} ; 83 | ~SDMMC_FAT32() {} ; 84 | 85 | esp_err_t ret; 86 | sdmmc_card_t card; 87 | 88 | void begin(); 89 | void end(); 90 | void testReadSpeed(uint32_t sectorsPerRead, uint32_t totalMB); 91 | void setCurrentDir(fpath_t pathToDir); 92 | void printCurrentDir(); 93 | void rewindDir(); 94 | entry_t* findEntry(const fpath_t& fname); 95 | entry_t* nextEntry(); 96 | entry_t* buildNextEntry(); 97 | entry_t* getCurrentEntryP() {return &_currentEntry;}; 98 | entry_t getCurrentEntry() {return _currentEntry;}; 99 | void setCurrentEntry(entry_t ent) {_currentEntry = ent;}; 100 | 101 | esp_err_t read_block(void *dst, uint32_t start_sector, uint32_t sector_count); 102 | esp_err_t read_sector(uint32_t sector); // reads one sector into uint8_t sector_buf[] 103 | esp_err_t cache_fat( uint32_t sector ); // reads FAT_CACHE_SECTORS into uint8_t fat_cache.uint8[] sets first and last sectors read 104 | esp_err_t cache_dir( uint32_t sector ); // reads DIR_CACHE_SECTORS into uint8_t dir_cache[] sets first and last sectors read 105 | esp_err_t write_block(const void *source, uint32_t block, uint32_t size); 106 | uint8_t* readFirstSector(const fname_t& fname); // 107 | uint8_t* readFirstSector(entry_t* entry); 108 | uint8_t* readSector(uint32_t sector); // reads one sector into uint8_t sector_buf[] returns pointer to it 109 | uint8_t* readNextSector(uint32_t curSector); 110 | uint32_t getNextSector(uint32_t curSector = 0); 111 | 112 | point_t getCurrentPoint() ; 113 | void setCurrentPoint(point_t& p); 114 | 115 | uint8_t getPartitionId() {return _partitionId;} 116 | uint32_t getFirstSector() {return _firstSector;} 117 | uint32_t getFsType() {return _fsType;} 118 | uint32_t getFirstDataSector() {return _firstDataSector;} 119 | uint8_t getNumFats() {return _numFats;} 120 | uint32_t getReservedSectors() {return _reservedSectors;} 121 | uint32_t getSectorsPerFat() {return _sectorsPerFat;} 122 | uint32_t getSectorsPerCluster(){return _sectorsPerCluster;} 123 | uint32_t getBytesPerSector() {return _bytesPerSector;} 124 | uint32_t getBytesPerCluster() {return _bytesPerCluster;} 125 | 126 | esp_err_t get_mbr(); 127 | esp_err_t get_bpb(); 128 | 129 | private: 130 | struct __attribute__((packed)) { 131 | uint8_t code[440]; 132 | uint32_t diskSerial; // This is optional 133 | uint16_t reserved; // Usually 0x0000 134 | part_t partitionData[4]; 135 | uint8_t bootSign[2]; // 0x55 0xAA for bootable 136 | } mbrStruct; 137 | 138 | struct __attribute__((packed)) { 139 | uint8_t jumpBoot[3]; 140 | uint8_t OEMName[8]; 141 | uint16_t bytesPerSector; 142 | uint8_t sectorPerCluster; 143 | uint16_t reservedSectorCount; 144 | uint8_t numberofFATs; 145 | uint16_t rootEntryCount; 146 | uint16_t totalSectors_F16; // [19, 20] 147 | uint8_t mediaType; // [21] 148 | uint16_t FATsize_F16; // [22, 23] 149 | uint16_t sectorsPerTrack; // [24, 25] 150 | uint16_t numberofHeads; // [26, 27] 151 | uint32_t hiddenSectors; // [28, 31] 152 | uint32_t totalSectors; // [32, 35] 153 | uint32_t sectorsPerFat; // [36, 39] 154 | uint16_t extFlags; // [40, 41] 155 | uint16_t FSversion; // [42, 43] 156 | uint32_t rootCluster; 157 | uint16_t FSinfo; // [48, 49] 158 | uint16_t BackupBootSector; // [50, 51] 159 | uint8_t reserved[12]; 160 | uint8_t driveNumber; 161 | uint8_t reserved1; 162 | uint8_t bootSignature; 163 | uint32_t volumeID; 164 | uint8_t volumeLabel[11]; 165 | uint8_t fileSystemType[8]; 166 | uint8_t bootData[420]; 167 | uint16_t bootEndSignature; 168 | } bpbStruct; 169 | 170 | uint8_t sector_buf[BYTES_PER_SECTOR]; // read_sector(sector) uses this buf 171 | volatile uint32_t _sectorInBuf = 0; 172 | volatile int _dirent_num = 0; 173 | 174 | uint8_t dir_cache[BYTES_PER_SECTOR * DIR_CACHE_SECTORS]; // cache_dir() 175 | 176 | union { 177 | uint32_t uint32[BYTES_PER_SECTOR * FAT_CACHE_SECTORS / 4]; 178 | uint8_t uint8[BYTES_PER_SECTOR * FAT_CACHE_SECTORS]; 179 | } fat_cache; 180 | uint8_t _partitionId; 181 | uint32_t _firstSector; 182 | uint32_t _fsType; 183 | uint32_t _firstDataSector; 184 | uint8_t _numFats; 185 | uint32_t _reservedSectors; 186 | uint32_t _sectorsPerFat; 187 | uint32_t _bytesPerSector; 188 | uint32_t _sectorsPerCluster; 189 | uint32_t _rootCluster; 190 | uint32_t _sectorsTotal; 191 | volatile uint32_t _firstCachedFatSector = 0; 192 | volatile uint32_t _lastCachedFatSector = 0; 193 | volatile uint32_t _firstCachedDirSector = 0; 194 | volatile uint32_t _lastCachedDirSector = 0; 195 | volatile uint32_t _currentSector; 196 | volatile uint32_t _currentCluster; 197 | fpath_t _currentDir; 198 | entry_t _currentEntry; 199 | uint32_t _bytesPerCluster; 200 | volatile uint32_t _startSector; 201 | volatile uint32_t _startCluster; 202 | /* 203 | * Cluster and sector calculations 204 | */ 205 | inline uint32_t fatSectorByCluster(uint32_t cluster) {return _firstSector + cluster * 4 / _bytesPerSector + _reservedSectors; } 206 | inline uint32_t fatClusterBySector(uint32_t sector) {return (sector - _reservedSectors - _firstSector) * _bytesPerSector / 4 ; } 207 | inline uint32_t fatByteOffset(uint32_t cluster) {return cluster * 4 % _bytesPerSector; } 208 | inline uint32_t firstSectorOfCluster(uint32_t cluster) {return _firstSector + (cluster - _rootCluster) * _sectorsPerCluster + _firstDataSector ; } 209 | inline uint32_t lastSectorOfCluster(uint32_t cluster) {return firstSectorOfCluster(cluster + 1) - 1 ; } 210 | inline uint32_t clusterBySector(uint32_t sector) {return (sector - _firstSector - _firstDataSector) / _sectorsPerCluster + _rootCluster ; } 211 | inline uint32_t fat32_cluster_id(uint16_t high, uint16_t low) {return high << 16 | low; } 212 | inline uint32_t fat32_cluster_id(sfn_dir_t *d) {return d->hi_start << 16 | d->lo_start; } 213 | uint32_t getNextCluster(uint32_t cluster); 214 | uint32_t findEntryCluster(const fpath_t& search_name) ; // returns first sector of a file 215 | uint32_t findEntrySector(const fpath_t& search_name) {return firstSectorOfCluster(findEntryCluster(search_name));} 216 | 217 | /********************************************************************** 218 | * fat32 helpers. 219 | */ 220 | 221 | // convert name from 8.3 222 | void dirent_name(sfn_dir_t *d, char *name); 223 | 224 | inline int is_dir(sfn_dir_t *d) { return d->attr == FAT32_DIR; } 225 | inline int dirent_is_lfn(sfn_dir_t *d) { return (d->attr == FAT32_LONG_FILE_NAME); } 226 | 227 | inline int is_attr(uint32_t x, uint32_t flag) { 228 | if(x == FAT32_LONG_FILE_NAME) 229 | return x == flag; 230 | return (x & flag) == flag; 231 | } 232 | void dirent_print_helper(sfn_dir_t *d); 233 | int dirent_free(sfn_dir_t *d); 234 | int dirent_is_deleted_lfn(sfn_dir_t *d); 235 | int fat_entry_type(uint32_t x); 236 | const char* fat_entry_type_str(uint32_t x); 237 | 238 | const char* to_8dot3(const char*); 239 | const char* add_nul(const char*); 240 | 241 | // sfn_dir_t* dir_filename(char*, sfn_dir_t*, sfn_dir_t*); 242 | 243 | int dir_lookup(const char *name, sfn_dir_t *dirs, unsigned n); 244 | uint8_t lfn_checksum(const char*); 245 | int lfn_is_deleted(uint8_t); 246 | void lfn_print_ent(lfn_dir_t*, uint8_t); 247 | int lfn_terminator(uint8_t*); 248 | void lfn_print(lfn_dir_t*, int, uint8_t, int); 249 | fname_t unicode2ascii(uint16_t*, int len); 250 | #ifdef USE_MUTEX 251 | SemaphoreHandle_t mutex; 252 | #endif 253 | }; 254 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/sdmmc.ino: -------------------------------------------------------------------------------- 1 | #include "esp_err.h" 2 | #include "driver/sdmmc_host.h" 3 | #include "sdmmc_cmd.h" 4 | 5 | 6 | // utility functions for other files to import 7 | esp_err_t SDMMC_FAT32::write_block(const void *source, uint32_t block, uint32_t size) 8 | { 9 | #ifdef USE_MUTEX 10 | xSemaphoreTake(mutex, portMAX_DELAY); 11 | #endif 12 | ret = sdmmc_write_sectors(&card, (const void *)source, (uint32_t)block, (uint32_t)size); 13 | #ifdef USE_MUTEX 14 | xSemaphoreGive(mutex); 15 | #endif 16 | return(ret); 17 | } 18 | 19 | esp_err_t SDMMC_FAT32::read_block(void *dst, uint32_t start_sector, uint32_t sector_count) 20 | { 21 | #ifdef USE_MUTEX 22 | xSemaphoreTake(mutex, portMAX_DELAY); 23 | #endif 24 | ret = sdmmc_read_sectors(&card, (void *)dst, (uint32_t)start_sector, (uint32_t)sector_count); 25 | #ifdef USE_MUTEX 26 | xSemaphoreGive(mutex); 27 | #endif 28 | return(ret); 29 | } 30 | 31 | esp_err_t SDMMC_FAT32::read_sector( uint32_t sector ) 32 | { 33 | #ifdef USE_MUTEX 34 | xSemaphoreTake(mutex, portMAX_DELAY); 35 | #endif 36 | ret = sdmmc_read_sectors(&card, (void *)sector_buf, (uint32_t)sector, 1UL ); 37 | _sectorInBuf = sector; 38 | #ifdef USE_MUTEX 39 | xSemaphoreGive(mutex); 40 | #endif 41 | return(ret); 42 | } 43 | 44 | esp_err_t SDMMC_FAT32::cache_fat( uint32_t sector ) 45 | { 46 | #ifdef USE_MUTEX 47 | xSemaphoreTake(mutex, portMAX_DELAY); 48 | #endif 49 | ret = sdmmc_read_sectors(&card, (void *)(fat_cache.uint8), (uint32_t)sector, FAT_CACHE_SECTORS ); 50 | _firstCachedFatSector = sector; 51 | _lastCachedFatSector = sector + FAT_CACHE_SECTORS - 1; 52 | #ifdef USE_MUTEX 53 | xSemaphoreGive(mutex); 54 | #endif 55 | return(ret); 56 | } 57 | 58 | esp_err_t SDMMC_FAT32::cache_dir( uint32_t sector ) 59 | { 60 | #ifdef USE_MUTEX 61 | xSemaphoreTake(mutex, portMAX_DELAY); 62 | #endif 63 | ret = sdmmc_read_sectors(&card, (void *)(dir_cache), (uint32_t)sector, DIR_CACHE_SECTORS ); 64 | _firstCachedDirSector = sector; 65 | _lastCachedDirSector = sector + DIR_CACHE_SECTORS - 1; 66 | #ifdef USE_MUTEX 67 | xSemaphoreGive(mutex); 68 | #endif 69 | return(ret); 70 | } 71 | 72 | // tool to test hardware with different buffer sizes 73 | void SDMMC_FAT32::testReadSpeed(uint32_t sectorsPerRead, uint32_t totalMB){ 74 | uint32_t readCount = totalMB * 1024 * 1024 / BYTES_PER_SECTOR / sectorsPerRead; 75 | //auto rcv = static_cast(malloc(BYTES_PER_SECTOR*READ_BUF_SECTORS*sizeof(uint8_t))); 76 | uint8_t rcv[BYTES_PER_SECTOR*READ_BUF_SECTORS]; 77 | pinMode(18,INPUT); 78 | randomSeed(analogRead(18)); 79 | volatile size_t ms1, ms2; 80 | ms1 = micros(); 81 | for (uint32_t i = 0; i>>Reading MBR done"); 184 | ret = ESP_OK; 185 | break; 186 | } else { 187 | ret = ESP_ERR_NOT_SUPPORTED; 188 | } 189 | } 190 | } 191 | return ret; 192 | } 193 | 194 | esp_err_t SDMMC_FAT32::get_bpb() { 195 | ret = read_block(&bpbStruct, mbrStruct.partitionData[0].firstSector, 1); 196 | if (ret == ESP_OK) { 197 | _sectorsPerFat = bpbStruct.sectorsPerFat; 198 | DEBF("_sectorsPerFat %d\r\n", _sectorsPerFat); 199 | _bytesPerSector = bpbStruct.bytesPerSector; 200 | DEBF("_bytesPerSector %d\r\n", _bytesPerSector); 201 | _numFats = bpbStruct.numberofFATs; 202 | DEBF("_numFats %d\r\n", _numFats); 203 | _reservedSectors = bpbStruct.reservedSectorCount; 204 | DEBF("_reservedSectors %d\r\n", _reservedSectors); 205 | _rootCluster = bpbStruct.rootCluster; 206 | DEBF("_rootCluster %d\r\n", _rootCluster); 207 | _firstDataSector = _sectorsPerFat * _numFats + _reservedSectors ; 208 | DEBF("_firstDataSector %d\r\n", _firstDataSector); 209 | _sectorsPerCluster = bpbStruct.sectorPerCluster; 210 | DEBF("_sectorsPerCluster %d\r\n", _sectorsPerCluster); 211 | _bytesPerCluster = _sectorsPerCluster * _bytesPerSector ; 212 | DEBF("_bytesPerCluster %d\r\n", _bytesPerCluster); 213 | DEBUG(">>>Reading BPB done."); 214 | } 215 | return ret; 216 | } 217 | 218 | uint32_t SDMMC_FAT32::getNextSector(uint32_t curSector) { 219 | if (curSector == 0) { 220 | curSector = _sectorInBuf; 221 | DEBUG("curSector = 0"); 222 | } 223 | uint32_t curCluster = clusterBySector(curSector); 224 | uint32_t nextSector = curSector + 1; 225 | uint32_t nextCluster = clusterBySector(nextSector); 226 | if (curCluster != nextCluster) { 227 | nextCluster = getNextCluster(curCluster); 228 | if (fat_entry_type(nextCluster)==LAST_CLUSTER) { 229 | return 0; 230 | } 231 | nextSector = firstSectorOfCluster(nextCluster); 232 | } 233 | return nextSector; 234 | } 235 | 236 | uint32_t SDMMC_FAT32::getNextCluster(uint32_t cluster) { 237 | uint32_t nextCluster; 238 | uint32_t neededSector = fatSectorByCluster(cluster); 239 | uint32_t recOffset = fatByteOffset(cluster) / 4; 240 | if (neededSector < _firstCachedFatSector || neededSector > _lastCachedFatSector) { 241 | cache_fat(neededSector); 242 | } 243 | nextCluster = fat_cache.uint32[(neededSector - _firstCachedFatSector) * (BYTES_PER_SECTOR/4) + recOffset]; 244 | return nextCluster; 245 | } 246 | 247 | 248 | entry_t* SDMMC_FAT32::findEntry(const fpath_t& search_path) { 249 | entry_t* entry; 250 | fpath_t name, testname; 251 | name = search_path; 252 | name.toUpperCase(); 253 | name.replace("\\",""); 254 | DEBF("Searching in <%s> for [%s] entry:\r\n", _currentDir.c_str(), search_path.c_str()); 255 | rewindDir(); 256 | entry = nextEntry(); 257 | while (!entry->is_end) { 258 | testname = entry->name; 259 | testname.toUpperCase(); 260 | //DEBF("compare <%s> <%s>\r\n", name.c_str(), testname.c_str()); 261 | if (testname == name) { 262 | return entry; 263 | break; 264 | } 265 | entry = nextEntry(); 266 | } 267 | entry->is_dir = -1; 268 | entry->is_end = 1; 269 | entry->name = ""; 270 | entry->size = 0; 271 | return entry; 272 | } 273 | 274 | uint32_t SDMMC_FAT32::findEntryCluster(const fpath_t& search_path) { 275 | uint32_t first_cluster = 0; 276 | entry_t* entry; 277 | fpath_t name, testname; 278 | name = search_path; 279 | name.toUpperCase(); 280 | name.replace("\\",""); 281 | DEBF("Searching in <%s> for [%s] entry cluster:\r\n", _currentDir.c_str(), search_path.c_str()); 282 | rewindDir(); 283 | entry = nextEntry(); 284 | while (!entry->is_end) { 285 | testname = entry->name; 286 | testname.toUpperCase(); 287 | //DEBF("compare <%s> <%s>\r\n", name.c_str(), testname.c_str()); 288 | if (testname == name) { 289 | first_cluster = clusterBySector(entry->sectors[0].first); 290 | break; 291 | } 292 | entry = nextEntry(); 293 | } 294 | return first_cluster; 295 | } 296 | 297 | uint8_t* SDMMC_FAT32::readSector(uint32_t sec) { 298 | read_sector(sec); 299 | return §or_buf[0]; 300 | } 301 | 302 | uint8_t* SDMMC_FAT32::readFirstSector(const fname_t& fname) { 303 | uint32_t sec = findEntrySector(fname); 304 | read_sector(sec); 305 | return §or_buf[0]; 306 | } 307 | 308 | uint8_t* SDMMC_FAT32::readFirstSector(entry_t* entry) { 309 | uint32_t sec = entry->sectors[0].first; 310 | read_sector(sec); 311 | return §or_buf[0]; 312 | } 313 | 314 | uint8_t* SDMMC_FAT32::readNextSector(uint32_t curSector) { 315 | uint32_t sec = getNextSector(curSector); 316 | if (sec > 0) { 317 | read_sector(sec); 318 | return §or_buf[0]; 319 | } else { 320 | return (uint8_t*)nullptr; 321 | } 322 | } 323 | 324 | void SDMMC_FAT32::setCurrentDir(fpath_t dir_path){ 325 | // not fully implemented, for now it accepts "" or "/" as a root folder, or some name without slashes as a directory in a root, yes only 1 level 326 | if (dir_path == "" || dir_path == "/" || dir_path == "\\") { 327 | // root 328 | _currentDir = "/"; 329 | _startCluster = _rootCluster; 330 | } else { 331 | dir_path.replace("\\","/"); // unix style 332 | dir_path.replace("//","/"); // in case of some confuses 333 | // now we only get the last part of the path supplied 334 | if (dir_path.endsWith("/")) { 335 | dir_path.remove(dir_path.length() - 1, 1); 336 | } 337 | int i = dir_path.lastIndexOf("/"); 338 | if (i >= 0) { 339 | dir_path.remove(0, i + 1); 340 | } 341 | // here we are supposed to have the last portion of the given pathname 342 | _startCluster = findEntryCluster(dir_path); 343 | _currentDir = dir_path; 344 | } 345 | _currentCluster = _startCluster; 346 | _startSector = firstSectorOfCluster(_startCluster); 347 | _currentSector = _startSector; 348 | DEBF("SDMMC: Current ROOT set to <%s>\r\n", _currentDir.c_str() ); 349 | } 350 | 351 | 352 | void SDMMC_FAT32::setCurrentPoint(point_t& p) { 353 | cache_dir(p.firstCachedDirSector); 354 | cache_fat(p.firstCachedFatSector); 355 | _currentEntry = p.entry; 356 | _currentDir = p.currentDir; 357 | _startSector = p.startSector; 358 | _startCluster = p.startCluster; 359 | _currentSector = p.curSector; 360 | _currentCluster = p.curCluster; 361 | _dirent_num = p.direntNum; 362 | } 363 | 364 | 365 | point_t SDMMC_FAT32::getCurrentPoint() { 366 | point_t p; 367 | entry_t ent; 368 | p.entry = _currentEntry; 369 | p.currentDir = _currentDir; 370 | p.startSector = _startSector; 371 | p.startCluster= _startCluster; 372 | p.curSector = _currentSector; 373 | p.curCluster = _currentCluster; 374 | p.firstCachedFatSector = _firstCachedFatSector; 375 | p.firstCachedDirSector = _firstCachedDirSector; 376 | p.direntNum = _dirent_num; 377 | return p; 378 | } 379 | 380 | 381 | void SDMMC_FAT32::rewindDir() { 382 | _currentCluster = _startCluster; 383 | _startSector = firstSectorOfCluster(_startCluster); 384 | _currentSector = _startSector; 385 | _dirent_num = 0; 386 | _currentEntry.name = ""; 387 | _currentEntry.size = 0; 388 | _currentEntry.is_dir = -1; 389 | _currentEntry.sectors.clear(); 390 | } 391 | 392 | entry_t* SDMMC_FAT32::nextEntry() { 393 | entry_t* ent; 394 | ent = buildNextEntry(); 395 | while (!(ent->is_end)) { 396 | if (ent->is_dir>=0) 397 | { 398 | // DEBF("inside nextEntry() %s %d\r\n", ent->name.c_str(), ent->sectors[0].first); 399 | return ent; 400 | } 401 | ent = buildNextEntry(); 402 | } 403 | return ent; 404 | } 405 | 406 | entry_t* SDMMC_FAT32::buildNextEntry() { 407 | uint32_t cl, cl_addr; 408 | uint8_t fname[513]; 409 | memset(&fname, 0, 513); 410 | uint8_t attr, n; 411 | fname_t filename, shortname; 412 | chain_t chain; 413 | esp_err_t ret; 414 | bool entry_done = false; 415 | _currentEntry.name = ""; 416 | _currentEntry.size = 0; 417 | _currentEntry.is_dir = -1; 418 | _currentEntry.is_end = 0; 419 | _currentEntry.sectors.clear(); 420 | 421 | while (!entry_done) { 422 | if (_dirent_num >= NDIR_PER_SEC) { // going out of sector 423 | _dirent_num = 0; 424 | if (clusterBySector(_currentSector+1) != _currentCluster) { // going out of cluster 425 | _currentCluster = getNextCluster(_currentCluster); 426 | if (fat_entry_type(_currentCluster)==LAST_CLUSTER) { 427 | rewindDir(); 428 | _currentEntry.is_end = 1; 429 | break; 430 | } 431 | _currentSector = firstSectorOfCluster(_currentCluster); 432 | } else { 433 | _currentSector++; 434 | } 435 | } 436 | 437 | if (_currentSector==_sectorInBuf) { 438 | ret = ESP_OK; // already buffered 439 | } else { 440 | ret = read_sector(_currentSector); 441 | if (ret!=ESP_OK) break; 442 | } 443 | 444 | sfn_dir_t (&rec_array)[NDIR_PER_SEC] = reinterpret_cast(sector_buf); 445 | 446 | attr = rec_array[_dirent_num].attr; 447 | if (attr == FAT32_LONG_FILE_NAME) { // process LFN entry part 448 | entry_done = false; 449 | lfn_dir_t (&lfn_entry) = reinterpret_cast(rec_array[_dirent_num]); 450 | // lfn_print_ent(&lfn_entry, lfn_entry.cksum); 451 | n = lfn_entry.seqno; 452 | // helpers: lfn_is_first(n), lfn_is_last(n), lfn_is_deleted(n) 453 | if ( lfn_is_deleted(n) ) { 454 | // DEBUG("\tSkipping deleted LFN entry"); 455 | _dirent_num++; 456 | continue; 457 | } 458 | n = (uint8_t)((n)<<2)>>2; 459 | n = (n-1)*26 ; // strip 6th bit and bring it to zero base , multiply by 26 (lfn part is 26 bytes long) 460 | memcpy(&(fname[n]), &(lfn_entry.name1_5), 10); 461 | memcpy(&(fname[10+n]), &(lfn_entry.name6_11), 12); 462 | memcpy(&(fname[22+n]), &(lfn_entry.name12_13), 4); 463 | 464 | } else { // rec_array[i] contains SFN entry or empty record 465 | // dirent_print_helper(&(rec_array[i])); 466 | entry_done = true; 467 | if (rec_array[_dirent_num].attr&FAT32_VOLUME_LABEL || rec_array[_dirent_num].attr&FAT32_HIDDEN || rec_array[_dirent_num].attr&FAT32_SYSTEM_FILE || dirent_free(&(rec_array[_dirent_num]))) { 468 | memset(&fname, 0, 513); 469 | _dirent_num++; 470 | continue; // skip deleted, empty, system and hidden, sysvol 471 | } 472 | shortname = to_8dot3(rec_array[_dirent_num].filename); 473 | // DEBF(">>>\tShort name = <%s>\r\n", shortname.c_str()); 474 | 475 | } 476 | if (entry_done) { 477 | filename = unicode2ascii(reinterpret_cast(&fname), MAX_NAME_LEN) ; 478 | // DEBF(">>>\tLong name = <%s>\r\n\r\n", filename.c_str()); 479 | if (shortname>"" && filename == "") filename = shortname; 480 | if (filename.endsWith(".")) filename.remove(filename.length()-1,1); 481 | _currentEntry.name = filename; 482 | _currentEntry.size = rec_array[_dirent_num].file_nbytes; 483 | _currentEntry.is_dir = (rec_array[_dirent_num].attr&FAT32_DIR); 484 | 485 | cl = fat32_cluster_id(&rec_array[_dirent_num]); 486 | 487 | chain.first=firstSectorOfCluster(cl); 488 | chain.last = chain.first; 489 | cl_addr = cl; 490 | while (true) { 491 | cl = getNextCluster(cl_addr); 492 | if (fat_entry_type(cl)==LAST_CLUSTER) { 493 | chain.last = lastSectorOfCluster(cl_addr); 494 | break; 495 | } 496 | if ( (cl - cl_addr) != 1 ) { 497 | chain.last = lastSectorOfCluster(cl_addr); 498 | _currentEntry.sectors.push_back(chain); 499 | chain.first = firstSectorOfCluster(cl); 500 | } 501 | cl_addr = cl; 502 | } 503 | _currentEntry.sectors.push_back(chain); 504 | 505 | // DEB("First:"); 506 | // DEBUG(chain.first); 507 | // DEB("Last:"); 508 | // DEBUG(chain.last); 509 | memset(&fname, 0, 513); 510 | _dirent_num++; 511 | return &_currentEntry; 512 | } else { 513 | _dirent_num++; 514 | } 515 | } 516 | return &_currentEntry; 517 | } 518 | 519 | void SDMMC_FAT32::printCurrentDir() { 520 | DEBF("Directory <%s>:\r\n", _currentDir.c_str()); 521 | const char cdir[] = {""}; 522 | const char cfile[] = {""}; 523 | rewindDir(); 524 | entry_t* ent; 525 | int i = 0; 526 | ent = nextEntry(); 527 | while (!(ent->is_end)) { 528 | if (ent->size >= 10240) { 529 | DEBF("%d:\t%s\t%s\t%d kB\r\n", i, ent->is_dir ? cdir : cfile, ent->name.c_str(), ent->size/1024); 530 | } else { 531 | DEBF("%d:\t%s\t%s\t%d Bytes\r\n", i, ent->is_dir ? cdir : cfile, ent->name.c_str(), ent->size); 532 | } 533 | ent = nextEntry(); 534 | i++; 535 | } 536 | } 537 | 538 | int SDMMC_FAT32::fat_entry_type(uint32_t x) { 539 | // eliminate upper 4 bits: error to not do this. 540 | x = (x << 4) >> 4; 541 | switch(x) { 542 | case FREE_CLUSTER: 543 | case RESERVED_CLUSTER: 544 | case BAD_CLUSTER: 545 | return x; 546 | } 547 | if(x >= 0x2 && x <= 0xFFFFFEF) 548 | return USED_CLUSTER; 549 | if(x >= 0xFFFFFF0 && x <= 0xFFFFFF6) 550 | // DEBF("reserved value: %x\n", x); 551 | if(x >= 0xFFFFFF8 && x <= 0xFFFFFFF) 552 | return LAST_CLUSTER; 553 | // DEBF("impossible type value: %x\n", x); 554 | return LAST_CLUSTER; //??? 555 | } 556 | 557 | int SDMMC_FAT32::dirent_free(sfn_dir_t *d) { 558 | uint8_t x = d->filename[0]; 559 | if(d->attr == FAT32_LONG_FILE_NAME) 560 | return (dirent_is_deleted_lfn(d)); 561 | return x == 0 || x == 0xE5; 562 | } 563 | 564 | // trim spaces from the first 8. 565 | // not re-entrant, gross. 566 | const char* SDMMC_FAT32::to_8dot3(const char p[11]) { 567 | static char s[14]; 568 | const char *suffix = &p[8]; 569 | // find last non-space 570 | memcpy(s, p, 8); 571 | int i = 7; 572 | for(; i >= 0; i--) { 573 | if(s[i] != ' ') { 574 | i++; 575 | break; 576 | } 577 | } 578 | s[i++] = '.'; 579 | for(int j = 0; j < 3; j++) { 580 | if (suffix[j] !=' ') { 581 | s[i++] = suffix[j]; 582 | } 583 | } 584 | s[i++] = 0; 585 | return s; 586 | } 587 | 588 | void SDMMC_FAT32::dirent_name(sfn_dir_t *d, char *fname) { 589 | strcpy(fname, to_8dot3(d->filename)); 590 | } 591 | 592 | // not re-entrant, gross. 593 | const char* SDMMC_FAT32::add_nul(const char filename[11]) { 594 | static char s[14]; 595 | memcpy(s, filename, 11); 596 | s[11] = 0; 597 | return s; 598 | } 599 | 600 | uint8_t SDMMC_FAT32::lfn_checksum(const char* pFCBName) { 601 | uint8_t sum = 0; 602 | for (int i = 11; i; i--) 603 | sum = ((sum & 1) << 7) + (sum >> 1) + *pFCBName++; 604 | return sum; 605 | } 606 | 607 | int SDMMC_FAT32::lfn_is_deleted(uint8_t seqno) { 608 | return (seqno & 0xe5) == 0xe5; 609 | } 610 | 611 | int SDMMC_FAT32::dirent_is_deleted_lfn(sfn_dir_t *d) { 612 | assert(d->attr == FAT32_LONG_FILE_NAME); 613 | lfn_dir_t *l = reinterpret_cast(d); 614 | return lfn_is_deleted(l->seqno); 615 | } 616 | 617 | void SDMMC_FAT32::dirent_print_helper(sfn_dir_t *d) { 618 | DEB(">>>\t"); 619 | if(dirent_free(d)) { 620 | DEB("dirent is not allocated\n"); 621 | return; 622 | } 623 | if(d->attr == FAT32_LONG_FILE_NAME) { 624 | DEB("dirent is an LFN\n"); 625 | return; 626 | } 627 | if(is_attr(d->attr, FAT32_ARCHIVE)) { 628 | DEB("[ARCHIVE]: assuming short part of LFN:"); 629 | } else if(!(is_attr(d->attr, FAT32_DIR))) { 630 | DEBF("need to handle attr %x \n", d->attr); 631 | if(is_attr(d->attr, FAT32_ARCHIVE)) 632 | return; 633 | } 634 | DEBF("\tfilename = raw=<%s> 8.3=<%s>\n", add_nul(d->filename),to_8dot3(d->filename)); 635 | DEB("\tbyte version = {"); 636 | for(int i = 0; i < sizeof d->filename; i++) { 637 | if(i==8) { 638 | DEB("\n"); 639 | DEB("\t\t\t\t"); 640 | } 641 | DEBF("[%c]/%x,", d->filename[i],d->filename[i]); 642 | } 643 | DEBUG("}"); 644 | 645 | DEBF("\tattr = %x ", d->attr); 646 | if(d->attr != FAT32_LONG_FILE_NAME) { 647 | if(d->attr&FAT32_RO) 648 | DEB(" [Read-only]\n"); 649 | if(d->attr&FAT32_HIDDEN) 650 | DEB(" [HIDDEN]\n"); 651 | if(d->attr&FAT32_SYSTEM_FILE) 652 | DEB(" [SYSTEM FILE: don't move]\n"); 653 | DEBUG(""); 654 | } 655 | 656 | DEBF("\thi_start = %x\n", d->hi_start); 657 | DEBF("\tlo_start = %d\n", d->lo_start); 658 | DEBF("\tfile_nbytes = %d\n", d->file_nbytes); 659 | } 660 | 661 | void SDMMC_FAT32::lfn_print_ent(lfn_dir_t *l, uint8_t cksum) { 662 | uint8_t n = l->seqno; 663 | fname_t t; 664 | DEBF(">>>\tseqno = %x, deleted=%d\n", n, lfn_is_deleted(n)); 665 | uint8_t buf[27]; 666 | memcpy(&buf[0], l->name1_5, 10); 667 | memcpy(&buf[10], l->name6_11, 12); 668 | memcpy(&buf[22], l->name12_13, 4); 669 | buf[26] = 0; 670 | 671 | for(int i = 0; i < 26; i += 2) { 672 | if(buf[i] == 0 && buf[i+1] == 0) 673 | break; 674 | DEBF("\t\tlfn[%d] = [%c] = %x\n", i, buf[i], buf[i]); 675 | } 676 | DEBF("\tcksum=%x (expected=%x)\n", l->cksum, cksum); 677 | } 678 | 679 | 680 | fname_t SDMMC_FAT32::unicode2ascii(uint16_t* in, int len) { 681 | uint16_t x; 682 | fname_t out = ""; 683 | for(int i = 0; i < len ; i++) { 684 | x = in[i]; 685 | if (x==0xffff) x = 0; 686 | x = (uint16_t)(x << 9)>>9; 687 | if (x<32 && x>0) x = 94; 688 | switch (x) { 689 | case 34: 690 | case 42: 691 | case 47: 692 | case 58: 693 | case 60: 694 | case 62: 695 | case 63: 696 | case 92: 697 | case 124: 698 | case 127: 699 | x = 94; 700 | break; 701 | } 702 | if (x==0) continue; 703 | out += (char)x; 704 | } 705 | return out; 706 | } 707 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/sdmmc_file.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // helper class, it is not that fast and clear, but sometimes you just want to read lines from a config file 4 | // (here it's limited to 256-1 bytes by FixedString, but you can refactor it and use String MAYBE), 5 | // though consider using the full SDMMC/FS combo or vfs (esp_vfs_fat_sdmmc_mount()) in that case 6 | #include "sdmmc_types.h" 7 | 8 | class SDMMC_FileReader { 9 | public: 10 | SDMMC_FileReader(SDMMC_FAT32* Card); 11 | ~SDMMC_FileReader(){}; 12 | esp_err_t open(fpath_t fname); 13 | esp_err_t close(); 14 | void read_line(str_max_t& retStr); // we assume that line length < FixedString max size (256 bytes) 15 | bool available(); 16 | void rewind(); 17 | entry_t next_entry(); 18 | 19 | private: 20 | SDMMC_FAT32* _Card; 21 | char _sectorBuf[BYTES_PER_SECTOR]; 22 | uint32_t _sectorRead; 23 | fpath_t _path; 24 | entry_t _entry; 25 | entry_t _currentEntry; 26 | uint32_t _filePos; // promote to size_t if you'd like 27 | uint32_t _bufPos; 28 | uint32_t _bufCount; 29 | uint32_t _dirPos; 30 | }; 31 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/sdmmc_file.ino: -------------------------------------------------------------------------------- 1 | #include "sdmmc_file.h" 2 | #include "sdmmc.h" 3 | 4 | SDMMC_FileReader::SDMMC_FileReader( SDMMC_FAT32* Card ) { 5 | _Card = Card; 6 | } 7 | 8 | esp_err_t SDMMC_FileReader::open(fpath_t fname) { 9 | _filePos = 0; 10 | _bufCount = 0; 11 | _bufPos = 0; 12 | entry_t* entry_p = _Card->findEntry(fname); 13 | 14 | //DEBF(" &entry_p = %#010x\r\n", entry_p); 15 | _entry = *entry_p; 16 | if (_entry.is_end) return 0x105; // NOT_FOUND 17 | return 0 ; // ESP_OK 18 | } 19 | 20 | esp_err_t SDMMC_FileReader::close() { 21 | _entry.is_end = 1; 22 | return 0 ; // ESP_OK; 23 | } 24 | 25 | bool SDMMC_FileReader::available() { 26 | if (_entry.is_end == 1) return false; 27 | if (_filePos>=_entry.size) return false; 28 | return true; 29 | } 30 | 31 | void SDMMC_FileReader::read_line(str_max_t& str) { 32 | str = ""; 33 | char ch; 34 | int str_pos = 0; 35 | uint32_t sec; 36 | if (_filePos==0) { 37 | sec = _entry.sectors[0].first; 38 | char* tmp = reinterpret_cast(_Card->readSector(sec)); 39 | memcpy(_sectorBuf, tmp, BYTES_PER_SECTOR); 40 | _sectorRead = sec; 41 | } 42 | while (true) { 43 | 44 | if (_bufPos >= BYTES_PER_SECTOR) { 45 | // DEBUG("Next sector"); 46 | _bufPos = 0; 47 | sec = _Card->getNextSector(_sectorRead); 48 | char* tmp = reinterpret_cast(_Card->readSector(sec)); 49 | memcpy(_sectorBuf, tmp, BYTES_PER_SECTOR); 50 | _sectorRead = sec; 51 | } 52 | ch = _sectorBuf[_bufPos]; 53 | if (str_pos >= MAX_STR_LEN-2) { 54 | str += '\0'; 55 | _filePos++; 56 | _bufPos++; 57 | // DEBUG("FixedString limit"); 58 | break; // FixedString overflow 59 | } 60 | if (_filePos >= _entry.size) { 61 | str += '\0'; 62 | _entry.is_end = 1; 63 | // DEBUG("EOF"); 64 | break; // EOF 65 | } 66 | if ( ch == '\0' || ch == '\n' ) { 67 | _bufPos++; 68 | _filePos++; 69 | str += '\0'; 70 | // DEBUG("EOL"); 71 | break; // EOL 72 | } 73 | if (ch != '\r') { 74 | str += ch; 75 | str_pos++; 76 | } 77 | _bufPos++; 78 | _filePos++; 79 | } 80 | str.trim(); 81 | // DEBF("position: buf=%d file=%d str=%d \r\n", _bufPos, _filePos, str_pos); 82 | } 83 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/sdmmc_types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "driver/sdmmc_host.h" 4 | #include 5 | #include 6 | 7 | #define MAX_NAME_LEN (64) // must be dividible by 4 8 | #define MAX_PATH_LEN (128) // must be dividible by 4 9 | #define MAX_STR_LEN (256) // max string size 10 | #define BYTES_PER_SECTOR (512) 11 | #define FAT_CACHE_SECTORS (8) 12 | #define DIR_CACHE_SECTORS (8) 13 | #define DIR_ENTRY_SIZE (32) 14 | #define NDIR_PER_SEC (BYTES_PER_SECTOR/DIR_ENTRY_SIZE) 15 | 16 | using fname_t = FixedString ; 17 | using fpath_t = FixedString ; 18 | using str_max_t = FixedString ; 19 | 20 | // directory attribute types. 21 | enum { 22 | FAT32_NO_ATTR = 0, 23 | FAT32_RO = 0x01, 24 | FAT32_HIDDEN = 0x02, 25 | FAT32_SYSTEM_FILE = 0x04, 26 | FAT32_VOLUME_LABEL = 0x08, 27 | FAT32_LONG_FILE_NAME = 0x0F, 28 | FAT32_DIR = 0x10, 29 | FAT32_ARCHIVE = 0x20, 30 | FAT32_DEVICE = 0x40, 31 | FAT32_RESERVED = 0x80, 32 | } eAttr_t; 33 | 34 | enum { 35 | FREE_CLUSTER = 0, 36 | RESERVED_CLUSTER = 0x01, 37 | USED_CLUSTER = 0x02, 38 | BAD_CLUSTER = 0x0FFFFFF7, 39 | LAST_CLUSTER = 0x0FFFFFF8, 40 | }; 41 | 42 | typedef struct __attribute__((packed)) { 43 | uint8_t status; 44 | uint8_t headStart; 45 | uint16_t cylSectStart; 46 | uint8_t fsType; 47 | uint8_t headEnd; 48 | uint16_t cylSectEnd; 49 | uint32_t firstSector; 50 | uint32_t sectorsTotal; 51 | } part_t; 52 | 53 | typedef struct __attribute__ ((packed)) { 54 | // if filename[0] == 0, then not allocated. 55 | // 2-11 char of file name. the "." is implied b/n bytes 7 and 8 56 | char filename[11]; // 0-10 File name (8 bytes) with extension (3 bytes) 57 | uint8_t attr; // 11 Attribute - a bitvector. 58 | // Bit 0: read only. 59 | // Bit 1: hidden. 60 | // Bit 2: system file. 61 | // Bit 3: volume label. 62 | // Bit 4: subdirectory. 63 | // Bit 5: archive. 64 | // Bits 6-7: unused. 65 | uint8_t reserved0; // 12 Reserved 66 | uint8_t ctime_tenths; // 13 file creation time (tenths of sec) 67 | uint16_t ctime; // 14-15 create time hours,min,sec 68 | uint16_t create_date; // 16-17 create date 69 | uint16_t access_date; // 18-19 create date 70 | uint16_t hi_start; // 20-21 high two bytes of first cluster 71 | uint16_t mod_time; // 22-23 Time (5/6/5 bits, for hour/minutes/doubleseconds) 72 | uint16_t mod_date; // 24-25 Date (7/4/5 bits, for year-since-1980/month/day) 73 | uint16_t lo_start; // 26-27 low order 2 bytes of first cluster 74 | // 0 for directories 75 | uint32_t file_nbytes; // 28-31 Filesize in bytes 76 | } sfn_dir_t; 77 | 78 | typedef struct __attribute__ ((packed)) { 79 | uint8_t seqno; // 0-0 Sequence number (ORed with 0x40) and allocation 80 | // status (0xe5 if unallocated) 81 | uint8_t name1_5[10]; // 1-10 File name characters 1-5 (Unicode) 82 | uint8_t attr; // 11-11 File attributes (always 0x0f) 83 | uint8_t reserved; // 12-12 Reserved 84 | uint8_t cksum; // 13-13 Checksum 85 | uint8_t name6_11[12]; // 14-25 File name characters 6-11 (Unicode) 86 | uint16_t reserved1; // 26-27 Reserved 87 | uint8_t name12_13[4]; // 28-31 File name characters 12-13 (Unicode) 88 | } lfn_dir_t; 89 | 90 | typedef struct { 91 | uint32_t first; 92 | uint32_t last; 93 | } chain_t; 94 | 95 | typedef struct { 96 | fname_t name; 97 | uint32_t size; 98 | int is_dir; 99 | int is_end; 100 | std::vector sectors; 101 | } entry_t; 102 | 103 | typedef struct { 104 | entry_t entry; 105 | fpath_t currentDir; 106 | uint32_t curSector ; 107 | uint32_t curCluster ; 108 | uint32_t startSector; 109 | uint32_t startCluster; 110 | uint32_t firstCachedFatSector; 111 | uint32_t firstCachedDirSector; 112 | int direntNum ; 113 | } point_t; 114 | 115 | typedef std::vector dirList_t; 116 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/src/midiusb/src/MIDIUSB_Defs.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is the copy of the part of MIDIUSB Library for Arduino 3 | https://github.com/arduino-libraries/MIDIUSB/blob/master/src/MIDIUSB_Defs.h 4 | Licensing statement is here 5 | https://github.com/arduino-libraries/MIDIUSB/blob/master/LICENSE.txt 6 | */ 7 | 8 | #pragma once 9 | #include 10 | 11 | typedef struct 12 | { 13 | uint8_t header; 14 | uint8_t byte1; 15 | uint8_t byte2; 16 | uint8_t byte3; 17 | } midiEventPacket_t; -------------------------------------------------------------------------------- /ESP32_SD_Sampler/src/midiusb/src/MIDIUSB_ESP32.cpp: -------------------------------------------------------------------------------- 1 | #include "USB.h" 2 | #if CONFIG_TINYUSB_MIDI_ENABLED 3 | 4 | #include "MIDIUSB_ESP32.h" 5 | 6 | // Interface counter 7 | enum interface_count { 8 | #if CFG_TUD_MIDI 9 | ITF_NUM_MIDI = 0, 10 | ITF_NUM_MIDI_STREAMING, 11 | #endif 12 | ITF_COUNT 13 | }; 14 | 15 | // USB Endpoint numbers 16 | enum usb_endpoints { 17 | // Available USB Endpoints: 5 IN/OUT EPs and 1 IN EP 18 | EP_EMPTY = 0, 19 | #if CFG_TUD_MIDI 20 | EPNUM_MIDI, 21 | #endif 22 | }; 23 | 24 | /** TinyUSB descriptors **/ 25 | #define TUSB_DESCRIPTOR_ITF_MIDI_LEN \ 26 | (TUD_MIDI_DESC_HEAD_LEN + TUD_MIDI_DESC_JACK_LEN * USB_MIDI_NUM_CABLES + \ 27 | TUD_MIDI_DESC_EP_LEN(USB_MIDI_NUM_CABLES) * 2) 28 | #define TUSB_DESCRIPTOR_TOTAL_LEN \ 29 | (TUD_CONFIG_DESC_LEN + CFG_TUD_MIDI * TUSB_DESCRIPTOR_ITF_MIDI_LEN) 30 | 31 | ESP_EVENT_DEFINE_BASE(ARDUINO_USB_MIDI_EVENTS); 32 | 33 | MIDIUSB MidiUSB; 34 | 35 | static uint16_t load_midi_descriptor(uint8_t *dst, uint8_t *itf) { 36 | uint8_t descriptor[TUSB_DESCRIPTOR_ITF_MIDI_LEN] = { 37 | TUD_MIDI_DESC_HEAD(*itf, 4, USB_MIDI_NUM_CABLES), 38 | TUD_MIDI_DESC_JACK_DESC(1, 0), 39 | #if USB_MIDI_NUM_CABLES >= 2 40 | TUD_MIDI_DESC_JACK_DESC(2, 0), 41 | #endif 42 | #if USB_MIDI_NUM_CABLES >= 3 43 | TUD_MIDI_DESC_JACK_DESC(3, 0), 44 | #endif 45 | TUD_MIDI_DESC_EP(EPNUM_MIDI, 64, USB_MIDI_NUM_CABLES), 46 | TUD_MIDI_JACKID_IN_EMB(1), 47 | #if USB_MIDI_NUM_CABLES >= 2 48 | TUD_MIDI_JACKID_IN_EMB(2), 49 | #endif 50 | #if USB_MIDI_NUM_CABLES >= 3 51 | TUD_MIDI_JACKID_IN_EMB(3), 52 | #endif 53 | TUD_MIDI_DESC_EP(0x80 | EPNUM_MIDI, 64, USB_MIDI_NUM_CABLES), 54 | TUD_MIDI_JACKID_OUT_EMB(1), 55 | #if USB_MIDI_NUM_CABLES >= 2 56 | TUD_MIDI_JACKID_OUT_EMB(2), 57 | #endif 58 | #if USB_MIDI_NUM_CABLES >= 3 59 | TUD_MIDI_JACKID_OUT_EMB(3) 60 | #endif 61 | }; 62 | *itf += 2; // +2 interface count(Audio Control, Midi Streaming) 63 | memcpy(dst, descriptor, TUSB_DESCRIPTOR_ITF_MIDI_LEN); 64 | return TUSB_DESCRIPTOR_ITF_MIDI_LEN; 65 | } 66 | 67 | static void usbEventCallback(void *arg, esp_event_base_t event_base, 68 | int32_t event_id, void *event_data) { 69 | if (event_base == ARDUINO_USB_EVENTS) { 70 | arduino_usb_event_data_t *data = (arduino_usb_event_data_t *)event_data; 71 | switch (event_id) { 72 | case ARDUINO_USB_STARTED_EVENT: 73 | HWSerial.println("USB PLUGGED"); 74 | break; 75 | case ARDUINO_USB_STOPPED_EVENT: 76 | HWSerial.println("USB UNPLUGGED"); 77 | break; 78 | case ARDUINO_USB_SUSPEND_EVENT: 79 | HWSerial.printf("USB SUSPENDED: remote_wakeup_en: %u\n", 80 | data->suspend.remote_wakeup_en); 81 | break; 82 | case ARDUINO_USB_RESUME_EVENT: 83 | HWSerial.println("USB RESUMED"); 84 | break; 85 | default: 86 | break; 87 | } 88 | } else if (event_base == ARDUINO_USB_MIDI_EVENTS) { 89 | midiEventPacket_t *data = (midiEventPacket_t *)event_data; 90 | switch (event_id) { 91 | default: 92 | HWSerial.printf("MIDI EVENT: ID=%d, DATA=%d\r\n", event_id, 93 | (uint32_t)data); 94 | break; 95 | } 96 | } 97 | } 98 | 99 | MIDIUSB::MIDIUSB() { 100 | tinyusb_enable_interface(USB_INTERFACE_MIDI, TUSB_DESCRIPTOR_ITF_MIDI_LEN, 101 | load_midi_descriptor); 102 | USB.onEvent(usbEventCallback); 103 | } 104 | 105 | MIDIUSB::~MIDIUSB(){}; 106 | 107 | void MIDIUSB::begin() { 108 | USB.begin(); 109 | } 110 | 111 | midiEventPacket_t MIDIUSB::read() { 112 | // The MIDI interface always creates input and output port/jack descriptors 113 | // regardless of these being used or not. Therefore incoming traffic should 114 | // be read (possibly just discarded) to avoid the sender blocking in IO 115 | uint8_t packet[4] = {0}; 116 | midiEventPacket_t data = {0, 0, 0, 0}; 117 | if (tud_midi_packet_read(packet)) { 118 | memcpy(&data, packet, 4); 119 | } 120 | return data; 121 | } 122 | void MIDIUSB::flush(void) {} 123 | void MIDIUSB::sendMIDI(midiEventPacket_t event) { 124 | tud_midi_packet_write((uint8_t *)&event); 125 | } 126 | 127 | #endif /* CONFIG_TINYUSB_MIDI_ENABLED */ 128 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/src/midiusb/src/MIDIUSB_ESP32.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "sdkconfig.h" 4 | #if CONFIG_TINYUSB_MIDI_ENABLED 5 | 6 | #include 7 | #include 8 | #include "esp_event.h" 9 | #include "freertos/FreeRTOS.h" 10 | #include "freertos/queue.h" 11 | #include "freertos/semphr.h" 12 | #include "Stream.h" 13 | 14 | #include "USB.h" 15 | 16 | #include "src/midiusb/src/MIDIUSB_Defs.h" 17 | 18 | #include "HardwareSerial.h" 19 | #include "HWCDC.h" 20 | #include "esp32-hal-tinyusb.h" 21 | 22 | #if ARDUINO_USB_CDC_ON_BOOT 23 | #define HWSerial Serial0 24 | #define USBSerial Serial 25 | #else 26 | #define HWSerial Serial 27 | #endif 28 | 29 | ESP_EVENT_DECLARE_BASE(ARDUINO_USB_MIDI_EVENTS); 30 | #define USB_MIDI_NUM_CABLES 1 31 | #if (USB_MIDI_NUM_CABLES <= 0) || (USB_MIDI_NUM_CABLES > 3) 32 | #error "USB_MIDI_NUM_CABLES must be 1, 2 or 3" 33 | #endif 34 | 35 | typedef enum { 36 | ARDUINO_USB_MIDI_ANY_EVENT = ESP_EVENT_ANY_ID, 37 | } arduino_usb_midi_event_t; 38 | 39 | class MIDIUSB 40 | { 41 | public: 42 | MIDIUSB(); 43 | ~MIDIUSB(); 44 | 45 | // MIDIUSB method 46 | void begin(); 47 | midiEventPacket_t read(); 48 | void flush(void); 49 | void sendMIDI(midiEventPacket_t event); 50 | 51 | }; 52 | 53 | extern MIDIUSB MidiUSB; 54 | 55 | #endif /* CONFIG_TINYUSB_MIDI_ENABLED */ 56 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/src/usbmidi/src/USB-MIDI.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 lathoub 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #if defined(ESP32) || defined(ARDUINO_ARCH_ESP32) 27 | #if defined(ESP32_USB_HOST) 28 | #include 29 | #else 30 | //#include 31 | #include "src/midiusb/src/MIDIUSB_ESP32.h" 32 | #endif 33 | #else 34 | #include "MIDIUSB.h" 35 | #define USB_MIDI_NUM_CABLES 1 36 | #if USB_MIDI_NUM_CABLES != 1 37 | #error "Currently MIDIUSB Library for Arduino does not support Multi Cable" 38 | #endif 39 | #endif 40 | 41 | #include "USB-MIDI_defs.h" 42 | #include "USB-MIDI_Namespace.h" 43 | 44 | BEGIN_USBMIDI_NAMESPACE 45 | 46 | class usbMidiTransport 47 | { 48 | private: 49 | byte mTxBuffer[4]; 50 | size_t mTxIndex; 51 | MidiType mTxStatus; 52 | 53 | static byte mRxBuffer[USB_MIDI_NUM_CABLES][64]; 54 | static size_t mRxLength[USB_MIDI_NUM_CABLES]; 55 | static size_t mRxIndex[USB_MIDI_NUM_CABLES]; 56 | 57 | midiEventPacket_t mPacket; 58 | uint8_t cableNumber; 59 | static uint8_t cableNumberTotal; 60 | static uint16_t cableNumberMask; 61 | 62 | public: 63 | ~usbMidiTransport() { 64 | 65 | } 66 | usbMidiTransport(uint8_t cableNumber = 0) 67 | { 68 | if (cableNumber >= USB_MIDI_NUM_CABLES || cableNumberMask & (1 << cableNumber)) { 69 | // Selected cable number is invalid 70 | this->cableNumber = 0xFF; 71 | } 72 | this->cableNumber = cableNumber; 73 | cableNumberTotal++; 74 | cableNumberMask |= (1 << cableNumber); 75 | }; 76 | 77 | public: 78 | 79 | static const bool thruActivated = false; 80 | 81 | void begin() 82 | { 83 | #if defined(ESP32) || defined(ARDUINO_ARCH_ESP32) 84 | MidiUSB.begin(); 85 | #endif 86 | mTxIndex = 0; 87 | mRxIndex[cableNumber] = 0; 88 | mRxLength[cableNumber] = 0; 89 | }; 90 | 91 | void end() 92 | { 93 | } 94 | 95 | bool beginTransmission(MidiType status) 96 | { 97 | mTxStatus = status; 98 | 99 | byte cin = 0; 100 | if (status < SystemExclusive) { 101 | // Non System messages 102 | cin = type2cin[((status & 0xF0) >> 4) - 7][1]; 103 | mPacket.header = MAKEHEADER(cableNumber, cin); 104 | } 105 | else { 106 | // Only System messages 107 | cin = system2cin[status & 0x0F][1]; 108 | mPacket.header = MAKEHEADER(cableNumber, cin); 109 | } 110 | 111 | mPacket.byte1 = mPacket.byte2 = mPacket.byte3 = 0; 112 | mTxIndex = 0; 113 | 114 | return true; 115 | }; 116 | 117 | void write(byte byte) 118 | { 119 | if (mTxStatus != MidiType::SystemExclusive) { 120 | if (mTxIndex == 0) mPacket.byte1 = byte; 121 | else if (mTxIndex == 1) mPacket.byte2 = byte; 122 | else if (mTxIndex == 2) mPacket.byte3 = byte; 123 | } 124 | else if (byte == MidiType::SystemExclusiveStart) { 125 | mPacket.header = MAKEHEADER(cableNumber, 0x04); 126 | mPacket.byte1 = byte; 127 | } 128 | else // SystemExclusiveEnd or SysEx data 129 | { 130 | auto i = mTxIndex % 3; 131 | if (byte == MidiType::SystemExclusiveEnd) 132 | mPacket.header = MAKEHEADER(cableNumber, (0x05 + i)); 133 | 134 | if (i == 0) { 135 | mPacket.byte1 = byte; mPacket.byte2 = mPacket.byte3 = 0x00; 136 | } 137 | else if (i == 1) { 138 | mPacket.byte2 = byte; mPacket.byte3 = 0x00; 139 | } 140 | else if (i == 2) { 141 | mPacket.byte3 = byte; 142 | if (byte != MidiType::SystemExclusiveEnd) 143 | SENDMIDI(mPacket); 144 | } 145 | } 146 | mTxIndex++; 147 | }; 148 | 149 | void endTransmission() 150 | { 151 | SENDMIDI(mPacket); 152 | }; 153 | 154 | byte read() 155 | { 156 | RXBUFFER_POPFRONT(byte); 157 | return byte; 158 | }; 159 | 160 | unsigned available() 161 | { 162 | // consume mRxBuffer first, before getting a new packet 163 | bool allCableAvailable = true; 164 | for (uint8_t i = 0; i < USB_MIDI_NUM_CABLES; i++) { 165 | if (mRxLength[i] > 0) { 166 | allCableAvailable = false; 167 | break; 168 | } 169 | 170 | } 171 | 172 | if(!allCableAvailable) { 173 | return mRxLength[cableNumber]; 174 | } 175 | 176 | mPacket = MidiUSB.read(); 177 | if (mPacket.header != 0) { 178 | auto cn = GETCABLENUMBER(mPacket); 179 | if (cn >= cableNumberTotal) 180 | return 0; 181 | 182 | mRxIndex[cn] = 0; 183 | auto cin = GETCIN(mPacket); 184 | auto len = cin2Len[cin][1]; 185 | switch (len) { 186 | case 0: 187 | if (cin == 0x4 || cin == 0x7) 188 | RXBUFFER_PUSHBACK3 189 | else if (cin == 0x5) 190 | RXBUFFER_PUSHBACK1 191 | else if (cin == 0x6) 192 | RXBUFFER_PUSHBACK2 193 | break; 194 | case 1: 195 | RXBUFFER_PUSHBACK1 196 | break; 197 | case 2: 198 | RXBUFFER_PUSHBACK2 199 | break; 200 | case 3: 201 | RXBUFFER_PUSHBACK3 202 | break; 203 | default: 204 | break; // error 205 | } 206 | } 207 | 208 | return mRxLength[cableNumber]; 209 | }; 210 | }; 211 | 212 | byte usbMidiTransport::mRxBuffer[USB_MIDI_NUM_CABLES][64] = {0}; 213 | size_t usbMidiTransport::mRxLength[USB_MIDI_NUM_CABLES] = {0}; 214 | size_t usbMidiTransport::mRxIndex[USB_MIDI_NUM_CABLES] = {0}; 215 | uint8_t usbMidiTransport::cableNumberTotal = 0; 216 | uint16_t usbMidiTransport::cableNumberMask = 0; 217 | 218 | END_USBMIDI_NAMESPACE 219 | 220 | /*! \brief 221 | */ 222 | #define USBMIDI_CREATE_INSTANCE(CableNr, Name) \ 223 | USBMIDI_NAMESPACE::usbMidiTransport __usb##Name(CableNr);\ 224 | MIDI_NAMESPACE::MidiInterface Name((USBMIDI_NAMESPACE::usbMidiTransport&)__usb##Name); 225 | 226 | #define USBMIDI_CREATE_CUSTOM_INSTANCE(CableNr, Name, Settings) \ 227 | USBMIDI_NAMESPACE::usbMidiTransport __usb##Name(CableNr);\ 228 | MIDI_NAMESPACE::MidiInterface Name((USBMIDI_NAMESPACE::usbMidiTransport&)__usb##Name); 229 | 230 | #define USBMIDI_CREATE_DEFAULT_INSTANCE() \ 231 | USBMIDI_CREATE_INSTANCE(0, MIDI) 232 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/src/usbmidi/src/USB-MIDI_Namespace.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 lathoub 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #define USBMIDI_NAMESPACE usbMidi 26 | #define BEGIN_USBMIDI_NAMESPACE \ 27 | namespace USBMIDI_NAMESPACE \ 28 | { 29 | #define END_USBMIDI_NAMESPACE } 30 | 31 | #define USING_NAMESPACE_USBMIDI using namespace USBMIDI_NAMESPACE; 32 | 33 | BEGIN_USBMIDI_NAMESPACE 34 | 35 | END_USBMIDI_NAMESPACE 36 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/src/usbmidi/src/USB-MIDI_defs.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2020 lathoub 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "USB-MIDI_Namespace.h" 26 | 27 | BEGIN_USBMIDI_NAMESPACE 28 | 29 | using namespace MIDI_NAMESPACE; 30 | 31 | // from https://www.usb.org/sites/default/files/midi10.pdf 32 | // 4 USB-MIDI Event Packets 33 | // Table 4-1: Code Index Number Classifications 34 | 35 | static uint8_t type2cin[][2] = { {MidiType::InvalidType,0}, {MidiType::NoteOff,8}, {MidiType::NoteOn,9}, {MidiType::AfterTouchPoly,0xA}, {MidiType::ControlChange,0xB}, {MidiType::ProgramChange,0xC}, {MidiType::AfterTouchChannel,0xD}, {MidiType::PitchBend,0xE} }; 36 | 37 | static uint8_t system2cin[][2] = { {MidiType::SystemExclusive,0}, {MidiType::TimeCodeQuarterFrame,2}, {MidiType::SongPosition,3}, {MidiType::SongSelect,2}, {0,0}, {0,0}, {MidiType::TuneRequest,5}, {MidiType::SystemExclusiveEnd,0}, {MidiType::Clock,0xF}, {0,0}, {MidiType::Start,0xF}, {MidiType::Continue,0xF}, {MidiType::Stop,0xF}, {0,0}, {MidiType::ActiveSensing,0xF}, {MidiType::SystemReset,0xF} }; 38 | 39 | static byte cin2Len[][2] = { {0,0}, {1,0}, {2,2}, {3,3}, {4,0}, {5,0}, {6,0}, {7,0}, {8,3}, {9,3}, {10,3}, {11,3}, {12,2}, {13,2}, {14,3}, {15,1} }; 40 | 41 | #define GETCABLENUMBER(packet) (packet.header >> 4); 42 | #define GETCIN(packet) (packet.header & 0x0f); 43 | #define MAKEHEADER(cn, cin) (((cn & 0x0f) << 4) | cin) 44 | #define RXBUFFER_PUSHBACK1 { mRxBuffer[cn][mRxLength[cn]++] = mPacket.byte1; } 45 | #define RXBUFFER_PUSHBACK2 { mRxBuffer[cn][mRxLength[cn]++] = mPacket.byte1; mRxBuffer[cn][mRxLength[cn]++] = mPacket.byte2; } 46 | #define RXBUFFER_PUSHBACK3 { mRxBuffer[cn][mRxLength[cn]++] = mPacket.byte1; mRxBuffer[cn][mRxLength[cn]++] = mPacket.byte2; mRxBuffer[cn][mRxLength[cn]++] = mPacket.byte3; } 47 | 48 | #define RXBUFFER_POPFRONT(byte) auto byte = mRxBuffer[cableNumber][mRxIndex[cableNumber]++]; mRxLength[cableNumber]--; 49 | #define SENDMIDI(packet) { MidiUSB.sendMIDI(packet); MidiUSB.flush(); } 50 | 51 | END_USBMIDI_NAMESPACE 52 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define BUF_EXTRA_BYTES 32 // for buffer overlapping 3 | #define BUF_NUMBER 2 // tic-tac don't change, for readability only 4 | #define CHANNELS 2 // 1 = mono, 2 = stereo 5 | #define BYTES_PER_CHANNEL 2 6 | 7 | const int BUF_SIZE_BYTES = (READ_BUF_SECTORS * BYTES_PER_SECTOR); 8 | const float DIV_BUF_SIZE_BYTES = (1.0f / BUF_SIZE_BYTES); 9 | const int INTS_PER_SECTOR = (BYTES_PER_SECTOR / 2); 10 | const int start_byte[5] = { 0, 0, 0, 1, 2 }; // offset values for [-], 8, 16, 24, 32 pcm bits per channel 11 | 12 | #include "adsr.h" 13 | #include "sdmmc.h" 14 | 15 | typedef struct __attribute__((packed)){ 16 | char riff[4] = {'R', 'I', 'F', 'F'}; 17 | uint32_t fileSize; // actually, ( filesize-8 ) 18 | char waveType[4] = {'W', 'A', 'V', 'E'}; 19 | char format[4] = {'f', 'm', 't', ' '}; 20 | uint32_t lengthOfData = 16; // length of the rest of the header : 16 for PCM 21 | uint16_t audioFormat = 1; // 1 for PCM 22 | uint16_t numberOfChannels = 2; // 1 - mono, 2 - stereo 23 | uint32_t sampleRate = 44100; 24 | uint32_t byteRate = 44100 * 2 * 2; // sample rate * number of channels * bytes per sample 25 | uint16_t blockAlign = 16 / 8 * 2; // bytes per sample (all channels) 26 | uint16_t bitsPerSample = 16; 27 | char dataStr[4] = {'d', 'a', 't', 'a'}; 28 | uint32_t dataSize; 29 | } wav_header_t; 30 | 31 | typedef struct { 32 | int byte_offset = 44; // = wav header size 33 | uint32_t size; 34 | uint32_t data_size; 35 | float orig_freq; 36 | float speed = 0.0f; // samples 37 | uint8_t orig_velo_layer; 38 | int sample_rate = 44100; 39 | int channels = -1; 40 | int bit_depth = -1; 41 | float amp = 1.0f; 42 | // float attack_time = 0.0f; 43 | // float decay_time = 0.5f; 44 | // float sustain_level = 1.0f; 45 | // float release_time = 12.0f; 46 | int loop_mode = 0; // 0 = none, 1 = forward 47 | int32_t loop_first_smp= -1; 48 | int32_t loop_last_smp = -1; 49 | bool native_freq = false; 50 | // FixedString<4> name; // only used in SamplerEngine::printMapping() 51 | std::vector sectors; 52 | } sample_t; 53 | 54 | class Voice { 55 | public: 56 | Voice(){}; 57 | void init(SDMMC_FAT32* Card, bool* sustain, bool* normalized); 58 | bool allocateBuffers(); 59 | void getSample(float& L, float& R); 60 | inline float interpolate(float& s1, float& s2, float i); 61 | void start(const sample_t nextSmp, uint8_t nextNote, uint8_t nextVelo); 62 | void end(Adsr::eEnd_t); 63 | void fadeOut(); 64 | void feed(); 65 | inline uint32_t hunger(); 66 | inline void setStarted(bool st) {_started = st;} 67 | inline void setPressed(bool pr) {_pressed = pr;} 68 | inline void setPitch(float speedModifier); 69 | inline int getChannels() {return _sampleFile.channels;} 70 | inline bool isActive() {return _active;} 71 | inline bool isDying() {return _dying;} 72 | inline uint8_t getMidiNote() {return _midiNote;} 73 | inline uint8_t getMidiVelo() {return _midiVelo;} 74 | inline uint32_t getBufPlayed() {return _bufPlayed;} 75 | inline float getAmplitude() {return _amplitude;} 76 | inline float getKillScore() ; 77 | inline int getPlayPos() {return _bufPosSmp[_idToPlay];} 78 | inline uint32_t getBufSize() {return _bufSizeSmp;} 79 | inline void toggleBuf(); 80 | inline void setAttackTime(float timeInS) {AmpEnv.setAttackTime(timeInS, 0.0f);} 81 | inline void setDecayTime(float timeInS) {AmpEnv.setDecayTime(timeInS);} 82 | inline void setReleaseTime(float timeInS) {AmpEnv.setReleaseTime(timeInS);} 83 | inline void setSustainLevel(float normLevel){AmpEnv.setSustainLevel(normLevel);} 84 | int my_id =0; 85 | 86 | private: 87 | // some members are volatile because they are used in different tasks on both cores, while real-time conditions require immediate changes without caching 88 | SDMMC_FAT32* _Card ; 89 | bool* _sustain ; // every voice needs to know if sustain is ON. 90 | bool* _normalized ; 91 | float _amp = 1.0f; 92 | bool _active = false; 93 | volatile bool _dying = false; 94 | volatile bool _started = false; 95 | uint8_t* _buffer0; // pointer to the 1st allocated SD-reader buffer 96 | uint8_t* _buffer1; // pointer to the 2nd allocated SD-reader buffer 97 | uint8_t* _playBuffer; // pointer to the buffer which is being played (one of the two toggling buffers) 98 | uint8_t* _fillBuffer; // pointer to the buffer which awaits filling (one of the two toggling buffers) 99 | uint32_t _bufSizeBytes = BUF_SIZE_BYTES; 100 | uint32_t _bufSizeSmp = 0; 101 | int _changedBufBytes = 0; 102 | uint32_t _hunger = 0; 103 | int _bytesToRead = 0; // can be negative 104 | uint32_t _bytesToPlay = 0; 105 | int _playBufOffset = 0; // play-buffer byte offset till the 1st sample 106 | int _fillBufOffset = 0; // fill-buffer byte offset till the 1st sample 107 | volatile int _pL1, _pL2, _pR1, _pR2 ; 108 | int _samplesInFillBuf = 0; 109 | int _samplesInPlayBuf = 0; 110 | volatile int _posSmp = 0; // global position in terms of samples 111 | volatile int _bufPosSmp[2] = {0, 0}; // sample pos, it depends on the number of channels and bit depth of a wav file assigned to this voice; 112 | float _bufPosSmpF = 0.0f; // exact calculated sample reading position including speed, pitchbend etc. 113 | bool _bufEmpty[2] = {true, true}; 114 | uint32_t _fullSampleBytes = 4; // bytes 115 | float _divFileSize = 0.001f; 116 | float _divVelo = 0; 117 | volatile int _idToFill = 0; // tic-tac buffer id 118 | volatile int _idToPlay = 1; 119 | uint8_t _midiNote = 0; 120 | uint8_t _midiVelo = 0; 121 | float _speed = 1.0f; // _speed param corrects the central freq of a sample 122 | float _speedModifier = 1.0f; // pitchbend, portamento etc. 123 | volatile uint32_t _lastSectorRead = 0; // last sector that was read during this sample playback 124 | uint32_t _curChain = 0; // current chain (linear non-fragmented segment of a sample file) index 125 | uint32_t _bufPlayed = 0; // number of buffers played (for float correction) 126 | uint32_t _coarseBytesPlayed = 0; 127 | uint32_t _bytesPlayed = 0; 128 | float _amplitude = 0.0f; 129 | volatile bool _pressed = false; 130 | volatile bool _eof = true; 131 | volatile float _killScoreCoef = 1.0f; 132 | volatile float _hungerCoef = 1.0f; 133 | bool _loop = false; 134 | int _loopState = 0; 135 | uint32_t _loopFirstSmp = 0; 136 | uint32_t _loopLastSmp = 0; 137 | uint32_t _loopFirstSector = 0; 138 | uint32_t _loopLastSector = 0; 139 | int _lowest = 1; 140 | Adsr AmpEnv ; 141 | sample_t _sampleFile ; 142 | }; 143 | -------------------------------------------------------------------------------- /ESP32_SD_Sampler/voice.ino: -------------------------------------------------------------------------------- 1 | #include "voice.h" 2 | 3 | bool Voice::allocateBuffers() { 4 | // heap_caps_print_heap_info(MALLOC_CAP_8BIT); 5 | _buffer0 = (uint8_t*)heap_caps_malloc( BUF_SIZE_BYTES + BUF_EXTRA_BYTES , MALLOC_CAP_INTERNAL); 6 | _buffer1 = (uint8_t*)heap_caps_malloc( BUF_SIZE_BYTES + BUF_EXTRA_BYTES , MALLOC_CAP_INTERNAL); 7 | if( _buffer0 == NULL || _buffer1 == NULL){ 8 | DEBUG("No more RAM for sampler buffer!"); 9 | return false; 10 | } else { 11 | DEBF("%d Bytes RAM allocated for sampler buffers, &_buffer0=%#010x\r\n", BUF_NUMBER * ( BUF_SIZE_BYTES + BUF_EXTRA_BYTES ) , _buffer0); 12 | } 13 | return true; 14 | } 15 | 16 | 17 | void Voice::init(SDMMC_FAT32* Card, bool* sustain, bool* normalized){ 18 | _Card = Card; 19 | _sustain = sustain; 20 | _normalized = normalized; 21 | _speedModifier = 1.0f; 22 | if (!allocateBuffers()) { 23 | DEBUG("VOICE: INIT: NOT ENOUGH MEMORY"); 24 | delay(100); 25 | while(1){;} 26 | } 27 | AmpEnv.init(SAMPLE_RATE); 28 | AmpEnv.end(Adsr::END_NOW); 29 | _active = false; 30 | _midiNote = 255; 31 | _pressed = false; 32 | _eof = true; 33 | } 34 | 35 | 36 | // If the voice is free, it sets the new sample to play 37 | 38 | void Voice::start(const sample_t smpFile, uint8_t midiNote, uint8_t midiVelo) { // executed in Control Task (Core1) 39 | _sampleFile = smpFile; 40 | _bytesToRead = smpFile.size; 41 | _bytesToPlay = smpFile.byte_offset + smpFile.data_size; 42 | _amplitude = 0.0f; 43 | _bytesPlayed = 0; 44 | _coarseBytesPlayed = 0; 45 | _fullSampleBytes = smpFile.channels * smpFile.bit_depth / 8; 46 | _speed = smpFile.speed * _speedModifier; 47 | _bufSizeBytes = BUF_SIZE_BYTES; 48 | _bufSizeSmp = _bufSizeBytes / _fullSampleBytes; 49 | _bufEmpty[0] = true; 50 | _bufEmpty[1] = true; 51 | _bufPosSmp[0] = _bufSizeSmp; 52 | _bufPosSmp[1] = _bufSizeSmp; 53 | _eof = false; 54 | // _bufPlayed = 0; 55 | _fillBuffer = _buffer0; 56 | _playBuffer = _buffer1; 57 | _idToFill = 0; 58 | _idToPlay = 1; 59 | _curChain = 0; 60 | _loop = (smpFile.loop_mode > 0); 61 | if (_loop) { 62 | if (_sampleFile.loop_first_smp >=0 ) { 63 | _loopFirstSmp = smpFile.loop_first_smp; 64 | } else { 65 | _loopFirstSmp = 0; 66 | } 67 | if (_sampleFile.loop_last_smp >=0 ) { 68 | _loopLastSmp = smpFile.loop_last_smp; 69 | } else { 70 | _loopLastSmp = _bytesToPlay / _fullSampleBytes - 1; 71 | } 72 | _loopFirstSector = (smpFile.byte_offset + _fullSampleBytes * _loopFirstSmp ) / BYTES_PER_SECTOR; 73 | _loopLastSector = (smpFile.byte_offset + _fullSampleBytes * _loopLastSmp ) / BYTES_PER_SECTOR; 74 | 75 | } 76 | _pL1 = start_byte[_fullSampleBytes / smpFile.channels]; 77 | _pL2 = _pL1 + _fullSampleBytes; 78 | _pR1 = _pL1 + ( _fullSampleBytes / smpFile.channels ); 79 | _pR2 = _pR1 + _fullSampleBytes; 80 | if (_sampleFile.size == 0) { 81 | _divFileSize = 0.001f; 82 | } else { 83 | _divFileSize = 1.0f/((float)smpFile.data_size); 84 | } 85 | if (midiVelo > 0) { 86 | _divVelo = 1.0f/((float)midiVelo); 87 | } else { 88 | _divVelo = 256.0f; 89 | } 90 | _lastSectorRead = smpFile.sectors[0].first - 1; 91 | _midiNote = midiNote; 92 | _midiVelo = midiVelo; 93 | if (*_normalized) { 94 | _amp = (float)_midiVelo * MIDI_NORM * 0.000033f; 95 | } else { 96 | _amp = 0.000033f; 97 | } 98 | _amp *= smpFile.amp; 99 | _killScoreCoef = (float)_divFileSize * (float)_divVelo; 100 | 101 | // _killScoreCoef = (float)_divFileSize; 102 | _hungerCoef = (float)_fullSampleBytes * (float)_speed; 103 | // DEBF("VOICE %d: START note %d velo %d offset %d\r\n", my_id, midiNote, midiVelo, smpFile.byte_offset); 104 | AmpEnv.retrigger(Adsr::END_NOW); 105 | _active = true; 106 | _dying = false; 107 | _pressed = true; 108 | } 109 | 110 | 111 | void Voice::end(Adsr::eEnd_t end_type){ // most likely being executed in Control Task (Core1) 112 | switch ((int)end_type) { 113 | case Adsr::END_NOW:{ 114 | AmpEnv.end(Adsr::END_NOW); 115 | // DEBF("VOICE %d: END: NOW midi note %d\r\n", my_id, _midiNote); 116 | _active = false; 117 | _midiNote = 255; 118 | _amplitude = 0.0; 119 | break; 120 | } 121 | case Adsr::END_FAST:{ 122 | _dying = true; 123 | AmpEnv.end(Adsr::END_FAST); 124 | // DEBF("VOICE %d: END: FAST %d\r\n", my_id, _midiNote); 125 | break; 126 | } 127 | case Adsr::END_REGULAR: 128 | default:{ 129 | if (!_pressed && !(*_sustain)) { 130 | // DEBF("VOICE %d: END: REGULAR %d\r\n", my_id, _midiNote); 131 | AmpEnv.end(Adsr::END_REGULAR); 132 | } 133 | } 134 | } 135 | } 136 | 137 | void Voice::getSample(float& sampleL, float& sampleR) { 138 | float env; 139 | int bufPosBytes; 140 | float l1, l2, r1, r2; 141 | sampleL = 0.0f; 142 | sampleR = 0.0f; 143 | if (!_active ) return; 144 | if (_bufEmpty[0] && _bufEmpty[1]) { 145 | return; 146 | } else { 147 | env = (float)AmpEnv.process() * (float)_amp ; 148 | // env = _amp; 149 | if (AmpEnv.isIdle()) { 150 | _active = false; 151 | _dying = false; 152 | _midiNote = 255; 153 | _amplitude = 0.0f; 154 | // DEBF("Voice::getSample: note %d active=false\r\n", _midiNote); 155 | return; 156 | } else { 157 | bufPosBytes = (int)_playBufOffset + (int)_fullSampleBytes * (int)_bufPosSmp[_idToPlay ]; // pos in a byte buffer 158 | 159 | //DEBF("pos %d \t posF %f\r\n", _bufPosSmp[_idToPlay ], _bufPosSmpF); 160 | l1 = *( reinterpret_cast( &_playBuffer[ bufPosBytes + _pL1 ] ) ); 161 | l2 = *( reinterpret_cast( &_playBuffer[ bufPosBytes + _pL2 ] ) ); 162 | sampleL = (float)interpolate( l1, l2, _bufPosSmpF ) * (float)env; 163 | 164 | if (_sampleFile.channels == 2){ 165 | r1 = *( reinterpret_cast( &_playBuffer[ bufPosBytes + _pR1 ] ) ); 166 | r2 = *( reinterpret_cast( &_playBuffer[ bufPosBytes + _pR2 ] ) ); 167 | sampleR = (float)interpolate( r1, r2, _bufPosSmpF ) * (float)env; 168 | } else { 169 | sampleR = sampleL; 170 | } 171 | 172 | _bufPosSmpF += (float)_speed ; // * _speedModifier; 173 | _bufPosSmp[_idToPlay ] = _bufPosSmpF; 174 | 175 | _bytesPlayed = _coarseBytesPlayed + (int)_bufPosSmp[_idToPlay] * (int)_fullSampleBytes; 176 | 177 | /* 178 | if ( _bytesPlayed % 16 == 0 ) { 179 | _amplitude = 0.96f * (float)_amplitude + (float)fabs(sampleL) + 0.04f * (float)fabs(sampleR); 180 | } 181 | */ 182 | if ( _bytesPlayed >= _bytesToPlay ) { 183 | end(Adsr::END_NOW); 184 | // DEBF("VOICE %d: DATA END: bytes played = %d , bytes to play = %d , pos in buffer = %d \r\n", my_id, _bytesPlayed , _bytesToPlay, _bufPosSmp[_idToPlay]); 185 | } else { 186 | if (_bufPosSmp[_idToPlay ] > _samplesInPlayBuf ) { 187 | if (_started) toggleBuf(); 188 | } 189 | } 190 | } 191 | } 192 | } 193 | 194 | void Voice::feed() { // executed in Control Task (Core1) 195 | if (_bufEmpty[_idToFill] && !_eof) { 196 | 197 | if (_loop) { 198 | int bytes_till_loop_end = _loopLastSmp * _fullSampleBytes - _bytesPlayed; 199 | float fill_coef = (float)bytes_till_loop_end * (float)DIV_BUF_SIZE_BYTES ; 200 | if (fill_coef >= 1.0f && fill_coef < 1.42f) { // this situation needs correction 201 | // change buffer size to allow looping wav data to be loaded from SD (not just a few bytes to play before rewinding to the loop start) 202 | // divide the remaining bytes appx by half 203 | 204 | } 205 | } 206 | 207 | int sectorsToRead = READ_BUF_SECTORS; 208 | int sectorsAvailable; 209 | volatile uint8_t* bufAddr = _fillBuffer; 210 | volatile uint32_t lastSec, firstSec; 211 | firstSec = lastSec = _lastSectorRead; 212 | // DEBF("VOICE %d: FEED: lastSec before %d", my_id, lastSec); 213 | // DEBF("fill buf addr %d\r\n", bufAddr); 214 | while (sectorsToRead > 0) { 215 | sectorsAvailable = min(_sampleFile.sectors[_curChain].last - lastSec, (uint32_t) sectorsToRead) ; 216 | if (sectorsAvailable > 0) { // we have some sectors in the current chain to read 217 | // DEBF("block available = %d Pointer = %010x\r\n", sectorsAvailable, bufAddr); 218 | _Card->read_block((uint8_t*)bufAddr, lastSec+1, sectorsAvailable); 219 | lastSec += sectorsAvailable; 220 | _bufEmpty[_idToFill] = false; // bufToFill is now filled with the first sectors of sample file 221 | sectorsToRead -= sectorsAvailable; 222 | _bytesToRead -= sectorsAvailable * BYTES_PER_SECTOR; 223 | bufAddr += sectorsAvailable * BYTES_PER_SECTOR; 224 | } else { // we've done with the current chain 225 | if (_curChain + 1 < _sampleFile.sectors.size()) { // we still got some sectors to read 226 | _curChain++; 227 | lastSec = _sampleFile.sectors[_curChain].first - 1; 228 | } else { // this was the last chain of sectors 229 | _eof = true; 230 | break; 231 | } 232 | } 233 | } 234 | // _lastSectorRead could have changed while we were reading here 235 | if (firstSec == _lastSectorRead) { 236 | _lastSectorRead = lastSec; 237 | // copy first bytes of fillBuffer to playBuffer's extra zone for speeding up interpolation on bufToggle 238 | memcpy((void*)(_playBuffer + _bufSizeBytes), (const void*)(_fillBuffer ), BUF_EXTRA_BYTES); 239 | if (!_started) { // init state: bufToFill = 0, bufToPlay = 1 240 | _idToFill = 1; 241 | _idToPlay = 0; 242 | _playBuffer = _buffer0; 243 | _fillBuffer = _buffer1; 244 | _bufPosSmp[_idToFill] = _bufSizeSmp; 245 | _bufPosSmp[_idToPlay] = 0; 246 | _bufPosSmpF = _bufPosSmp[_idToPlay]; 247 | _playBufOffset = _sampleFile.byte_offset ; 248 | _samplesInPlayBuf = (_bufSizeBytes - _playBufOffset) / _fullSampleBytes ; 249 | // DEBF("VOICE %d: FEED-0: pos: %d, inBuf: %d, offset: %d, BPlyd: %d, firstSec %d, lastSec %d \r\n", my_id, _bufPosSmp[_idToPlay ], _samplesInPlayBuf, _playBufOffset, _bytesPlayed, firstSec, _lastSectorRead ); 250 | _started = true; 251 | } else { 252 | _bufPosSmp[_idToFill] = 0; 253 | // _fillBufOffset = ( _fullSampleBytes - ( (BUF_SIZE_BYTES - _playBufOffset) % _fullSampleBytes )) % _fullSampleBytes ; 254 | // _samplesInFillBuf = ((int)BUF_SIZE_BYTES - (int)_fillBufOffset ) / (int)_fullSampleBytes ; 255 | // DEBF("VOICE %d: FEED: pos: %d, inBuf: %d, offset: %d, BPlyd: %d, firstSec %d, lastSec %d \r\n", my_id, _bufPosSmp[_idToPlay ], _samplesInPlayBuf, _playBufOffset, _bytesPlayed, firstSec, _lastSectorRead ); 256 | 257 | } 258 | } else { 259 | DEBUG ("HERE IT IS!!! "); 260 | } 261 | } 262 | } 263 | 264 | 265 | inline void Voice::toggleBuf(){ // Core0 266 | if (!_started ) return; 267 | if (_bufEmpty[_idToFill ]) { 268 | end(Adsr::END_NOW); // O-oh!!! We are late (( 269 | return; 270 | } 271 | int filePosBytes = (int)_coarseBytesPlayed + (int)_playBufOffset + (int)((int)_bufPosSmp[_idToPlay] * (int)_fullSampleBytes); 272 | // _bufPlayed++; 273 | _coarseBytesPlayed += _bufSizeBytes; 274 | _bufEmpty[_idToPlay ] = true; 275 | _bufPosSmpF -= (float)_bufPosSmp[_idToPlay]; 276 | _bufPosSmp[_idToFill] = _bufPosSmpF; 277 | _bytesPlayed = (int)filePosBytes - (int)_sampleFile.byte_offset ; 278 | _playBufOffset = (int)filePosBytes - (int)_coarseBytesPlayed; 279 | _samplesInPlayBuf = ( (int)_bufSizeBytes - (int)_playBufOffset ) / (int)_fullSampleBytes ; 280 | // DEBF("VOICE %d: TOGGLE: pos: %d, inBuf: %d, off: %d, BPlyd: %d, ampl %f lastSec %d \r\n", my_id, _bufPosSmp[_idToPlay ], _samplesInPlayBuf, _playBufOffset, _bytesPlayed, _amplitude, _lastSectorRead ); 281 | switch(_idToPlay ) { 282 | case 0: 283 | _playBuffer = _buffer1; 284 | _fillBuffer = _buffer0; 285 | _idToPlay = 1; 286 | _idToFill = 0; 287 | break; 288 | case 1: 289 | _playBuffer = _buffer0; 290 | _fillBuffer = _buffer1; 291 | _idToPlay = 0; 292 | _idToFill = 1; 293 | } 294 | _bufEmpty[_idToFill ] = true; 295 | // DEBF("&playBuf=%#010x &fillBuf=%#010x\r\n", _buffer0, _fillBuffer); 296 | } 297 | 298 | 299 | 300 | 301 | uint32_t Voice::hunger() { // called by SamplerEngine::fillBuffer() in ControlTask, Core1 302 | if (!_active) return 0; 303 | if ( _eof) return 0; 304 | if ( _dying) return 0; 305 | if (!_bufEmpty[0] && !_bufEmpty[1]) return 0; 306 | return (/*(float)_speedModifier * */(float)_hungerCoef * ((float)_bufPosSmp[0] + (float)_bufPosSmp[1])); // the bigger the value, the sooner we empty the buffer 307 | } 308 | 309 | 310 | // linear interpolation of 2 neighbour values by float index (mantissa only used) 311 | inline float Voice::interpolate(float& v1, float& v2, float index) { 312 | float res; 313 | int32_t i = (int32_t)index; 314 | float f = (float)index - i; 315 | res = (float)f * (float)(v2 - v1) + (float)v1; 316 | return res; 317 | } 318 | 319 | 320 | inline float Voice::getKillScore() { // called by SamplerEngine::assignVoice() and freeSomeVoices in ControlTast, Core1 321 | if (_dying ) return 0.0f; // don't kill twice 322 | return (float)_bytesPlayed * (float)_killScoreCoef ; 323 | } 324 | 325 | 326 | inline void Voice::setPitch(float speedModifier) { // called by SamplerEngine::setPitch, Core1 327 | _speedModifier = speedModifier; 328 | _speed = _sampleFile.speed * speedModifier; 329 | } 330 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 copych 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | !Attention! ESP Arduino cores v.3.1.2 and 3.1.3 have some bug that won't allow i2s driver to install when PSRAM is enabled, please, avoid using these versions! 2 | 3 | # ESP32-S3 SD Sampler 4 | ESP32-S3 SD Sampler is a polyphonic music synthesizer, which can play PCM WAV samples directly from an SD (microSD) card connected to an ESP32-S3. 5 | Simple: one directory = one sample set. Plain text "sampler.ini" manages how samples to be spread over the keyboard. 6 | The main difference, comparing to the projects available on the net, is that this sampler WON'T try to preload all the stuff into the RAM/PSRAM to play it on demand. So it's not limited in this way by the size of the memory chip and can take really huge (per-note true sampled multi-velocity several gigabytes) sample sets. It only requires that the card is freshly formatted FAT32 and has no or very few bad blocks (actually it requires that the WAV files are written with little or no fragmentation at all). On start it analyzes existing file allocation table (FAT) and forms it's own sample lookup table to be able to access data immediately, using SDMMC with a 4-bit wide bus. 7 | 8 | # Features 9 | * Easy to build and to customize Arduino code for ESP32S3 10 | * Hardware would cost you about $15, including a microSD card 11 | * Audio output is 44100Hz 16bit stereo 12 | * Direct read-only access to the sample sets on an SD/microSD card: it's based on custom sdmmc routines and it's fast 13 | * Size of a sample set is only limited by the card size 14 | * Polyphony of 15-20 stereo voices depending on your card's specs 15 | * 16/24 bit WAV files supported 16 | * Melodic and percussive sample sets supported 17 | * ADSR envelope, per-note configurable 18 | * Built-in Reverb FX 19 | * MIDI control currently supports the following messages: 20 | * Note On 21 | * Note Off 22 | * Sustain (CC64) 23 | * Pitchbend 24 | * Attack time (CC73) 25 | * Release time (CC72) 26 | * Decay time (CC75) 27 | * Sustain level (CC76) 28 | * Reverb time (CC87) 29 | * Reverb level (CC88) 30 | * Reverb send (CC91) 31 | * Human readable SAMPLER.INI controls initial parameters of a sample set globally, per-range and per-note 32 | 33 | # YouTube Video 34 | 35 | [![Video](https://img.youtube.com/vi/6Oe6QPwk1ak/maxresdefault.jpg)](https://youtu.be/6Oe6QPwk1ak?feature=shared) 36 | 37 | # How To Build the Thing 38 | * You need ```Arduino IDE``` installed, preferrably, version 1.8.x. (Version 2.x.x will probably also work, but you may face some unexpected issues) 39 | * Next is ```ESP32 Arduino core```, version 2.0.17 seems to be the best choice (as for June 2024) 40 | * Libraries required are: 41 | * ```Fixed string library``` https://github.com/fatlab101/FixedString 42 | * ```Arduino MIDI library``` https://github.com/FortySevenEffects/arduino_midi_library 43 | * [optionally] If you want to use RGB LEDs, then also ```FastLED library``` is needed https://github.com/FastLED/FastLED 44 | * Download the [zipped code](https://github.com/copych/ESP32_S3_Sampler/archive/refs/heads/main.zip) or use git commands to get the project files 45 | * Unpack and place ```ESP32_SD_Sampler``` folder to your Arduino projects directory 46 | * Open ```ESP32_SD_Sampler.ino``` file with Arduino IDE 47 | * Connect your board via USB to your computer, select the corresponding ```port``` in the ```Tools``` menu and press the ```Upload``` button in the Arduino IDE 48 | 49 | # Is it possible to run on ESP32 not S3? 50 | For the time being the sampler only supports S3 variant. 51 | It's possible to rearrange classes in order to allocate members dynamically, and run it on an ESP32, but ESP32 has a strong limitation: memory segmentation (SRAM0, SRAM1, SRAM2 and segments within these parts like BSS, IRAM, DRAM etc) is hardware defined and has different performance. So it's quite a challenge to fit all the objects and buffers in appropriate memory regions. I have tried and managed to compile, but the performance was much worse so I rolled back. If someone would like to, please fork the repository and try. 52 | 53 | 54 | # Polyphony 55 | The maximum number of simultaneously sounding voices mainly depends on the following four factors: 56 | * SD card tech specs 57 | * The size of the allocated per-voice buffers (fast internal RAM required, which is also used by main program and audio effects) 58 | * Required data rate (it depends on sample rates, number of channels and bit depths of both output bus and the sample files) 59 | * CPU and memory performance (in fact, the low-level memory caching routines seem to be the bottleneck for now) 60 | Each of the mentioned parameters has it's own limitations and even more, they are partly compete for the same resources. So the choosen configuration is always a compromise. 61 | 62 | With the microSD cards that I have, my current setting is 17 stereo voices. I now set 7 sectors per read, which gives approx. 5 MB/s reading speed. Combined limitation is per-voice buffer size (i.e. how many sectors we read from the SD per request). The more the size, the more the speed. But the more the size, the more memory we need. In theory, 5 MB/s at 44100 Hz 16 bit stereo should give 29 voices polyphony, so there is probably a room to improve to get more simultaneous voices. But the limitation can also be caused by the computing power and by the internal cache performance. 63 | 64 | PS. Of what I have tested, faster cards won't give you dramatical improvement in the matter of polyphony. I have tried a newer microSD which reads 8 sectors random blocks at apx. 7 MB/s, but only 20 voices I have managed to run at MAX. 65 | 66 | # Velocity layers 67 | There are currently 16 velocity layers (i.e. dynamic variants of each sampled note) which corresponds to the maximum count that I have found (https://freepats.zenvoid.org/Piano/acoustic-grand-piano.html). 68 | 69 | 70 | # SAMPLER.INI and examples 71 | One should put the sampler.ini file to the same folder where the corresponding WAV files are stored. 72 | 73 | Ini syntax is described here: https://github.com/copych/ESP32_S3_Sampler/blob/main/sampler_ini_syntax.md 74 | the doc is under development and will be improved. 75 | 76 | Here is an example of a sampler.ini file for the Salamander Grand Piano WAV-set: 77 | ``` 78 | [sampleset] 79 | title = Salamander Grand Piano 80 | ; type=percussive/melodic 81 | type=melodic 82 | ; Are all the samples of equal loudness? If true then we apply amplification according to the midi note velocity. 83 | normalized=true 84 | ; Overall Amplification 85 | amplify = 1.2 86 | 87 | ; ADSR times in seconds 88 | attackTime = 0.0 89 | decayTime = 0.05 90 | releaseTime = 12.0 91 | ; ADSR sustain level 0.0 - 1.0 92 | sustainLevel = 1.0 93 | 94 | # !!!!!!!!!!!!!!! "enveloped=bool" is no longer supported, as the ADSR is always applyed 95 | 96 | # !!!!!!!!!!!!!!! [envelope] section is no longer supported, [sampleset] now holds global settings 97 | 98 | [filename] 99 | # Filename elements recognized: 100 | ; - note name in sharp (#) or flat(b) notation, i.e. both Eb and D# are valid 101 | ; - octave number 102 | ; - sometimes sample file name contains midi note number 103 | ; - parsed, but not used for now: i.e. some numbers initially used for naming, sorting or whatever 104 | ; velocity layer 105 | ; instruments names (mostly percussion) used in filenames, initially they are collected from this ini file 106 | ; elements without brackets will be treated as some constant string delimeters 107 | 108 | ; these elements are case insensitive, heading and trailing spaces are trimmed. 109 | template=v 110 | 111 | ; we must provide these variants along with the template. The order is important: from the most quiet 112 | ; to the most loud, comma separated 113 | veloVariants = 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 114 | 115 | ; in addition we can provide optional info on how velocity layers shall be spread over 116 | ; the whole range of 0-127, giving the upper limits of each range 117 | veloLimits = 26,34,36,43,46,50,56,64,72,80,88,96,104,112,121,127 118 | 119 | [range] 120 | # notes without dampers 121 | first = F#6 122 | last = G8 123 | noteoff = false 124 | 125 | # [note] sections 126 | ; instr=instrument_name(as in filename) 127 | ; noteoff=0/1 (0=ignore, 1=end note) 128 | ; speed=float_number(1.0 means unchanged (default), 1.2 means 20% faster which is higher pitch, 0.9 is 10% lower) 129 | ``` 130 | 131 | another sampler.ini example is for a drumkit MuldjordKit-SFZ-20201018, all the samples moved to a single folder 132 | ``` 133 | [sampleset] 134 | title = Generic Drum Kit 135 | 136 | ; type=percussive/melodic 137 | ; different technics used while spreading samples over the clavier 138 | ; percussive type allows per-note fx-send level 139 | type=percussive 140 | 141 | ; Are all the samples of equal loudness? If true then we apply amplification according to the midi note velocity. 142 | normalized=true 143 | 144 | ; ADSR times in seconds 145 | attackTime = 0.0 146 | decayTime = 0.05 147 | releaseTime = 0.2 148 | ; ADSR sustain level 0.0 - 1.0 149 | sustainLevel = 1.0 150 | 151 | # !!!!!!!!!!!!!!! "enveloped=bool" is no longer supported, as the ADSR is always applyed 152 | 153 | # !!!!!!!!!!!!!!! [envelope] section is no longer supported, [sampleset] now holds global settings 154 | 155 | [filename] 156 | # Filename elements recognized: 157 | ; - note name=name in sharp (#) or flat(b) notation, i.e. both Eb and D# are valid 158 | ; - octave number 159 | ; - sometimes sample file name contains midi note number 160 | ; - parsed, but not used for now: i.e. some numbers initially used for naming, sorting or whatever 161 | ; velocity layer 162 | ; instruments names (mostly percussion) used in filenames, initially they are collected from this ini file 163 | ; only instrument names mentioned in this ini (in [note] or [range] sections) will be recognized 164 | ; elements without brackets will be treated as some constant string delimeters 165 | ; these elements are case insensitive, heading and trailing spaces are trimmed. 166 | ; template should be specified first in this section 167 | template=- 168 | 169 | ; we must provide these variants. The order is important: from the most quiet to the most loud, comma separated 170 | veloVariants = 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 171 | 172 | 173 | [group] 174 | notes = F#1,A#1,G#1 175 | 176 | notes = G#2,A2 177 | 178 | notes = C#2,D2 179 | 180 | 181 | # [note] sections 182 | ; name = notename in sharp (#) or flat(b) notation, i.e. both Eb and D# are valid 183 | ; instr = instrument_name(as in filename) 184 | ; noteoff = 0/1 (0 is 'ignore', 1 is 'end note'), also yes/no, true/false are recognized 185 | ; speed = float_number (1.0 means unchanged (default), 1.2 means 20% faster which is higher pitch, 0.9 is 10% lower) 186 | ; only instrument names mentioned in this ini (in [note] or [range] sections) will be recognized 187 | 188 | [note] 189 | name=b0 190 | instr=kdrumL 191 | noteoff=false 192 | 193 | [note] 194 | name=c1 195 | instr=kdrumR 196 | noteoff=no 197 | 198 | [note] 199 | name=C#1 200 | instr=SnareRest 201 | noteoff=0 202 | 203 | [note] 204 | name=D1 205 | instr=Snare 206 | noteoff=yes 207 | 208 | [note] 209 | name=D#1 210 | ; assuming we have "#-SnareRest (2).WAV" files in the drumkit folder after copying all the files into a single dir. 211 | instr=SnareRest (2) 212 | noteoff=true 213 | 214 | [note] 215 | name=E1 216 | ; assuming we have "#-Snare (2).WAV" files in the drumkit folder after copying all the files into a single dir. 217 | instr=snare (2) 218 | noteoff=0 219 | 220 | [note] 221 | name=F1 222 | instr=Tom1 223 | noteoff=0 224 | 225 | [note] 226 | name=G1 227 | instr=Tom2 228 | noteoff=0 229 | 230 | [note] 231 | name=A1 232 | instr=Tom3 233 | noteoff=0 234 | 235 | [note] 236 | name=B1 237 | instr=Tom4 238 | noteoff=0 239 | 240 | [note] 241 | name=C2 242 | instr=Tom4 243 | noteoff=0 244 | speed=1.15 245 | 246 | [note] 247 | name=F#1 248 | instr=HiHatClosed 249 | noteoff=0 250 | 251 | [note] 252 | name=A#1 253 | instr=HiHatOpen 254 | noteoff=1 255 | 256 | [note] 257 | name=C#2 258 | instr=CrashL 259 | noteoff=0 260 | 261 | [note] 262 | name=D2 263 | instr=CrashL 264 | noteoff=true 265 | 266 | [note] 267 | name=E2 268 | instr=China 269 | noteoff=0 270 | 271 | [note] 272 | name=G#2 273 | instr=CrashR 274 | noteoff=0 275 | 276 | [note] 277 | name=A2 278 | instr=CrashR 279 | noteoff=yes 280 | 281 | [note] 282 | name=D#2 283 | instr=RideL 284 | noteoff=0 285 | 286 | [note] 287 | name=B2 288 | instr=RideR 289 | noteoff=0 290 | 291 | [note] 292 | name=F2 293 | instr=RideLBell 294 | noteoff=0 295 | 296 | [note] 297 | name=A#2 298 | instr=RideRBell 299 | noteoff=0 300 | ``` 301 | -------------------------------------------------------------------------------- /media/2024-05-05 12-37-57.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copych/ESP32_S3_Sampler/a78a30cf209ceb0fe3e58497b6d368d516021512/media/2024-05-05 12-37-57.JPG -------------------------------------------------------------------------------- /media/LOLIN S3 PRO mod.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copych/ESP32_S3_Sampler/a78a30cf209ceb0fe3e58497b6d368d516021512/media/LOLIN S3 PRO mod.JPG -------------------------------------------------------------------------------- /media/README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sampler_ini_syntax.md: -------------------------------------------------------------------------------- 1 | # SAMPLER.INI syntax 2 | ## General 3 | Directories in the root folder containing SAMPLER.INI file are considered as containing sample sets. 4 | Sampler.ini file is a human readable plain text file, the syntax is as following, and is subject to develop. 5 | Also, please, note that some parameters are parsed, but not implemented yet. 6 | 7 | * Due to using fixed strings, a line length shouldn't exceed 252 symbols. 8 | * Quotes are not used. 9 | * ```Boolean``` values can be represented by the following strings: yes, no, true, false, 1, 0, y, n, none 10 | * ```Integer``` values should be numbers with no commas or points, i.e. 127 or -1 11 | * ```Float``` values should contain a point, e.g. 127.0 or -1.001 12 | * ```Strings``` are used without quotes. Leading and trailing spaces are trimmed. E.g. value of ```param = a b c ``` would be parsed as "a b c" 13 | 14 | ## Comment lines 15 | Comments are started with a sharp sign (#) or a semicolon (;) 16 | 17 | ## Section [SAMPLESET] 18 | This section contains global parameters: 19 | * title = ```string``` 20 | * type = ```melodic``` or ```percussive``` 21 | * normalized = ```boolean``` 22 | * enveloped = ```boolean``` no longer supported 23 | * amplify = ```float``` 24 | * max_voices = ```integer``` 25 | * limit_same_notes = ```integer``` 26 | * attack_time = ```float``` 27 | * decay_time = ```float``` 28 | * release_time = ```float``` 29 | * sustain_level = ```float``` 30 | 31 | ## Section [FILENAME] 32 | This section describes how the WAV files are self-mapped basing on the info parsed out of their filenames 33 | * template = ```string``` 34 | * `````` - note name in sharp (#) or flat(b) notation, i.e. both Eb and D# are valid 35 | * `````` - octave number 36 | * `````` - midi note number (either ```note name and octave``` or ```midi number``` should be used, not both of them) 37 | * `````` - parsed, but not used for now: i.e. some numbers initially used for naming, sorting or whatever 38 | * `````` - velocity layer 39 | * `````` - instruments names (mostly percussion) used in filenames, initially they are collected from this ini file 40 | * all other elements without brackets will be treated as some constant string delimeters 41 | * e.g. for the files named like ```065_F#2_Mid.wav``` we would use ```template = __```, where ```065``` is some number (unused), ```F#``` is the note name, ```2``` is an octave number, ```_``` are just separators, and ```Mid``` is a velocity layer 42 | * velo_variants = ```comma separated strings``` 43 | * we list the variants that are present in the filenames e.g. ```velo_variants = Soft,Mid,Hard``` 44 | * velo_limits = ```comma separated integers``` 45 | * optional, we list the upper limits of each velocity layer e.g. ```velo_limits = 40,96,127``` 46 | 47 | ## Section [ENVELOPE] no longer supported, moved to [sampleset] 48 | 49 | ## Sections [RANGE] and [NOTE] 50 | You should provide either 51 | * name = ```string``` - note name in sharp (#) or flat(b) notation, i.e. both Eb and D# are valid 52 | or borders: 53 | * first = ```string``` - first note of the range 54 | * last = ```string``` - last note of the range 55 | Here is the supported parameters list: 56 | * instr = ```string``` - this should be the instrument names used in your samples' filenames 57 | * noteoff = ```boolean``` - if the note/range is triggered by noteOff events 58 | * speed = ```float``` - 'tuning' 59 | * attack_time = ```float``` - ADSR attack time 60 | * decay_time = ```float``` - ADSR decay time 61 | * release_time = ```float``` - ADSR release time 62 | * sustain_level = ```float``` - ADSR sustain level [0.0 .. 1.0] 63 | * limit_same_notes = ```integer``` - limits number of simultaneous same notes 64 | 65 | ## Section [GROUP] 66 | * notes = ```comma separated strings``` - exclusive groups are used to imitate the behavior of real instruments, e.g. only one of "closed hat" or "open hat" can sound at a time. Example: ```notes = F#1, G#1, A#1``` 67 | --------------------------------------------------------------------------------