From 742e876d3393dcab847e12f9a214621ee1b502aa Mon Sep 17 00:00:00 2001 From: copych <70332557+copych@users.noreply.github.com> Date: Thu, 9 Feb 2023 11:18:12 +0300 Subject: [PATCH] Cleanup; Both midi-in's can run simultaneously Core tasks swapped, because Core1 is also running Arduino and Interrupts. 32 samples DMA buffer length timing is ~725us per buffer, Synth2 + Drums + Mixer + Effects ~500us, and we can double number of synth buffers to compact tasks even more. --- AcidBanger.ino | 214 ++++++----- AcidBox.ino | 279 +++++++------- config.h | 39 +- fx_delay.h | 1 + fx_filtercrusher.h | 298 ++++----------- fx_filtercrusher.ino | 120 ++++++ general.ino | 13 +- i2s_setup.ino | 32 +- moogladder.ino | 2 +- overdrive.ino | 18 +- rosic_TeeBeeFilter.ino | 8 +- sampler.h | 4 +- sampler.ino | 813 +++++++++++++++++++++-------------------- synthvoice.h | 11 +- synthvoice.ino | 329 +++++++++-------- tables.ino | 31 +- 16 files changed, 1149 insertions(+), 1063 deletions(-) create mode 100644 fx_filtercrusher.ino diff --git a/AcidBanger.ino b/AcidBanger.ino index c05691b..632364a 100644 --- a/AcidBanger.ino +++ b/AcidBanger.ino @@ -45,11 +45,12 @@ // // =============================== // 2023 edit by Copych -// added auto CC ramps +// added auto CC ramps for reso, cutoff etc. // added drum parts categorizing -// added auto breaks/fills +// added auto breaks/fills +// added crash cymbal // removed/modified buttons processing -// +// #define KICK_NOTE 0 //001 #define SNARE_NOTE 1 //002 @@ -70,7 +71,7 @@ #define MEM4_BUTTON 23 #define MEM5_BUTTON 23 -#define send_midi_start() {} +#define send_midi_start() {} #define send_midi_stop() {} #define send_midi_tick() {} @@ -80,13 +81,13 @@ #define NUM_RAMPS 6 // simultaneous knob rotatings #ifndef NO_PSRAM - #define NUM_SYNTH_CCS 11 // how many synth CC params do we have to play - #define NUM_DRUM_CCS 7 // how many drum CC params do we have to play - #define VOL_SYNTH 80 +#define NUM_SYNTH_CCS 11 // how many synth CC params do we have to play +#define NUM_DRUM_CCS 7 // how many drum CC params do we have to play +#define VOL_SYNTH 80 #else - #define NUM_SYNTH_CCS 10 // how many synth CC params do we have to play - #define NUM_DRUM_CCS 5 // how many drum CC params do we have to play - #define VOL_SYNTH 60 +#define NUM_SYNTH_CCS 10 // how many synth CC params do we have to play +#define NUM_DRUM_CCS 5 // how many drum CC params do we have to play +#define VOL_SYNTH 60 #endif struct sSynthCCs { @@ -95,18 +96,18 @@ struct sSynthCCs { uint8_t cc_default_value; uint8_t cc_min_value; uint8_t cc_max_value; - bool reset_after_use; + bool reset_after_use; }; sSynthCCs synth1_ramps[NUM_SYNTH_CCS] = { - //cc cpl def min max reset + //cc cpl def min max reset {CC_303_RESO, CC_303_CUTOFF, 64, 40, 125, true}, {CC_303_CUTOFF, CC_303_RESO, 20, 5, 120, true}, {CC_303_PAN, 0, 47, 0, 127, true}, {CC_303_ENVMOD_LVL, 0, 100, 0, 127, false}, {CC_303_WAVEFORM, 0, 0, 0, 64, true}, #ifndef NO_PSRAM - {CC_303_REVERB_SEND,0, 5, 2, 127, true}, + {CC_303_REVERB_SEND, 0, 5, 2, 127, true}, #endif {CC_303_DELAY_SEND, 0, 0, 64, 127, false}, {CC_303_DISTORTION, 0, 0, 2, 127, true}, @@ -116,14 +117,14 @@ sSynthCCs synth1_ramps[NUM_SYNTH_CCS] = { }; sSynthCCs synth2_ramps[NUM_SYNTH_CCS] = { - //cc cpl def min max reset + //cc cpl def min max reset {CC_303_RESO, CC_303_CUTOFF, 64, 60, 127, true}, {CC_303_CUTOFF, CC_303_RESO, 20, 0, 120, false}, {CC_303_PAN, 0, 80, 0, 127, true}, {CC_303_ENVMOD_LVL, 0, 100, 0, 127, false}, {CC_303_WAVEFORM, 0, 127, 64, 127, true}, #ifndef NO_PSRAM - {CC_303_REVERB_SEND,0, 5, 2, 127, true}, + {CC_303_REVERB_SEND, 0, 5, 2, 127, true}, #endif {CC_303_DELAY_SEND, 0, 0, 64, 127, false}, {CC_303_OVERDRIVE, 0, 0, 2, 127, true}, @@ -133,16 +134,16 @@ sSynthCCs synth2_ramps[NUM_SYNTH_CCS] = { }; sSynthCCs drum_ramps[NUM_DRUM_CCS] = { - //cc cpl def min max reset + //cc cpl def min max reset {CC_808_CUTOFF, 0, 64, 64, 127, true}, - {CC_808_SD_TONE, 0, 64, 64, 127, true}, {CC_808_RESO, 0, 0, 0, 127, true}, + {CC_808_SD_TONE, 0, 64, 64, 127, true}, #ifndef NO_PSRAM {CC_808_REVERB_SEND, 0, 5, 30, 127, true}, // reverb is not available with no psram {CC_808_DELAY_SEND, 0, 0, 64, 127, true}, // delay for drums needs more delay time (read 'RAM') than we can afford #endif - {CC_808_BD_DECAY, 0, 64, 50, 90, true}, - {CC_808_SD_TONE, 0, 64, 40, 70, true} + {CC_808_BD_DECAY, 0, 127, 50, 127, true}, + {CC_808_BD_TONE, 0, 64, 40, 64, true} }; struct sMidiRamp { @@ -158,7 +159,7 @@ struct sMidiRamp { } midiRamps[NUM_RAMPS]; -typedef enum drum_kinds{ +typedef enum drum_kinds { DrumBreak, DrumStraight, DrumHang, @@ -247,7 +248,7 @@ struct Instrument { static Instrument instruments[NumInstruments]; -static uint32_t bar_current = 0; // it counts bars +static uint32_t bar_current = 0; // it counts bars struct sBreak { @@ -307,11 +308,11 @@ static void send_midi_noteoff(byte chan, byte note) { handleNoteOff( chan, note, 0) ; } static void init_midi() { -// Serial.begin(115200); -// MIDI.begin(MIDI_CHANNEL_OMNI); + // Serial.begin(115200); + // MIDI.begin(MIDI_CHANNEL_OMNI); pinMode(LED_BUILTIN, OUTPUT); for (byte i = 0; i < ButLast; i++) { - init_button(&buttons[i], button_pins[i], i+1 ); + init_button(&buttons[i], button_pins[i], i + 1 ); } init_instruments(); init_patterns(); @@ -320,24 +321,24 @@ static void init_midi() { #endif } static void send_midi_control(byte chan, byte cc_number, byte cc_value) { - //MIDI.sendControlChange ( cc_number, cc_value, chan); + //MIDI.sendControlChange ( cc_number, cc_value, chan); handleCC( chan, cc_number, cc_value); } /* - * Pseudo-random generator with restorable state - */ + Pseudo-random generator with restorable state +*/ static inline uint16_t lfsr16_next(uint16_t x) { - uint16_t y = x >> 1; - if (x & 1) - y ^= 0xb400; - return y; + uint16_t y = x >> 1; + if (x & 1) + y ^= 0xb400; + return y; } //static uint16_t myRandomState = 0x1234; -static uint16_t myRandomState = (uint16_t)(random(0,0xffff)); +static uint16_t myRandomState = (uint16_t)(random(0, 0xffff)); static uint16_t myRandomAddEntropy(uint16_t data) { myRandomState = lfsr16_next((myRandomState << 1) ^ data); @@ -354,8 +355,8 @@ static inline uint16_t myRandom(uint16_t max) { } /* - * Buttons - */ + Buttons +*/ static void read_button(struct Button *button) { @@ -375,8 +376,8 @@ static void init_button(struct Button *button, byte pin, uint8_t num) } /* - * Instruments - */ + Instruments +*/ static void instr_noteoff(byte instr) { Instrument *ins = &instruments[instr]; @@ -429,8 +430,8 @@ static void instr_noteon(byte instr, byte value, byte do_glide, byte do_accent) } /* - * Sequencer - */ + Sequencer +*/ void sequencer_step(byte step) { do_midi_ramps(); @@ -450,16 +451,16 @@ void sequencer_step(byte step) { instr_noteoff(i); } if (Break.after == bar_current && step == 0) { - instr_noteon(NumInstruments-1, 127, 0, 0); -// instr_noteon_raw(NumInstruments-1, CRASH_NOTE, 127, 0); -#ifdef DEBUG_JUKEBOX + instr_noteon(NumInstruments - 1, 127, 0, 0); + // instr_noteon_raw(NumInstruments-1, CRASH_NOTE, 127, 0); +#ifdef DEBUG_JUKEBOX DEBUG("CRASH!!!!!!!!!!!!!!!!!!!!!"); #endif check_midi_ramps(true); } - if (step % 4 == 0 || step == 1) { + if (step % 4 == 0 || step == 1) { digitalWrite(LED_BUILTIN, HIGH); -#ifdef DEBUG_JUKEBOX +#ifdef DEBUG_JUKEBOX DEBUG(step); #endif } else { @@ -468,11 +469,11 @@ void sequencer_step(byte step) { } /* - * Endless Acid Banger pattern generator - * - * Adapted from https://www.vitling.xyz/toys/acid-banger/ - * created by Vitling (David Whiting) i.am@thewit.ch - */ + Endless Acid Banger pattern generator + + Adapted from https://www.vitling.xyz/toys/acid-banger/ + created by Vitling (David Whiting) i.am@thewit.ch +*/ #define NOTE_LIST(x...) (int8_t[]) { x, -1 } //#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) @@ -483,8 +484,10 @@ static const int8_t *const offset_choices[] = { NOTE_LIST(0, 1, 7, 10, 12, 13), NOTE_LIST(0), NOTE_LIST(0, 0, 0, 12), + NOTE_LIST(0, 0, 12, 12, 18, 24, 24), + NOTE_LIST(0, 0, 7, 14, 24, 24), NOTE_LIST(0, 0, 12, 14, 15, 19), - NOTE_LIST(0, 0, 0, 0, 12, 13, 16, 19, 22, 24, 25), + NOTE_LIST(0, 0, 0, 12, 12, 13, 16, 19, 22, 24, 25), NOTE_LIST(0, 0, 0, 7, 12, 15, 17, 20, 24), }; @@ -513,8 +516,8 @@ static byte flip(byte percent_chance) { } static void generate_melody(uint8_t *note_set, byte note_set_len, - uint8_t *pattern, byte pattern_len, - uint16_t *accent, uint16_t *glide) { + uint8_t *pattern, byte pattern_len, + uint16_t *accent, uint16_t *glide) { uint8_t density = 255; @@ -547,7 +550,7 @@ static void generate_drums(byte *kick, byte *snare, byte *oh, byte *ch, byte *pe byte hat_mode = HatsNone; byte snare_mode = SnareNone; byte perc_mode = PercNone; - + byte rndVal ; switch (drum_kind) { case DrumBreak: @@ -559,17 +562,17 @@ static void generate_drums(byte *kick, byte *snare, byte *oh, byte *ch, byte *pe } else { kick_mode = KickNone; } - + rndVal = myRandom(100); if (rndVal < 40) { snare_mode = SnareFill; } else if (rndVal < 80) { snare_mode = SnareBreak; } else { - snare_mode = SnareBackbeat; + snare_mode = SnareBackbeat; } - - hat_mode = myRandom(HatsNone); + + hat_mode = myRandom(HatsNone); perc_mode = myRandom(PercNone); break; case DrumStraight: @@ -579,17 +582,17 @@ static void generate_drums(byte *kick, byte *snare, byte *oh, byte *ch, byte *pe } else { kick_mode = KickFourFloor; } - + rndVal = myRandom(100); if (rndVal < 60) { snare_mode = SnareStraight; } else { snare_mode = SnareBackbeat; } - + if (flip(70)) hat_mode = HatsPop; - else hat_mode = myRandom(HatsNone); - + else hat_mode = myRandom(HatsNone); + perc_mode = myRandom(PercNone); break; case DrumHang: @@ -599,19 +602,19 @@ static void generate_drums(byte *kick, byte *snare, byte *oh, byte *ch, byte *pe } else { kick_mode = KickNone; } - + rndVal = myRandom(100); if (rndVal < 60) { snare_mode = SnareStraight; } else { snare_mode = SnareBackbeat; } - - hat_mode = myRandom(HatsNone); + + hat_mode = myRandom(HatsNone); perc_mode = myRandom(PercNone); break; case DrumNone: - // nothing + // nothing break; case DrumAny: default: @@ -620,7 +623,7 @@ static void generate_drums(byte *kick, byte *snare, byte *oh, byte *ch, byte *pe snare_mode = myRandom(SnareNone); perc_mode = myRandom(PercNone); } - + if (kick_mode == KickFourFloor) { for (byte i = 0; i < PatternLength; i++) { @@ -652,7 +655,7 @@ static void generate_drums(byte *kick, byte *snare, byte *oh, byte *ch, byte *pe } } else if (snare_mode == SnareFill) { for (byte i = 0; i < PatternLength; i++) { - snare[i] = 120; + snare[i] = 120; } } else if (snare_mode == SnareStraight) { for (byte i = 0; i < PatternLength; i++) { @@ -767,8 +770,8 @@ static void generate_drums(byte *kick, byte *snare, byte *oh, byte *ch, byte *pe } /* - * Generator-to-pattern binding - */ + Generator-to-pattern binding +*/ void mem_generate_melody(byte mem, byte voice) { Memory *m = &memories[mem]; @@ -824,7 +827,7 @@ void mem_generate_all(byte mem) { void print_pattern(struct Pattern *p, byte is_drum) { #ifdef DEBUG_JUKEBOX for (byte i = 0; i < PatternLength; i++) - DEBF("%3d ", p->notes[i]); + DEBF("%3d ", p->notes[i]); if (!is_drum) { for (byte i = 0; i < PatternLength; i++) @@ -853,8 +856,8 @@ void init_patterns() { } /* - * MIDI clock - */ + MIDI clock +*/ #define MIDI_TICKS_PER_16TH 6 @@ -867,10 +870,10 @@ static void decide_on_break() { // plan a break ? if ( bars_played == 28 ) { // 100% 1-bar break - Break.status = sPlaying; - Break.start = bar_current ; - Break.length = 4; - Break.after = Break.start + Break.length; + Break.status = sPlaying; + Break.start = bar_current ; + Break.length = 4; + Break.after = Break.start + Break.length; } else if ( bars_played == 15 ) { // 50% 1-bar break if (flip(50)) { @@ -1007,9 +1010,9 @@ static void check_midi_ramps(boolean force_restart) { if (midiRamps[i].need_reset) { send_midi_control(midiRamps[i].chan, midiRamps[i].cc_number, midiRamps[i].def_val); } - uint8_t chanSeed = random(0,100); // probability + uint8_t chanSeed = random(0, 100); // probability uint8_t ccSeed; - if (chanSeed<45) { + if (chanSeed < 45) { do { ccSeed = random(0, NUM_SYNTH_CCS); } while (ramp_cc_repeated(synth1_ramps[ccSeed].cc_number, SYNTH1_MIDI_CHAN)); @@ -1020,9 +1023,11 @@ static void check_midi_ramps(boolean force_restart) { midiRamps[i].def_val = synth1_ramps[ccSeed].cc_default_value; midiRamps[i].need_reset = synth1_ramps[ccSeed].reset_after_use; midiRamps[i].value = synth1_ramps[ccSeed].cc_default_value; - midiRamps[i].stepPer16th = (float)(random(-100,100))*0.05f ; - if (abs(midiRamps[i].stepPer16th) < 0.5 ) {midiRamps[i].stepPer16th = 0.5;} - midiRamps[i].leftBars = random(1,3)*2; + midiRamps[i].stepPer16th = (float)(random(-100, 100)) * 0.05f ; + if (abs(midiRamps[i].stepPer16th) < 0.5 ) { + midiRamps[i].stepPer16th = 0.5; + } + midiRamps[i].leftBars = random(1, 3) * 2; } else if (chanSeed < 86) { do { ccSeed = random(0, NUM_SYNTH_CCS); @@ -1034,9 +1039,11 @@ static void check_midi_ramps(boolean force_restart) { midiRamps[i].def_val = synth2_ramps[ccSeed].cc_default_value; midiRamps[i].need_reset = synth2_ramps[ccSeed].reset_after_use; midiRamps[i].value = synth2_ramps[ccSeed].cc_default_value; - midiRamps[i].stepPer16th = (float)(random(-100,100))*0.05f ; - if (abs(midiRamps[i].stepPer16th) < 0.5 ) {midiRamps[i].stepPer16th = 0.5;} - midiRamps[i].leftBars = random(1,3)*2; + midiRamps[i].stepPer16th = (float)(random(-100, 100)) * 0.05f ; + if (abs(midiRamps[i].stepPer16th) < 0.5 ) { + midiRamps[i].stepPer16th = 0.5; + } + midiRamps[i].leftBars = random(1, 3) * 2; } else { do { ccSeed = random(0, NUM_DRUM_CCS); @@ -1048,9 +1055,11 @@ static void check_midi_ramps(boolean force_restart) { midiRamps[i].def_val = drum_ramps[ccSeed].cc_default_value; midiRamps[i].need_reset = drum_ramps[ccSeed].reset_after_use; midiRamps[i].value = drum_ramps[ccSeed].cc_default_value; - midiRamps[i].stepPer16th = (float)(random(-100,100))*0.1f ; - if (abs(midiRamps[i].stepPer16th) < 0.5 ) {midiRamps[i].stepPer16th = 0.5;} - midiRamps[i].leftBars = random(1,3) ; + midiRamps[i].stepPer16th = (float)(random(-100, 100)) * 0.1f ; + if (abs(midiRamps[i].stepPer16th) < 0.5 ) { + midiRamps[i].stepPer16th = 0.5; + } + midiRamps[i].leftBars = random(1, 3) ; } } } @@ -1058,13 +1067,13 @@ static void check_midi_ramps(boolean force_restart) { if (bar_current == Break.start) { for (int i = 0; i < NUM_RAMPS; i++) { /* - if (midiRamps[i].need_reset) { + if (midiRamps[i].need_reset) { send_midi_control(midiRamps[i].chan, midiRamps[i].cc_number, midiRamps[i].def_val); - } + } */ midiRamps[i].leftBars = Break.length; midiRamps[i].value = midiRamps[i].min_val; - midiRamps[i].stepPer16th = ((float)(midiRamps[i].max_val-midiRamps[i].min_val)/(float)(Break.length*PatternLength)); + midiRamps[i].stepPer16th = ((float)(midiRamps[i].max_val - midiRamps[i].min_val) / (float)(Break.length * PatternLength)); } } } // if Break.status @@ -1079,7 +1088,7 @@ static void do_midi_tick() { if (midi_step >= PatternLength) { bar_current++; midi_step = 0; - decide_on_break(); + decide_on_break(); check_midi_ramps(false); // every bar } sequencer_step(midi_step); @@ -1093,8 +1102,8 @@ static void do_midi_stop() { } /* - * Instrument definition - */ + Instrument definition +*/ static const byte drum_notes[6] = { KICK_NOTE, SNARE_NOTE, CLOSED_HAT_NOTE, OPEN_HAT_NOTE, PERCUSSION_NOTE, CRASH_NOTE }; static const byte synth_midi_channels[2] = { SYNTH1_MIDI_CHAN, SYNTH2_MIDI_CHAN }; @@ -1123,18 +1132,18 @@ static void init_instruments() { } /* - * Main program - */ + Main program +*/ /* -void setup() { + void setup() { init_midi(); for (byte i = 0; i < ButLast; i++) { init_button(&buttons[i], button_pins[i]); } init_instruments(); init_patterns(); -} + } */ void start_midi_clock() { @@ -1214,6 +1223,9 @@ void run_tick() { } run_ui(); button_divider = 0; + + // DEBF ("synt1=%dus synt2=%dus drums=%dus mixer=%dus DMA_BUF=%dus\r\n" , s1T, s2T, drT, fxT, DMA_BUF_TIME); + DEBF ("Core0=%dus Core1=%dus DMA_BUF=%dus\r\n" , s2T + drT + fxT, s1T, DMA_BUF_TIME); } /* If MIDI is playing, then check for tick */ @@ -1228,18 +1240,18 @@ void run_tick() { } /* -static unsigned long last_ms = 0; + static unsigned long last_ms = 0; -void loop() { - // wait until next ms + void loop() { + // wait until next ms do { now = millis(); } while (now == last_ms); last_ms = now; - // advance one tick + // advance one tick run_tick(); myRandomAddEntropy(analogRead(0)); -} + } */ #endif diff --git a/AcidBox.ino b/AcidBox.ino index 408d22e..0db8a78 100644 --- a/AcidBox.ino +++ b/AcidBox.ino @@ -1,19 +1,19 @@ /* -* -* AcidBox -* ESP32 headless acid combo of 2 x 303 + 1 x 808 like synths. MIDI driven. I2S output. No indication. Uses both cores of ESP32. -* -* To build the thing -* You will need an ESP32 with PSRAM (ESP32 WROVER module). Preferrable an external DAC, like PCM5102. In ArduinoIDE Tools menu select: -* + + AcidBox + ESP32 headless acid combo of 303 + 303 + 808 like synths. MIDI driven. I2S output. No indication. Uses both cores of ESP32. + + To build the thing + You will need an ESP32 with PSRAM (ESP32 WROVER module). Preferrable an external DAC, like PCM5102. In ArduinoIDE Tools menu select: + * * Board: ESP32 Dev Module * * Partition scheme: No OTA (1MB APP/ 3MB SPIFFS) * * PSRAM: enabled -* -* Also you will need to upload samples from /data folder to the ESP32 flash. To do so follow the instructions: -* https://github.com/lorol/LITTLEFS#arduino-esp32-littlefs-filesystem-upload-tool -* And then use Tools -> ESP32 Sketch Data Upload -* + + Also you will need to upload samples from /data folder to the ESP32 flash. To do so follow the instructions: + https://github.com/lorol/LITTLEFS#arduino-esp32-littlefs-filesystem-upload-tool + And then use Tools -> ESP32 Sketch Data Upload + */ #include "config.h" @@ -21,48 +21,51 @@ #include "driver/i2s.h" #include "fx_delay.h" #ifndef NO_PSRAM - #include "fx_reverb.h" +#include "fx_reverb.h" #endif #include "compressor.h" #include "synthvoice.h" #include "sampler.h" #include -#ifdef MIDI_ON - #include - #ifdef MIDI_VIA_SERIAL - // default settings for Hairless midi is 115200 8-N-1 - struct CustomBaudRateSettings : public MIDI_NAMESPACE::DefaultSerialSettings { - static const long BaudRate = 115200; - }; - MIDI_NAMESPACE::SerialMIDI serialMIDI(Serial); - MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); - #else - // MIDI port on UART2, pins 16 (RX) and 17 (TX) prohibited, as they are used for PSRAM - - struct Serial2MIDISettings : public midi::DefaultSettings{ - static const long BaudRate = 31250; - static const int8_t RxPin = MIDIRX_PIN; - static const int8_t TxPin = MIDITX_PIN; - }; - - HardwareSerial MIDISerial(2); - MIDI_CREATE_CUSTOM_INSTANCE( HardwareSerial, MIDISerial, MIDI, Serial2MIDISettings ); - - #endif +#if defined MIDI_VIA_SERIAL2 || defined MIDI_VIA_SERIAL +#include +#endif + +#ifdef MIDI_VIA_SERIAL +// default settings for Hairless midi is 115200 8-N-1 +struct CustomBaudRateSettings : public MIDI_NAMESPACE::DefaultSerialSettings { + static const long BaudRate = 115200; +}; +MIDI_NAMESPACE::SerialMIDI serialMIDI(Serial); +MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); +#endif + +#ifdef MIDI_VIA_SERIAL2 +// MIDI port on UART2, pins 16 (RX) and 17 (TX) prohibited, as they are used for PSRAM +struct Serial2MIDISettings : public midi::DefaultSettings { + static const long BaudRate = 31250; + static const int8_t RxPin = MIDIRX_PIN; + static const int8_t TxPin = MIDITX_PIN; +}; +MIDI_NAMESPACE::SerialMIDI Serial2MIDI2(Serial2); +MIDI_NAMESPACE::MidiInterface> MIDI2((MIDI_NAMESPACE::SerialMIDI&)Serial2MIDI2); #endif const i2s_port_t i2s_num = I2S_NUM_0; // i2s port number - + // lookuptables static float midi_pitches[128]; static float midi_phase_steps[128]; -static float midi_2048_steps[128]; -static float exp_2048[WAVE_SIZE]; -static float square_2048[WAVE_SIZE]; -static float tanh_2048[WAVE_SIZE]; +static float midi_tbl_steps[128]; +static float exp_square_tbl[WAVE_SIZE]; +//static float square_tbl[WAVE_SIZE]; +//static float saw_tbl[WAVE_SIZE]; +static float exp_tbl[WAVE_SIZE]; +static float tanh_tbl[WAVE_SIZE]; static uint32_t last_reset = 0; static float param[POT_NUM] ; +//static float (*tables[])[WAVE_SIZE] = {&exp_square_tbl, &square_tbl, &saw_tbl, &exp_tbl}; // Audio buffers of all kinds static float synth_buf[2][DMA_BUF_LEN]; // 2 * 303 mono @@ -77,12 +80,12 @@ static union { // a dirty trick, instead of true converting volatile boolean processing = false; #ifndef NO_PSRAM -static float rvb_k1, rvb_k2, rvb_k3; +volatile float rvb_k1, rvb_k2, rvb_k3; #endif -static float dly_k1, dly_k2, dly_k3; +volatile float dly_k1, dly_k2, dly_k3; size_t bytes_written; // i2s -static uint32_t c1=0, c2=0, c3=0, d1=0, d2=0, d3=0, d4=0, c4=0; // debug timing +volatile uint32_t s1t, s2t, drt, fxt, s1T, s2T, drT, fxT; // debug timing: if we use less vars, compiler optimizes them volatile static uint32_t prescaler; // tasks for Core0 and Core1 @@ -97,63 +100,29 @@ SynthVoice Synth2(1); // use synth_buf[1] Sampler Drums(DRUMKITCNT , DEFAULT_DRUMKIT); // first arg = total number of sample sets, second = starting drumset [0 .. total-1] // Global effects - FxDelay Delay; +FxDelay Delay; #ifndef NO_PSRAM - FxReverb Reverb; +FxReverb Reverb; #endif Compressor Comp; -// Core0 task -static void audio_task1(void *userData) { - while(1) { - // this part of the code never intersects with mixer buffers - // this part of the code is operating with shared resources, so we should make it safe - if (ulTaskNotifyTake(pdTRUE, portMAX_DELAY)){ - c1=micros(); - Synth1.Generate(); - d1=micros()-c1; - xTaskNotifyGive(SynthTask2); // if you have glitches, you may want to place this string in the end of audio_task1 - } - // readPots(); - taskYIELD(); - } -} -// task for Core1, which tipically runs user's code on ESP32 -static void audio_task2(void *userData) { - while(1) { - // we can run it together with synth(), but not with mixer() - c2 = micros(); - drums_generate(); - // Synth1.Generate(); - d2 = micros() - c2; - Synth2.Generate(); - d3 = micros() - c2 - d2; - if (ulTaskNotifyTake(pdTRUE, portMAX_DELAY)) { // we need all the generators to fill the buffers here, so we wait - c4 = micros(); - mixer(); // actually we could send Notify before mixer() is done, but then we'd need tic-tac buffers for generation. Todo maybe - d4 = micros() - c4; - xTaskNotifyGive(SynthTask1); - } - - i2s_output(); - - taskYIELD(); - } -} + +/* + * Quite an ordinary SETUP() ******************************************************************************************************* +*/ void setup(void) { btStop(); - -#ifdef MIDI_ON - #ifdef MIDI_VIA_SERIAL - Serial.begin(115200); - #else - pinMode( MIDIRX_PIN , INPUT_PULLDOWN); - MIDISerial.begin( 31250, SERIAL_8N1, MIDIRX_PIN, MIDITX_PIN ); // midi port - #endif -#else + +#ifdef MIDI_VIA_SERIAL + Serial.begin(115200, SERIAL_8N1); +#endif +#ifdef MIDI_VIA_SERIAL2 + pinMode( MIDIRX_PIN , INPUT_PULLDOWN); + pinMode( MIDITX_PIN , OUTPUT); + Serial2.begin( 31250, SERIAL_8N1, MIDIRX_PIN, MIDITX_PIN ); // midi port #endif #ifdef DEBUG_ON @@ -162,28 +131,36 @@ void setup(void) { #endif #endif /* - for (uint8_t i = 0; i < GPIO_BUTTONS; i++) { + for (uint8_t i = 0; i < GPIO_BUTTONS; i++) { pinMode(buttonGPIOs[i], INPUT_PULLDOWN); - } + } */ - + buildTables(); -for (int i=0; i1000) { +#endif + + taskYIELD(); // breath for all the rest of the Core1 + +#ifdef MIDI_VIA_SERIAL2 + MIDI2.read(); +#endif + +#ifdef JUKEBOX + if (micros() - last_ms > 1000) { last_ms = micros(); run_tick(); myRandomAddEntropy((uint16_t)(last_ms & 0x0000FFFF)); - - // if (prescaler % 1024 == 0) DEBF ("synt1=%d drums=%d synt2=%d mixer=%d \r\n" , d1, d2, d3, d4); - - // DEBF("%0.4f %0.4f %0.4f \r\n", param1, param2, param3); + // DEBF("%0.4f %0.4f %0.4f \r\n", param1, param2, param3); + } +#endif +} + +/* + * Core Tasks ************************************************************************************************************************ +*/ + +// Core0 task +static void audio_task1(void *userData) { + while (1) { + // we can run it together with synth(), but not with mixer() + drt = micros(); + drums_generate(); + drT = micros() - drt; + s2t = micros(); + Synth2.Generate(); + s2T = micros() - s2t; + if (ulTaskNotifyTake(pdTRUE, portMAX_DELAY)) { // we need all the generators to fill the buffers here, so we wait + fxt = micros(); + mixer(); // actually we could send Notify before mixer() is done, but then we'd need tic-tac buffers for generation. Todo maybe + fxT = micros() - fxt; + xTaskNotifyGive(SynthTask2); + } + + i2s_output(); + + taskYIELD(); } - #endif +} - taskYIELD(); // breath for all the rest of the Core1 +// task for Core1, which tipically runs user's code on ESP32 +static void audio_task2(void *userData) { + while (1) { + // this part of the code never intersects with mixer buffers + // this part of the code is operating with shared resources, so we should make it safe + if (ulTaskNotifyTake(pdTRUE, portMAX_DELAY)) { + s1t = micros(); + Synth1.Generate(); + s1T = micros() - s1t; + xTaskNotifyGive(SynthTask1); // if you have glitches, you may want to place this string in the end of audio_task1 + } + // readPots(); + taskYIELD(); + // hopefully, other Core1 tasks (for example, loop()) run here + } } + + +/* + * Some service routines ***************************************************************************************************************************** +*/ + void readPots() { static const float snap = 0.005; static float tmp; - static const float NORMALIZE_ADC = 1.0f/4096.0f; + static const float NORMALIZE_ADC = 1.0f / 4096.0f; for (uint8_t i = 0; i < POT_NUM; i++) { - tmp = (float)analogRead(POT_PINS[i]) * NORMALIZE_ADC; + tmp = (float)analogRead(POT_PINS[i]) * NORMALIZE_ADC; if (fabs(tmp - param[i]) > snap) { param[i] = tmp; paramChange(i, tmp); @@ -266,6 +299,6 @@ void paramChange(uint8_t paramNum, float paramVal) { case 3: break; default: - {} + {} } } diff --git a/config.h b/config.h index 7650723..48218ed 100644 --- a/config.h +++ b/config.h @@ -1,39 +1,44 @@ #define PROG_NAME "ESP32 AcidBox" -#define VERSION "v1.0" +#define VERSION "v.1.0.1" -#define DEBUG_ON // note that debugging eats ticks initially belonging to real-time tasks, so sound output will be spoild in most cases, turn it off for production build -//#define DEBUG_MASTER_OUT // serial monitor plotter will draw the output waveform +#define DEBUG_ON // note that debugging eats ticks initially belonging to real-time tasks, so sound output will be spoild in most cases, turn it off for production build +//#define DEBUG_MASTER_OUT // serial monitor plotter will draw the output waveform //#define DEBUG_SAMPLER //#define DEBUG_JUKEBOX //#define DEBUG_FX -#define USE_INTERNAL_DAC // use this for testing, SOUND QUALITY SACRIFICED: 8BIT STEREO -//#define NO_PSRAM // if you don't have PSRAM on your board, then use this define, but REVERB AND DELAY'D BE SACRIFICED, SMALL DRUM KIT SAMPLES USED +//#define USE_INTERNAL_DAC // use this for testing, SOUND QUALITY SACRIFICED: NOISY 8BIT STEREO +//#define NO_PSRAM // if you don't have PSRAM on your board, then use this define, but REVERB TO BE SACRIFICED, SMALL DRUM KIT SAMPLES USED -#define MIDI_ON // use this option if you want to operate by MIDI -//#define MIDI_VIA_SERIAL // use this option together with MIDI_ON for Hairless MIDI style, this will block Serial debugging as well -#define JUKEBOX // real-time auto-compose acid tunes -#define JUKEBOX_PLAY_ON_START // should it play on power on, or should it wait for "boot" button to be pressed +//#define MIDI_VIA_SERIAL // use this option to enable Hairless MIDI on Serial port @115200 baud (USB connector), THIS WILL BLOCK SERIAL DEBUGGING as well +#define MIDI_VIA_SERIAL2 // use this option if you want to operate by MIDI @31250baud, UART2 (Serial2), +#define MIDIRX_PIN 4 // this pin will be used for input when MIDI_VIA_SERIAL2 defined (note that default pin 17 won't work with PSRAM) +#define MIDITX_PIN 0 // this pin will be used for output (not implemented yet) when MIDI_VIA_SERIAL2 defined + + +#define JUKEBOX // real-time endless auto-compose acid tunes +//#define JUKEBOX_PLAY_ON_START // should it play on power on, or should it wait for "boot" button to be pressed #define MAX_CUTOFF_FREQ 4000.0f #define MIN_CUTOFF_FREQ 250.0f #ifdef USE_INTERNAL_DAC -#define SAMPLE_RATE 44100 // price for increasing this value is less delay time +#define SAMPLE_RATE 22050 // price for increasing this value having NO_PSRAM is less delay time, you won't hear the difference at 8bit/sample #else -#define SAMPLE_RATE 44100 +#define SAMPLE_RATE 44100 // 44100 seems to be the right value, 48000 is also OK. Other values are not tested. #endif const float DIV_SAMPLE_RATE = 1.0f / (float)SAMPLE_RATE; const float DIV_2SAMPLE_RATE = 0.5f / (float)SAMPLE_RATE; -#define WAVE_SIZE 2048 // samples used for waveform lookup tables +#define WAVE_SIZE 2048 // samples used for lookup tables (it works pretty well down to 32 samples due to linear approximation, so listen and free some memory at your choice) const float DIV_WAVE_SIZE = 1.0f / (float)WAVE_SIZE; -#define TANH_LOOKUP_MAX 5.0f // maximum X argument value for tanh(X) lookup table, tanh(X)~=1 if X>4 +#define TANH_LOOKUP_MAX 5.0f // maximum X argument value for tanh(X) lookup table, tanh(X)~=1 if X>4 const float TANH_LOOKUP_COEF = (float)WAVE_SIZE / TANH_LOOKUP_MAX; -#define DMA_BUF_LEN 64 -#define DMA_NUM_BUF 2 +#define DMA_BUF_LEN 32 // there should be no problems with low values, down to 32 samples, 64 seems to be OK with some extra +#define DMA_NUM_BUF 2 // I see no reasom to set more than 2 DMA buffers, but... +const uint32_t DMA_BUF_TIME = (uint32_t)(1000000.0f / (float)SAMPLE_RATE * (float)DMA_BUF_LEN); // microseconds per buffer float bpm = 130.0; #define I2S_BCLK_PIN 5 @@ -41,15 +46,13 @@ float bpm = 130.0; #define I2S_DOUT_PIN 18 #define POT_NUM 3 -const uint8_t POT_PINS[POT_NUM] = {34,35,36}; +const uint8_t POT_PINS[POT_NUM] = {34, 35, 36}; #define SYNTH1_MIDI_CHAN 1 #define SYNTH2_MIDI_CHAN 2 #define DRUM_MIDI_CHAN 10 -#define MIDIRX_PIN 4 -#define MIDITX_PIN 0 /* #define MUXED_BUTTONS 0 diff --git a/fx_delay.h b/fx_delay.h index 4de8eff..69d72d1 100644 --- a/fx_delay.h +++ b/fx_delay.h @@ -34,6 +34,7 @@ class FxDelay { #else #define MAX_DELAY SAMPLE_RATE // 1 second +//#define MAX_DELAY 38000 // 1 second delayLine_l = (float *)ps_malloc(sizeof(float) * MAX_DELAY); if( delayLine_l == NULL){ DEBF("No more PSRAM!\n"); diff --git a/fx_filtercrusher.h b/fx_filtercrusher.h index 206e1c3..b8ca5f3 100644 --- a/fx_filtercrusher.h +++ b/fx_filtercrusher.h @@ -3,247 +3,109 @@ #ifndef FXFCR_H #define FXFCR_H /* - * this file includes some simple effects - * - dual filter - * - bit crusher - * - * Author: Marcel Licence - */ - + this file includes some simple effects + - dual filter + - bit crusher + + Author: Marcel Licence +*/ + #define WAVEFORM_BIT 10UL #define WAVEFORM_CNT (1<> (32 - WAVEFORM_BIT)) & WAVEFORM_MSK - + class FxFilterCrusher { - public: + public: FxFilterCrusher() {} void Init (float samplerate) { Init(); } - void Init( void ){ - for( int i = 0; i < WAVEFORM_CNT; i++ ){ - float val = (float)sin(i * 2.0 * PI / WAVEFORM_CNT); - sine[i] = val; - } - - mainFilterL_LP.filterCoeff = &filterGlobalC_LP; - mainFilterR_LP.filterCoeff = &filterGlobalC_LP; - mainFilterL_HP.filterCoeff = &filterGlobalC_HP; - mainFilterR_HP.filterCoeff = &filterGlobalC_HP; - }; + void Init( void ) { + for ( int i = 0; i < WAVEFORM_CNT; i++ ) { + float val = (float)sin(i * 2.0 * PI / WAVEFORM_CNT); + sine[i] = val; + } + + mainFilterL_LP.filterCoeff = &filterGlobalC_LP; + mainFilterR_LP.filterCoeff = &filterGlobalC_LP; + mainFilterL_HP.filterCoeff = &filterGlobalC_HP; + mainFilterR_HP.filterCoeff = &filterGlobalC_HP; + }; inline float Process (float sample) { Process(&sample, &sample); - return sample; + return sample; } - void Process( float* left, float* right ){ - effect_prescaler ++; - - Filter_Process(left, &mainFilterL_LP); - Filter_Process(right, &mainFilterR_LP); - Filter_Process(left, &mainFilterL_HP); - Filter_Process(right, &mainFilterR_HP); - - if( cutoff_lp_slow > lowpassC ){ - cutoff_lp_slow -= 0.001; - } - if( cutoff_lp_slow < lowpassC ){ - cutoff_lp_slow += 0.001; - } - - if( cutoff_hp_slow > highpassC ){ - cutoff_hp_slow -= 0.001; - } - if( cutoff_hp_slow < highpassC ){ - cutoff_hp_slow += 0.001; - } - - /* we can not calculate in each cycle */ - if( effect_prescaler % 8 == 0 ){ - Filter_CalculateTP(cutoff_lp_slow, filtReso, &filterGlobalC_LP); - Filter_CalculateHP(cutoff_hp_slow, filtReso, &filterGlobalC_HP); - } - - if( bitCrusher < 1.0f ){ - int32_t ul = *left * bitCrusher * (1 << 29); - *left = ((float)ul) * one_div(bitCrusher * (float)(1 << 29)); - - int32_t ur = *right * bitCrusher * (1 << 29); - *right = ((float)ur) * one_div(bitCrusher * (float)(1 << 29)); - } - }; - - - - void SetCutoff( float value ){ - highpassC = value >= 0.5 ? (value - 0.5f) * 2.0f : 0.0f; - lowpassC = value <= 0.5 ? (value) * 2.0f : 1.0f; + + void Process( float* left, float* right ); + + void SetCutoff( float value ) { + highpassC = value >= 0.5 ? (value - 0.5f) * 2.0f : 0.0f; + lowpassC = value <= 0.5 ? (value) * 2.0f : 1.0f; #ifdef DEBUG_FX - DEBF("Filter TP: %0.2f, HP: %02f\n", lowpassC, highpassC); + DEBF("Filter TP: %0.2f, HP: %02f\n", lowpassC, highpassC); #endif - }; + }; - void SetResonance( float value ){ - filtReso = 0.5f + 10 * value * value * value; /* min q is 0.5 here */ + void SetResonance( float value ) { + filtReso = 0.5f + 10 * value * value * value; /* min q is 0.5 here */ #ifdef DEBUG_FX - DEBF("main filter reso: %0.3f\n", filtReso); + DEBF("main filter reso: %0.3f\n", filtReso); #endif - }; + }; - void SetBitCrusher( float value ){ - bitCrusher = pow(2, -32.0f * value); + void SetBitCrusher( float value ) { + bitCrusher = pow(2, -32.0f * value); #ifdef DEBUG_FX - DEBF("main filter bitCrusher: %0.3f\n", bitCrusher); + DEBF("main filter bitCrusher: %0.3f\n", bitCrusher); #endif - }; - - private: - - struct filterCoeffT{ - float aNorm[2] = {0.0f, 0.0f}; - float bNorm[3] = {1.0f, 0.0f, 0.0f}; - }; - - struct filterProcT{ - struct filterCoeffT *filterCoeff; - float w[3]; - }; - - struct filterCoeffT filterGlobalC_LP, filterGlobalC_HP; - struct filterProcT mainFilterL_LP, mainFilterR_LP, mainFilterL_HP, mainFilterR_HP; - - float sine[WAVEFORM_CNT]; - - float highpassC = 0.0f; - float lowpassC = 1.0f; - float filtReso = 1.0f; - - float cutoff_hp_slow = 0.0f; - float cutoff_lp_slow = 1.0f; - - uint8_t effect_prescaler = 0; - - float bitCrusher = 1.0f; - - // calculate coefficients of the 2nd order IIR filter - - inline void Filter_CalculateTP(float c, float reso, struct filterCoeffT *const filterC ){ - float *aNorm = filterC->aNorm; - float *bNorm = filterC->bNorm; - - float Q = reso; - float cosOmega, omega, sinOmega, alpha, a[3], b[3]; - - // change curve of cutoff a bit - // maybe also log or exp function could be used - - c = c * c * c; - - if (c > 0.9975f ){ - omega = 0.9975f; - }else if( c < 0.0025f ){ - omega = 0.0025f; - }else{ - omega = c; - } - - // use lookup here to get quicker results - cosOmega = sine[WAVEFORM_I((uint32_t)((float)((1ULL << 31) - 1) * omega + (float)((1ULL << 30) - 1)))]; - sinOmega = sine[WAVEFORM_I((uint32_t)((float)((1ULL << 31) - 1) * omega))]; - - alpha = sinOmega * one_div(2.0 * Q); - b[0] = (1 - cosOmega) * 0.5f; - b[1] = 1 - cosOmega; - b[2] = b[0]; - a[0] = 1 + alpha; - a[1] = -2 * cosOmega; - a[2] = 1 - alpha; - - // Normalize filter coefficients - float factor = one_div(a[0]); - - aNorm[0] = a[1] * factor; - aNorm[1] = a[2] * factor; - - bNorm[0] = b[0] * factor; - bNorm[1] = b[1] * factor; - bNorm[2] = b[2] * factor; - }; - - inline void Filter_CalculateHP(float c, float reso, struct filterCoeffT *const filterC ){ - float *aNorm = filterC->aNorm; - float *bNorm = filterC->bNorm; - - float Q = reso; - float cosOmega, omega, sinOmega, alpha, a[3], b[3]; - - - // change curve of cutoff a bit - // maybe also log or exp function could be used - - c = c * c * c; - - if (c > 0.9975f ){ - omega = 0.9975f; - }else if( c < 0.0025f ){ - omega = 0.0025f; - }else{ - omega = c; - } - - // use lookup here to get quicker results - - cosOmega = sine[WAVEFORM_I((uint32_t)((float)((1ULL << 31) - 1) * omega + (float)((1ULL << 30) - 1)))]; - sinOmega = sine[WAVEFORM_I((uint32_t)((float)((1ULL << 31) - 1) * omega))]; - - alpha = sinOmega * one_div(2.0 * Q); - b[0] = (1 + cosOmega) * 0.5f; - b[1] = -(1 + cosOmega); - b[2] = b[0]; - a[0] = 1 + alpha; - a[1] = -2 * cosOmega; - a[2] = 1 - alpha; - - // Normalize filter coefficients - float factor = one_div(a[0]) ; - - aNorm[0] = a[1] * factor; - aNorm[1] = a[2] * factor; - - bNorm[0] = b[0] * factor; - bNorm[1] = b[1] * factor; - bNorm[2] = b[2] * factor; - }; - - inline void Filter_Process( float *const signal, struct filterProcT *const filterP ){ - const float out = filterP->filterCoeff->bNorm[0] * (*signal) + filterP->w[0]; - filterP->w[0] = filterP->filterCoeff->bNorm[1] * (*signal) - filterP->filterCoeff->aNorm[0] * out + filterP->w[1]; - filterP->w[1] = filterP->filterCoeff->bNorm[2] * (*signal) - filterP->filterCoeff->aNorm[1] * out; - *signal = out; - }; - - static __attribute__((always_inline)) inline float one_div(float a) { - float result; - asm volatile ( - "wfr f1, %1" "\n\t" - "recip0.s f0, f1" "\n\t" - "const.s f2, 1" "\n\t" - "msub.s f2, f1, f0" "\n\t" - "maddn.s f0, f0, f2" "\n\t" - "const.s f2, 1" "\n\t" - "msub.s f2, f1, f0" "\n\t" - "maddn.s f0, f0, f2" "\n\t" - "rfr %0, f0" "\n\t" - : "=r" (result) - : "r" (a) - : "f0","f1","f2" - ); - return result; - } - + }; + + private: + + struct filterCoeffT { + float aNorm[2] = {0.0f, 0.0f}; + float bNorm[3] = {1.0f, 0.0f, 0.0f}; + }; + + struct filterProcT { + struct filterCoeffT *filterCoeff; + float w[3]; + }; + + struct filterCoeffT filterGlobalC_LP, filterGlobalC_HP; + struct filterProcT mainFilterL_LP, mainFilterR_LP, mainFilterL_HP, mainFilterR_HP; + + float sine[WAVEFORM_CNT]; + + float highpassC = 0.0f; + float lowpassC = 1.0f; + float filtReso = 1.0f; + + float cutoff_hp_slow = 0.0f; + float cutoff_lp_slow = 1.0f; + + uint8_t effect_prescaler = 0; + + float bitCrusher = 1.0f; + + // calculate coefficients of the 2nd order IIR filter + + inline void Filter_CalculateTP(float c, float reso, struct filterCoeffT *const filterC ); + + inline void Filter_CalculateHP(float c, float reso, struct filterCoeffT *const filterC ); + + inline void Filter_Process( float *const signal, struct filterProcT *const filterP ) { + const float out = filterP->filterCoeff->bNorm[0] * (*signal) + filterP->w[0]; + filterP->w[0] = filterP->filterCoeff->bNorm[1] * (*signal) - filterP->filterCoeff->aNorm[0] * out + filterP->w[1]; + filterP->w[1] = filterP->filterCoeff->bNorm[2] * (*signal) - filterP->filterCoeff->aNorm[1] * out; + *signal = out; + }; + + }; #endif diff --git a/fx_filtercrusher.ino b/fx_filtercrusher.ino new file mode 100644 index 0000000..5f60e61 --- /dev/null +++ b/fx_filtercrusher.ino @@ -0,0 +1,120 @@ +#include "fx_filtercrusher.h" + +void FxFilterCrusher::Process( float* left, float* right ) { + effect_prescaler++; + + Filter_Process(left, &mainFilterL_LP); + Filter_Process(right, &mainFilterR_LP); + Filter_Process(left, &mainFilterL_HP); + Filter_Process(right, &mainFilterR_HP); + + cutoff_lp_slow = (float)cutoff_lp_slow * 0.9f + 0.1f * ((float)lowpassC - (float)cutoff_lp_slow); + cutoff_hp_slow = (float)cutoff_hp_slow * 0.9f + 0.1f * ((float)highpassC - (float)cutoff_hp_slow); + + + /* we can not calculate in each cycle */ + if ( effect_prescaler % 8 == 0 ) { + Filter_CalculateTP(cutoff_lp_slow, filtReso, &filterGlobalC_LP); + Filter_CalculateHP(cutoff_hp_slow, filtReso, &filterGlobalC_HP); + } + + if ( bitCrusher < 1.0f ) { + int32_t ul = *left * (float)bitCrusher * (float)(1 << 29); + *left = ((float)ul) * one_div((float)bitCrusher * (float)(1 << 29)); + + int32_t ur = *right * (float)bitCrusher * (float)(1 << 29); + *right = ((float)ur) * one_div((float)bitCrusher * (float)(1 << 29)); + } +}; + + +inline void FxFilterCrusher::Filter_CalculateTP(float c, float reso, struct filterCoeffT *const filterC ) { + float *aNorm = filterC->aNorm; + float *bNorm = filterC->bNorm; + + float Q = reso; + float cosOmega, omega, sinOmega, alpha, a[3], b[3]; + + // change curve of cutoff a bit + // maybe also log or exp function could be used + + c = (float)(c * c * c); + + if (c > 0.9975f ) { + omega = 0.9975f; + } else if ( c < 0.0025f ) { + omega = 0.0025f; + } else { + omega = c; + } + + // omega = fast_tanh(4.0f * c - 2.0f) * 0.5f + 0.5f; // it's smooth and sounds badly + // use lookup here to get quicker results + cosOmega = sine[WAVEFORM_I((uint32_t)((float)((1ULL << 31) - 1) * omega + (float)((1ULL << 30) - 1)))]; + sinOmega = sine[WAVEFORM_I((uint32_t)((float)((1ULL << 31) - 1) * omega))]; + + alpha = sinOmega * one_div(2.0 * Q); + b[0] = (1 - cosOmega) * 0.5f; + b[1] = 1 - cosOmega; + b[2] = b[0]; + a[0] = 1 + alpha; + a[1] = -2 * cosOmega; + a[2] = 1 - alpha; + + // Normalize filter coefficients + float factor = one_div(a[0]); + + aNorm[0] = a[1] * factor; + aNorm[1] = a[2] * factor; + + bNorm[0] = b[0] * factor; + bNorm[1] = b[1] * factor; + bNorm[2] = b[2] * factor; +}; + +inline void FxFilterCrusher::Filter_CalculateHP(float c, float reso, struct filterCoeffT *const filterC ) { + float *aNorm = filterC->aNorm; + float *bNorm = filterC->bNorm; + + float Q = reso; + float cosOmega, omega, sinOmega, alpha, a[3], b[3]; + + + // change curve of cutoff a bit + // maybe also log or exp function could be used + + c = (float)(c * c * c); + + if (c > 0.9975f ) { + omega = 0.9975f; + } else if ( c < 0.0025f ) { + omega = 0.0025f; + } else { + omega = c; + } + + + //omega = fast_tanh(4.0f * c - 2.0f) * 0.5f + 0.5f; + // use lookup here to get quicker results + + cosOmega = sine[WAVEFORM_I((uint32_t)((float)((1ULL << 31) - 1) * omega + (float)((1ULL << 30) - 1)))]; + sinOmega = sine[WAVEFORM_I((uint32_t)((float)((1ULL << 31) - 1) * omega))]; + + alpha = sinOmega * one_div(2.0 * Q); + b[0] = (1 + cosOmega) * 0.5f; + b[1] = -(1 + cosOmega); + b[2] = b[0]; + a[0] = 1 + alpha; + a[1] = -2 * cosOmega; + a[2] = 1 - alpha; + + // Normalize filter coefficients + float factor = one_div(a[0]) ; + + aNorm[0] = a[1] * factor; + aNorm[1] = a[2] * factor; + + bNorm[0] = b[0] * factor; + bNorm[1] = b[1] * factor; + bNorm[2] = b[2] * factor; +}; diff --git a/general.ino b/general.ino index 1098b68..3dcb7df 100644 --- a/general.ino +++ b/general.ino @@ -89,15 +89,10 @@ inline float lookupTable(float (&table)[WAVE_SIZE], float index ) { // lookup va static float f; i = (int32_t)index; f = index - i; - v1 = table[i]; - if (i=4.95f) { return sign; // tanh(x) ~= 1, when |x| > 4 } - if (x<=0.4f) return float(x*sign) * 0.9498724f; // smooth region borders; tanh(x) ~= x, when |x| < 0.4 - return sign * lookupTable(tanh_2048, (x*TANH_LOOKUP_COEF)); // lookup table 2048 / 5 = 409.6 + // if (x<=0.4f) return float(x*sign) * 0.9498724f; // smooth region borders; tanh(x) ~= x, when |x| < 0.4 + return sign * lookupTable(tanh_tbl, (x*TANH_LOOKUP_COEF)); // lookup table contains tanh(x), 0 <= x <= 5 // float poly = (2.12-2.88*x+4.0*x*x); // return sign * x * (poly * one_div(poly * x + 1.0f)); // very good approximation found here https://www.musicdsp.org/en/latest/Other/178-reasonably-accurate-fastish-tanh-approximation.html // but it uses float division which is not that fast on esp32 diff --git a/i2s_setup.ino b/i2s_setup.ino index 0ab1af4..b5e01d7 100644 --- a/i2s_setup.ino +++ b/i2s_setup.ino @@ -2,7 +2,7 @@ void i2sInit() { pinMode(25, OUTPUT); pinMode(26, OUTPUT); - + i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), .sample_rate = SAMPLE_RATE, @@ -17,32 +17,32 @@ void i2sInit() { i2s_driver_install(i2s_num, &i2s_config, 0, NULL); i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); - i2s_set_pin(i2s_num, NULL); + i2s_set_pin(i2s_num, NULL); i2s_zero_dma_buffer(i2s_num); } #else void i2sInit() { i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX ), - .sample_rate = SAMPLE_RATE, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S ), - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, - .dma_buf_count = DMA_NUM_BUF, - .dma_buf_len = DMA_BUF_LEN, - .use_apll = true, + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX ), + .sample_rate = SAMPLE_RATE, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S ), + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, + .dma_buf_count = DMA_NUM_BUF, + .dma_buf_len = DMA_BUF_LEN, + .use_apll = true, }; - + i2s_pin_config_t i2s_pin_config = { - .bck_io_num = I2S_BCLK_PIN, - .ws_io_num = I2S_WCLK_PIN, - .data_out_num = I2S_DOUT_PIN + .bck_io_num = I2S_BCLK_PIN, + .ws_io_num = I2S_WCLK_PIN, + .data_out_num = I2S_DOUT_PIN }; i2s_driver_install(i2s_num, &i2s_config, 0, NULL); - i2s_set_pin(i2s_num, &i2s_pin_config); + i2s_set_pin(i2s_num, &i2s_pin_config); i2s_zero_dma_buffer(i2s_num); } #endif diff --git a/moogladder.ino b/moogladder.ino index 02102d7..c83de7b 100644 --- a/moogladder.ino +++ b/moogladder.ino @@ -13,7 +13,7 @@ inline float MoogLadder::my_tanh(float x) return sign; } if (x<=0.4f) return float(x*sign) * 0.9498724f; // smooth region borders - return sign * lookupTable(tanh_2048,(x*TANH_LOOKUP_COEF)); // lookup table 2048 / 5 = 409.6, 2048 is table size, 5 is max argument value + return sign * lookupTable(tanh_tbl,(x*TANH_LOOKUP_COEF)); // lookup table, 5 is max argument value // poly = (2.12-2.88*x+4.0*x*x); // return sign * x * (poly / (poly * x + 1.0f)); // very good approximation found here https://www.musicdsp.org/en/latest/Other/178-reasonably-accurate-fastish-tanh-approximation.html // but it uses float division which is not that fast on esp32 diff --git a/overdrive.ino b/overdrive.ino index f31b883..1e7263c 100644 --- a/overdrive.ino +++ b/overdrive.ino @@ -8,25 +8,25 @@ void Overdrive::Init() float Overdrive::Process(float in) { - float pre = pre_gain_ * in * 2.0f; + float pre = (float)(pre_gain_ * in * 2.0f); - return fast_tanh(pre) * post_gain_; + return (float)(fast_tanh(pre) * post_gain_); // return SoftClip(pre) * post_gain_; } void Overdrive::SetDrive(float drive) { - drive = 0.125f + drive * (0.875f); + drive = 0.125f + (float)drive * (0.875f); //drive = fclamp(drive, 0.f, 1.f); - drive_ = 1.999f * drive; + drive_ = 1.999f * (float)drive; - const float drive_2 = drive_ * drive_; - const float pre_gain_a = drive_ * 0.5f; - const float pre_gain_b = drive_2 * drive_2 * drive_ * 24.0f; - pre_gain_ = pre_gain_a + (pre_gain_b - pre_gain_a) * drive_2; + const float drive_2 = (float)drive_ * drive_; + const float pre_gain_a = (float)drive_ * 0.5f; + const float pre_gain_b = (float)drive_2 * drive_2 * drive_ * 24.0f; + pre_gain_ = (float)pre_gain_a + (float)(pre_gain_b - pre_gain_a) * drive_2; const float drive_squashed = drive_ * (2.0f - drive_); - post_gain_ = 0.5f / fast_tanh(0.33f + drive_squashed * (pre_gain_ - 0.33f)); + post_gain_ = 0.5f / fast_tanh((float)(0.33f + drive_squashed * (float)(pre_gain_ - 0.33f))); #ifdef DEBUG_FX DEBF("pre %0.4f post %0.4f drive %0.4f\r\n", pre_gain_, post_gain_, drive_); #endif diff --git a/rosic_TeeBeeFilter.ino b/rosic_TeeBeeFilter.ino index ead0ab3..97e6a77 100644 --- a/rosic_TeeBeeFilter.ino +++ b/rosic_TeeBeeFilter.ino @@ -291,11 +291,13 @@ inline float TeeBeeFilter::shape(float x) void TeeBeeFilter::Init() { feedbackHighpass.reset(); - SetDrive(0.11f); y1 = 0.0f; y2 = 0.0f; y3 = 0.0f; y4 = 0.0f; + SetDrive(0.11f); + SetResonance(0.5f, false); + SetCutoff(1000.0f, true); } @@ -303,9 +305,11 @@ void TeeBeeFilter::Init(float sr) { feedbackHighpass.reset(); SetSampleRate(sr); - SetDrive(0.11f); y1 = 0.0f; y2 = 0.0f; y3 = 0.0f; y4 = 0.0f; + SetDrive(0.11f); + SetResonance(0.5f, false); + SetCutoff(1000.0f, true); } diff --git a/sampler.h b/sampler.h index 48dbce1..6888eb1 100644 --- a/sampler.h +++ b/sampler.h @@ -21,9 +21,9 @@ DEBF("Select note: %d\r\n", note); inline void SetNotePan_Midi( uint8_t data1); inline void SetNoteOffset_Midi( uint8_t data1 ); inline void SetNoteDecay_Midi( uint8_t data1); - inline void SetSoundPitch_Midi( uint8_t value); + inline void SetNoteVolume_Midi( uint8_t data1); + inline void SetSoundPitch_Midi( uint8_t data1); inline void SetSoundPitch(float value); - inline void SetNoteVolume_Midi( uint8_t data1){ samplePlayer[ selectedNote ].volume_midi = data1; }; inline void SetDelaySend(uint8_t lvl) {_sendDelay = (float)lvl;}; inline void SetReverbSend(uint8_t lvl) {_sendReverb = (float)lvl;}; uint16_t GetSoundSamplerate(){ return samplePlayer[ selectedNote ].sampleRate; }; diff --git a/sampler.ino b/sampler.ino index 7951e3f..6e2c198 100644 --- a/sampler.ino +++ b/sampler.ino @@ -1,18 +1,19 @@ /* - * this file includes the implementation of the sample player - * all samples are loaded from LittleFS stored on the external flash - * - * Author: Marcel Licence - * - * Modifications: - * 2021-04-05 E.Heinemann changed BLOCKSIZE from 2024 to 1024 - * , added DEBUG_SAMPLER - * , added sampleRate to the structure of samplePlayerS to optimize the pitch based on lower samplerates - * 2021-07-28 E.Heinemann, added pitch-decay and pan - * 2021-08-03 E.Heinemann, changed Accent/normal Velocity in the code - * 2022-11-27 Copych, made this a class, made it use one big PSRAM buffer for wave data - */ - + this file includes the implementation of the sample player + all samples are loaded from LittleFS stored on the external flash + + Author: Marcel Licence + + Modifications: + 2021-04-05 E.Heinemann changed BLOCKSIZE from 2024 to 1024 + , added DEBUG_SAMPLER + , added sampleRate to the structure of samplePlayerS to optimize the pitch based on lower samplerates + 2021-07-28 E.Heinemann, added pitch-decay and pan + 2021-08-03 E.Heinemann, changed Accent/normal Velocity in the code + 2022-11-27 Copych, made this a class, made it use one big PSRAM buffer for wave data + 2023-01-20 Copych, changed midi cc handling +*/ + #include "sampler.h" /* You only need to format LittleFS the first time you run a @@ -21,388 +22,394 @@ //#define DEBUG_SAMPLER -void Sampler::ScanContents(fs::FS &fs, const char *dirname, uint8_t levels){ -#ifdef DEBUG_SAMPLER - DEBF("Listing directory: %s\r\n", dirname); +void Sampler::ScanContents(fs::FS &fs, const char *dirname, uint8_t levels) { +#ifdef DEBUG_SAMPLER + DEBF("Listing directory: %s\r\n", dirname); #endif - File root = fs.open(dirname); - if( !root ){ - DEBUG("- failed to open directory"); - return; - } - if( !root.isDirectory() ){ - DEBUG(" - not a directory"); - return; - } + File root = fs.open(dirname); + if ( !root ) { + DEBUG("- failed to open directory"); + return; + } + if ( !root.isDirectory() ) { + DEBUG(" - not a directory"); + return; + } - File file = root.openNextFile(); - while( file ){ - if( file.isDirectory() ){ + File file = root.openNextFile(); + while ( file ) { + if ( file.isDirectory() ) { #ifdef DEBUG_SAMPLER - DEB(" DIR : "); - DEBUG(file.name()); -#endif - if( levels ){ - - ScanContents(fs, file.name(), levels - 1); - } - }else{ -#ifdef DEBUG_SAMPLER - DEB(" FILE: "); - DEB(dirname); - DEB(file.name()); - DEB("\tSIZE: "); - DEBUG(file.size()); + DEB(" DIR : "); + DEBUG(file.name()); #endif - String sDir = String(dirname); - String fname = String(file.name()); - String fullName = sDir + fname; - if( sampleInfoCount < SAMPLECNT ){ - strncpy( samplePlayer[ sampleInfoCount ].filename, fullName.c_str() , 32); - sampleInfoCount ++; - shortInstr[ sampleInfoCount ] = fname.substring(fname.length()-7, fname.length()-4); - } - } - delay(1); - file = root.openNextFile(); + if ( levels ) { + + ScanContents(fs, file.name(), levels - 1); + } + } else { +#ifdef DEBUG_SAMPLER + DEB(" FILE: "); + DEB(dirname); + DEB(file.name()); + DEB("\tSIZE: "); + DEBUG(file.size()); +#endif + String sDir = String(dirname); + String fname = String(file.name()); + String fullName = sDir + fname; + if ( sampleInfoCount < SAMPLECNT ) { + strncpy( samplePlayer[ sampleInfoCount ].filename, fullName.c_str() , 32); + sampleInfoCount ++; + shortInstr[ sampleInfoCount ] = fname.substring(fname.length() - 7, fname.length() - 4); + } } + delay(1); + file = root.openNextFile(); + } } -inline void Sampler::Init(){ - Effects.Init(); - Effects.SetBitCrusher( 0.0f ); - - uint32_t toRead = 512, buffPointer = 0; +inline void Sampler::Init() { + Effects.Init(); + Effects.SetBitCrusher( 0.0f ); - if( !LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)){ - DEBUG("LittleFS Mount Failed"); - return; - } - - String myDir = "/" + (String)progNumber + "/"; - - sampleInfoCount = 0; - ScanContents(LittleFS, myDir.c_str() , 5); + uint32_t toRead = 512, buffPointer = 0; + + if ( !LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)) { + DEBUG("LittleFS Mount Failed"); + return; + } + + String myDir = "/" + (String)progNumber + "/"; + + sampleInfoCount = 0; + ScanContents(LittleFS, myDir.c_str() , 5); #ifndef NO_PSRAM - // allocate ~1MB buffer in PSRAM to be able to load a whole sample set of SAMPLECNT {12} samples - if (psramFound()) { - if ( RamCache == NULL ) { - psramInit(); - RamCache = (uint8_t*)ps_malloc(PSRAM_SAMPLER_CACHE); - } - if (RamCache == NULL){ - DEBUG ("FAILED TO ALLOCATE PSRAM CACHE BUFFER!"); - } else { - DEBUG ("PSRAM BUFFER ALLOCATED! STANDARD CONFIG ENGAGED!"); - } + // allocate ~1MB buffer in PSRAM to be able to load a whole sample set of SAMPLECNT {12} samples + if (psramFound()) { + if ( RamCache == NULL ) { + psramInit(); + RamCache = (uint8_t*)ps_malloc(PSRAM_SAMPLER_CACHE); + } + if (RamCache == NULL) { + DEBUG ("FAILED TO ALLOCATE PSRAM CACHE BUFFER!"); } else { - DEBUG("STOP! Use #define NO_PSRAM option in config.h"); - while(1){} + DEBUG ("PSRAM BUFFER ALLOCATED! STANDARD CONFIG ENGAGED!"); } + } else { + DEBUG("STOP! Use #define NO_PSRAM option in config.h"); + while (1) {} + } #else - DEBF("Free heap: %d\r\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); - heap_caps_print_heap_info(MALLOC_CAP_8BIT); - if ( RamCache==NULL ) { - RamCache = (uint8_t*)malloc(RAM_SAMPLER_CACHE); - // RamCache = (uint8_t*)heap_caps_malloc(RAM_SAMPLER_CACHE, MALLOC_CAP_8BIT); - } - if (RamCache == NULL){ - DEBUG ("FAILED TO ALLOCATE RAM CACHE BUFFER!"); - } else { - DEBUG ("HEAP BUFFER ALLOCATED! MINIMAL CONFIG ENGAGED!"); - } + DEBF("Free heap: %d\r\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); + heap_caps_print_heap_info(MALLOC_CAP_8BIT); + if ( RamCache == NULL ) { + RamCache = (uint8_t*)malloc(RAM_SAMPLER_CACHE); + // RamCache = (uint8_t*)heap_caps_malloc(RAM_SAMPLER_CACHE, MALLOC_CAP_8BIT); + } + if (RamCache == NULL) { + DEBUG ("FAILED TO ALLOCATE RAM CACHE BUFFER!"); + } else { + DEBUG ("HEAP BUFFER ALLOCATED! MINIMAL CONFIG ENGAGED!"); + } #endif #ifdef DEBUG_SAMPLER - DEBUG("---\nListSamples:"); + DEBUG("---\nListSamples:"); #endif - for (int i = 0; i < sampleInfoCount; i++ ){ -#ifdef DEBUG_SAMPLER - DEBF( "s[%d]: %s\n", i, samplePlayer[i].filename ); -#endif - - File f = LittleFS.open( String( samplePlayer[i].filename) ); - - if( f ){ - union wavHeader wav; - int j = 0; - while( f.available() && ( j < sizeof(wav.wavHdr)) ){ - wav.wavHdr[j] = f.read(); - j++; - } - - j = 0; - // load sample data to the RAM/PSRAM buffer - samplePlayer[i].sampleStart = buffPointer; - while( f.available() ){ - toRead = 512; - f.read(&(RamCache[buffPointer]), toRead); - buffPointer += toRead; - } - - samplePlayer[i].file = f; /* store file pointer for future use */ - samplePlayer[i].sampleRate = wav.sampleRate; + for (int i = 0; i < sampleInfoCount; i++ ) { #ifdef DEBUG_SAMPLER - DEBF("fileSize: %d\n", wav.fileSize); - DEBF("lengthOfData: %d\n", wav.lengthOfData); - DEBF("numberOfChannels: %d\n", wav.numberOfChannels); - DEBF("sampleRate: %d\n", wav.sampleRate); - DEBF("byteRate: %d\n", wav.byteRate); - DEBF("bytesPerSample: %d\n", wav.bytesPerSample); - DEBF("bitsPerSample: %d\n", wav.bitsPerSample); - DEBF("dataSize: %d\n", wav.dataSize); - DEBF("dataStartinBuffer: %d\n", samplePlayer[i].sampleStart); - DEBF("dataInBlock: %d\n", (buffPointer-samplePlayer[i].sampleStart)); -#endif - samplePlayer[i].sampleSize = wav.dataSize; /* without mark section and size info */ - samplePlayer[i].sampleSeek = 0xFFFFFFFF; - }else{ - DEBF("error openening file!\n"); - } + DEBF( "s[%d]: %s\n", i, samplePlayer[i].filename ); +#endif + + File f = LittleFS.open( String( samplePlayer[i].filename) ); + + if ( f ) { + union wavHeader wav; + int j = 0; + while ( f.available() && ( j < sizeof(wav.wavHdr)) ) { + wav.wavHdr[j] = f.read(); + j++; + } + + j = 0; + // load sample data to the RAM/PSRAM buffer + samplePlayer[i].sampleStart = buffPointer; + while ( f.available() ) { + toRead = 512; + f.read(&(RamCache[buffPointer]), toRead); + buffPointer += toRead; + } + + samplePlayer[i].file = f; /* store file pointer for future use */ + samplePlayer[i].sampleRate = wav.sampleRate; +#ifdef DEBUG_SAMPLER + DEBF("fileSize: %d\n", wav.fileSize); + DEBF("lengthOfData: %d\n", wav.lengthOfData); + DEBF("numberOfChannels: %d\n", wav.numberOfChannels); + DEBF("sampleRate: %d\n", wav.sampleRate); + DEBF("byteRate: %d\n", wav.byteRate); + DEBF("bytesPerSample: %d\n", wav.bytesPerSample); + DEBF("bitsPerSample: %d\n", wav.bitsPerSample); + DEBF("dataSize: %d\n", wav.dataSize); + DEBF("dataStartinBuffer: %d\n", samplePlayer[i].sampleStart); + DEBF("dataInBlock: %d\n", (buffPointer - samplePlayer[i].sampleStart)); +#endif + samplePlayer[i].sampleSize = wav.dataSize; /* without mark section and size info */ + samplePlayer[i].sampleSeek = 0xFFFFFFFF; + } else { + DEBF("error openening file!\n"); } + } - for( int i = 0; i < SAMPLECNT; i++ ){ + for ( int i = 0; i < SAMPLECNT; i++ ) { - samplePlayer[i].sampleSeek = 0xFFFFFFFF; - samplePlayer[i].active = false; + samplePlayer[i].sampleSeek = 0xFFFFFFFF; + samplePlayer[i].active = false; + + decay_midi[i + 1] = 100; + samplePlayer[i].decay_midi = decay_midi[i + 1]; + samplePlayer[i].decay = 1.0f; - decay_midi[i+1] = 100; - samplePlayer[i].decay_midi = decay_midi[i+1]; - samplePlayer[i].decay = 1.0f; + offset_midi[i + 1] = 0; + samplePlayer[i].offset_midi = offset_midi[i + 1]; - offset_midi[i+1] = 0; - samplePlayer[i].offset_midi = offset_midi[i+1]; - - volume_midi[i+1] = 100; - samplePlayer[i].volume_midi = volume_midi[i+1]; + volume_midi[i + 1] = 100; + samplePlayer[i].volume_midi = volume_midi[i + 1]; - pan_midi[i+1] = 64; - samplePlayer[i].pan_midi = pan_midi[i+1]; - samplePlayer[i].pan = 0.5; + pan_midi[i + 1] = 64; + samplePlayer[i].pan_midi = pan_midi[i + 1]; + samplePlayer[i].pan = 0.5; - pitch_midi[i+1] = 64; - samplePlayer[i].pitch_midi = pitch_midi[i+1]; - if( samplePlayer[i].sampleRate > 0 ){ - samplePlayer[i].pitch = 1.0f / SAMPLE_RATE * samplePlayer[i].sampleRate; - } - }; + pitch_midi[i + 1] = 64; + samplePlayer[i].pitch_midi = pitch_midi[i + 1]; + if ( samplePlayer[i].sampleRate > 0 ) { + samplePlayer[i].pitch = 1.0f / SAMPLE_RATE * samplePlayer[i].sampleRate; + } + }; +} +inline void Sampler::SetNoteVolume_Midi( uint8_t data1) { + volume_midi[ selectedNote + 1 ] = data1; +#ifdef DEBUG_SAMPLER + DEBF("Sampler - Note[%d].midi_vol: %d\n", selectedNote, data1 ); +#endif } -inline void Sampler::SetNotePan_Midi( uint8_t data1){ +inline void Sampler::SetNotePan_Midi( uint8_t data1) { /* - samplePlayer[ selectedNote ].pan_midi = data1; - float value = MIDI_NORM * (float)data1; - samplePlayer[ selectedNote ].pan = value; + samplePlayer[ selectedNote ].pan_midi = data1; + float value = MIDI_NORM * (float)data1; + samplePlayer[ selectedNote ].pan = value; + #ifdef DEBUG_SAMPLER + DEBF("Sampler - Note[%d].pan: %0.2f\n", selectedNote, samplePlayer[ selectedNote ].pan ); + #endif + */ + pan_midi[ selectedNote + 1 ] = data1; #ifdef DEBUG_SAMPLER - DEBF("Sampler - Note[%d].pan: %0.2f\n", selectedNote, samplePlayer[ selectedNote ].pan ); -#endif -*/ - pan_midi[ selectedNote+1 ] = data1; -#ifdef DEBUG_SAMPLER - DEBF("Sampler - Note[%d].midi_pan: %0.2f\n", selectedNote, data1 ); -#endif + DEBF("Sampler - Note[%d].midi_pan: %d\n", selectedNote, data1 ); +#endif } -inline void Sampler::SetNoteDecay_Midi( uint8_t data1){ +inline void Sampler::SetNoteDecay_Midi( uint8_t data1) { /* - samplePlayer[ selectedNote ].decay_midi = data1; - float value = MIDI_NORM * (float)data1; - // samplePlayer[ selectedNote ].decay = 1.0f - (0.000005f * pow( 5000.0f, 1.0f - value) ); - samplePlayer[ selectedNote ].decay = 1.0f - value * 0.05 ; -#ifdef DEBUG_SAMPLER - DEBF("Sampler - Note[%d].decay: %0.2f\n", selectedNote, samplePlayer[ selectedNote ].decay); -#endif -*/ + samplePlayer[ selectedNote ].decay_midi = data1; + float value = MIDI_NORM * (float)data1; + // samplePlayer[ selectedNote ].decay = 1.0f - (0.000005f * pow( 5000.0f, 1.0f - value) ); + samplePlayer[ selectedNote ].decay = 1.0f - value * 0.05 ; + #ifdef DEBUG_SAMPLER + DEBF("Sampler - Note[%d].decay: %0.2f\n", selectedNote, samplePlayer[ selectedNote ].decay); + #endif + */ #ifdef DEBUG_SAMPLER - DEBF("Sampler - Note[%d].decay_midi: %0.2f\n", selectedNote, data1); -#endif - decay_midi[ selectedNote+1 ] = data1; + DEBF("Sampler - Note[%d].decay_midi: %d\n", selectedNote, data1); +#endif + decay_midi[ selectedNote + 1 ] = data1; } -inline void Sampler::SetNoteOffset_Midi( uint8_t data1){ +inline void Sampler::SetNoteOffset_Midi( uint8_t data1) { /* - samplePlayer[ selectedNote ].offset_midi = data1; -#ifdef DEBUG_SAMPLER - DEBF("Sampler - Note[%d].offset: %0.2f\n", selectedNote, samplePlayer[ selectedNote ].offset_midi); -#endif -*/ + samplePlayer[ selectedNote ].offset_midi = data1; + #ifdef DEBUG_SAMPLER + DEBF("Sampler - Note[%d].offset: %0.2f\n", selectedNote, samplePlayer[ selectedNote ].offset_midi); + #endif + */ #ifdef DEBUG_SAMPLER - DEBF("Sampler - Note[%d].offset_midi: %0.2f\n", selectedNote, data1); -#endif - offset_midi[ selectedNote+1 ] = data1; + DEBF("Sampler - Note[%d].offset_midi: %d\n", selectedNote, data1); +#endif + offset_midi[ selectedNote + 1 ] = data1; } -inline void Sampler::SetSoundPitch_Midi( uint8_t data1){ -/* - samplePlayer[ selectedNote ].pitch_midi = data1; - SetSoundPitch( MIDI_NORM * data1 ); -*/ +inline void Sampler::SetSoundPitch_Midi( uint8_t data1) { + /* + samplePlayer[ selectedNote ].pitch_midi = data1; + SetSoundPitch( MIDI_NORM * data1 ); + */ #ifdef DEBUG_SAMPLER - DEBF("Sampler - Note[%d].pitch_midi: %0.2f\n", selectedNote, data1); -#endif - pitch_midi[ selectedNote+1 ] = data1; + DEBF("Sampler - Note[%d].pitch_midi: %d\n", selectedNote, data1); +#endif + pitch_midi[ selectedNote + 1 ] = data1; } -inline void Sampler::SetSoundPitch(float value){ +inline void Sampler::SetSoundPitch(float value) { samplePlayer[ selectedNote ].pitch = pow( 2.0f, 4.0f * ( value - 0.5f ) ); -#ifdef DEBUG_SAMPLER +#ifdef DEBUG_SAMPLER DEBF("Sampler - Note[%d] pitch: %0.3f\n", selectedNote, samplePlayer[ selectedNote ].pitch ); -#endif +#endif } -inline void Sampler::NoteOn( uint8_t note, uint8_t vol ){ - - /* check for null to avoid division by zero */ - if( sampleInfoCount == 0 ){ - return; - } - int j = note % sampleInfoCount; +inline void Sampler::NoteOn( uint8_t note, uint8_t vol ) { - if( is_muted[ j+1 ]== true){ - return; - } + /* check for null to avoid division by zero */ + if ( sampleInfoCount == 0 ) { + return; + } + int j = note % sampleInfoCount; + + if ( is_muted[ j + 1 ] == true) { + return; + } #ifdef DEBUG_SAMPLER - DEBF("note %d on volume %d\n", note, vol ); - DEBF("Filename: %s \n", samplePlayer[ j ].filename ); + DEBF("note %d on volume %d\n", note, vol ); + DEBF("Filename: %s \n", samplePlayer[ j ].filename ); #endif - /* + /* if( global_pitch_decay_midi != global_pitch_decay_midi_old ){ - global_pitch_decay_midi_old = global_pitch_decay_midi; - if( global_pitch_decay_midi < 63 ){ - global_pitch_decay = (float) (65-global_pitch_decay_midi)/100; // good from -0.2 to +1.0 - }else if( global_pitch_decay_midi > 65 ){ - global_pitch_decay = (float) global_pitch_decay_midi/65; // good from -0.2 to +1.0 - }else{ - global_pitch_decay = 0.0f; - } + global_pitch_decay_midi_old = global_pitch_decay_midi; + if( global_pitch_decay_midi < 63 ){ + global_pitch_decay = (float) (65-global_pitch_decay_midi)/100; // good from -0.2 to +1.0 + }else if( global_pitch_decay_midi > 65 ){ + global_pitch_decay = (float) global_pitch_decay_midi/65; // good from -0.2 to +1.0 + }else{ + global_pitch_decay = 0.0f; + } } - */ + */ - if( volume_midi[ j+1 ] != samplePlayer[ j ].volume_midi ){ + if ( volume_midi[ j + 1 ] != samplePlayer[ j ].volume_midi ) { #ifdef DEBUG_SAMPLER - DEB("Volume"); - DEBUG( j ); - DEB(" samplePlayer"); - DEBUG( volume_midi[ j+1 ] ); -#endif - samplePlayer[ j ].volume_midi = volume_midi[ j+1 ]; - } + DEB("Volume"); + DEBUG( j ); + DEB(" samplePlayer"); + DEBUG( volume_midi[ j + 1 ] ); +#endif + samplePlayer[ j ].volume_midi = volume_midi[ j + 1 ]; + } - if( decay_midi[ j+1 ] != samplePlayer[ j ].decay_midi ){ + if ( decay_midi[ j + 1 ] != samplePlayer[ j ].decay_midi ) { #ifdef DEBUG_SAMPLER - DEB("Decay"); - DEBUG( j ); - DEB(" samplePlayer"); - DEBUG( decay_midi[ j+1 ] ); -#endif - samplePlayer[ j ].decay_midi = decay_midi[ j+1 ]; - float value = MIDI_NORM * decay_midi[ j+1 ]; - samplePlayer[ j ].decay = 1 - (0.000005 * pow( 5000, 1.0f - value) ); - } + DEB("Decay"); + DEBUG( j ); + DEB(" samplePlayer"); + DEBUG( decay_midi[ j + 1 ] ); +#endif + samplePlayer[ j ].decay_midi = decay_midi[ j + 1 ]; + float value = MIDI_NORM * decay_midi[ j + 1 ]; + samplePlayer[ j ].decay = 1 - (0.000005 * pow( 5000, 1.0f - value) ); + } - if( pitch_midi[ j+1 ] != samplePlayer[ j ].pitch_midi ){ + if ( pitch_midi[ j + 1 ] != samplePlayer[ j ].pitch_midi ) { #ifdef DEBUG_SAMPLER - DEB("Pitch"); - DEBUG( j ); - DEB(" samplePlayer"); - DEBUG( pitch_midi[ j+1 ] ); -#endif - samplePlayer[ j ].pitch_midi = pitch_midi[ j+1 ]; - float value = MIDI_NORM * pitch_midi[ j+1 ]; - samplePlayer[ j ].pitch = pow( 2.0f, 4.0f * ( value - 0.5f ) ); - } + DEB("Pitch"); + DEBUG( j ); + DEB(" samplePlayer"); + DEBUG( pitch_midi[ j + 1 ] ); +#endif + samplePlayer[ j ].pitch_midi = pitch_midi[ j + 1 ]; + float value = MIDI_NORM * pitch_midi[ j + 1 ]; + samplePlayer[ j ].pitch = pow( 2.0f, 4.0f * ( value - 0.5f ) ); + } - if( pan_midi[ j+1 ] != samplePlayer[ j ].pan_midi ){ + if ( pan_midi[ j + 1 ] != samplePlayer[ j ].pan_midi ) { #ifdef DEBUG_SAMPLER - DEB("Pan"); - DEBUG( j ); - DEB(" samplePlayer"); - DEBUG( pan_midi[ j+1 ] ); -#endif - samplePlayer[ j ].pan_midi = pan_midi[ j+1 ]; - float value = MIDI_NORM * pan_midi[ j+1 ]; - samplePlayer[ j ].pan = value; - } + DEB("Pan"); + DEBUG( j ); + DEB(" samplePlayer"); + DEBUG( pan_midi[ j + 1 ] ); +#endif + samplePlayer[ j ].pan_midi = pan_midi[ j + 1 ]; + float value = MIDI_NORM * pan_midi[ j + 1 ]; + samplePlayer[ j ].pan = value; + } - if( offset_midi[ j+1 ] != samplePlayer[ j ].offset_midi ){ + if ( offset_midi[ j + 1 ] != samplePlayer[ j ].offset_midi ) { #ifdef DEBUG_SAMPLER - DEB("Attack Offset"); - DEBUG( j ); - DEB(" samplePlayer"); - DEBUG( offset_midi[ j+1 ] ); -#endif - samplePlayer[ j ].offset_midi = offset_midi[ j+1 ]; + DEB("Attack Offset"); + DEBUG( j ); + DEB(" samplePlayer"); + DEBUG( offset_midi[ j + 1 ] ); +#endif + samplePlayer[ j ].offset_midi = offset_midi[ j + 1 ]; + } + + if ( pitchdecay_midi[ j + 1 ] != samplePlayer[ j ].pitchdecay_midi ) { + + samplePlayer[ j ].pitchdecay_midi = pitchdecay_midi[ j + 1 ]; + samplePlayer[ j ].pitchdecay = 0.0f; // default + if ( samplePlayer[ j ].pitchdecay_midi < 63 ) { + samplePlayer[ j ].pitchdecay = (float) (63 - samplePlayer[ j ].pitchdecay_midi ) / 20.0f; // good from -0.2 to +1.0 + } else if ( samplePlayer[ j ].pitchdecay_midi > 65 ) { + samplePlayer[ j ].pitchdecay = (float) - ( samplePlayer[ j ].pitchdecay_midi - 65) / 30.0f; // good from -0.2 to +1.0 } - - if( pitchdecay_midi[ j+1 ] != samplePlayer[ j ].pitchdecay_midi ){ - - samplePlayer[ j ].pitchdecay_midi = pitchdecay_midi[ j+1 ]; - samplePlayer[ j ].pitchdecay = 0.0f; // default - if( samplePlayer[ j ].pitchdecay_midi < 63 ){ - samplePlayer[ j ].pitchdecay = (float) (63 - samplePlayer[ j ].pitchdecay_midi ) /20.0f; // good from -0.2 to +1.0 - }else if( samplePlayer[ j ].pitchdecay_midi > 65 ){ - samplePlayer[ j ].pitchdecay = (float) -( samplePlayer[ j ].pitchdecay_midi -65) /30.0f; // good from -0.2 to +1.0 - } #ifdef DEBUG_SAMPLER - DEB("PitchDecay"); - DEBUG( j ); - DEB(" samplePlayer "); - DEB( pitchdecay_midi[ j+1 ] ); - DEB(" FloatValue: " ); - DEBUG( samplePlayer[ j ].pitchdecay ); -#endif - } + DEB("PitchDecay"); + DEBUG( j ); + DEB(" samplePlayer "); + DEB( pitchdecay_midi[ j + 1 ] ); + DEB(" FloatValue: " ); + DEBUG( samplePlayer[ j ].pitchdecay ); +#endif + } - struct samplePlayerS *newSamplePlayer = &samplePlayer[j]; - - if( newSamplePlayer->active ){ - /* add last output signal to slow release to avoid noise */ - slowRelease = newSamplePlayer->signal; - } + struct samplePlayerS *newSamplePlayer = &samplePlayer[j]; + + if ( newSamplePlayer->active ) { + /* add last output signal to slow release to avoid noise */ + slowRelease = newSamplePlayer->signal; + } + + newSamplePlayer->samplePosF = 4.0f * newSamplePlayer->offset_midi; // 0.0f; + newSamplePlayer->samplePos = 4 * newSamplePlayer->offset_midi; // 0; + // newSamplePlayer->lastDataOut = PRELOADSIZE; /* trigger loading second half */ - newSamplePlayer->samplePosF = 4.0f * newSamplePlayer->offset_midi; // 0.0f; - newSamplePlayer->samplePos = 4 * newSamplePlayer->offset_midi; // 0; - // newSamplePlayer->lastDataOut = PRELOADSIZE; /* trigger loading second half */ - - newSamplePlayer->volume = vol * MIDI_NORM * newSamplePlayer->volume_midi * MIDI_NORM; - newSamplePlayer->vel = 1.0f; - newSamplePlayer->dataIn = 0; - newSamplePlayer->sampleSeek = 44 + 4*newSamplePlayer->offset_midi; // 16 Bit-Samples wee nee + newSamplePlayer->volume = vol * MIDI_NORM * newSamplePlayer->volume_midi * MIDI_NORM; + newSamplePlayer->vel = 1.0f; + newSamplePlayer->dataIn = 0; + newSamplePlayer->sampleSeek = 44 + 4 * newSamplePlayer->offset_midi; // 16 Bit-Samples wee nee - newSamplePlayer->active = true; + newSamplePlayer->active = true; } -inline void Sampler::NoteOff( uint8_t note ){ - /* - * nothing to do yet - * we could stop samples if we want to - */ - if( sampleInfoCount == 0 ){ - return; - } - // int j = note % sampleInfoCount; - // samplePlayer[j]->active = false; +inline void Sampler::NoteOff( uint8_t note ) { + /* + nothing to do yet + we could stop samples if we want to + */ + if ( sampleInfoCount == 0 ) { + return; + } + // int j = note % sampleInfoCount; + // samplePlayer[j]->active = false; } -void Sampler::SetPlaybackSpeed( float value ){ - value = pow( 2.0f, 4.0f * (value - 0.5) ); - DEBF( "SetPlaybackSpeed: %0.2f\n", value ); - sampler_playback = value; +void Sampler::SetPlaybackSpeed( float value ) { + value = pow( 2.0f, 4.0f * (value - 0.5) ); + DEBF( "SetPlaybackSpeed: %0.2f\n", value ); + sampler_playback = value; } -void Sampler::SetProgram( uint8_t prog ){ +void Sampler::SetProgram( uint8_t prog ) { progNumber = prog % countPrograms; Init(); } @@ -450,7 +457,11 @@ inline void Sampler::ParseCC(uint8_t cc_number , uint8_t cc_value) { break; case CC_808_BD_TONE: SelectNote( 0 ); // BD - SetSoundPitch_Midi ( cc_value ); + SetSoundPitch_Midi ( cc_value ); + break; + case CC_808_BD_LEVEL: + SelectNote( 0 ); // BD + SetNoteVolume_Midi ( cc_value ); break; case CC_808_SD_SNAP: SelectNote( 1 ); // SD @@ -460,86 +471,110 @@ inline void Sampler::ParseCC(uint8_t cc_number , uint8_t cc_value) { SelectNote( 1 ); // SD SetSoundPitch_Midi( cc_value ); break; -/* -#define CC_808_BD_TONE 21 // Specific per drum control -#define CC_808_BD_DECAY 23 -#define CC_808_BD_LEVEL 24 -#define CC_808_SD_TONE 25 -#define CC_808_SD_SNAP 26 -#define CC_808_SD_LEVEL 29 -#define CC_808_CH_TUNE 61 -#define CC_808_CH_LEVEL 63 -#define CC_808_OH_TUNE 80 -#define CC_808_OH_DECAY 81 -#define CC_808_OH_LEVEL 82 -*/ + case CC_808_SD_LEVEL: + SelectNote( 1 ); // SD + SetNoteVolume_Midi( cc_value ); + break; + case CC_808_CH_TUNE: + SelectNote( 6 ); // CH + SetSoundPitch_Midi( cc_value ); + break; + case CC_808_CH_LEVEL: + SelectNote( 6 ); // CH + SetNoteVolume_Midi( cc_value ); + break; + case CC_808_OH_TUNE: + SelectNote( 7 ); // OH + SetSoundPitch_Midi( cc_value ); + break; + case CC_808_OH_LEVEL: + SelectNote( 7 ); // OH + SetNoteVolume_Midi( cc_value ); + break; + case CC_808_OH_DECAY: + SelectNote( 7 ); // OH + SetNoteDecay_Midi( cc_value ); + break; + /* + #define CC_808_BD_TONE 21 // Specific per drum control + #define CC_808_BD_DECAY 23 + #define CC_808_BD_LEVEL 24 + #define CC_808_SD_TONE 25 + #define CC_808_SD_SNAP 26 + #define CC_808_SD_LEVEL 29 + #define CC_808_CH_TUNE 61 + #define CC_808_CH_LEVEL 63 + #define CC_808_OH_TUNE 80 + #define CC_808_OH_DECAY 81 + #define CC_808_OH_LEVEL 82 + */ } } -inline void Sampler::Process( float *left, float *right ){ +inline void Sampler::Process( float *left, float *right ) { - if( progNumber != program_tmp ){ - SetProgram( program_tmp ); - } - - float signal_l = 0.0f; - signal_l += slowRelease; - float signal_r = 0.0f; - signal_r += slowRelease; - - slowRelease = slowRelease * 0.99; // go slowly to zero - - for( int i = 0; i < SAMPLECNT; i++ ){ - - if( samplePlayer[i].active ){ - samplePlayer[i].samplePos = samplePlayer[i].samplePosF; - samplePlayer[i].samplePos -= samplePlayer[i].samplePos % 2; - - uint32_t dataOut = samplePlayer[i].samplePos; - // DEBUG(dataOut); - - // - // reconstruct signal from data - // - uint8_t byte2 , byte1; - union { - uint16_t u16; - int16_t s16; - } sampleU; - byte1 = RamCache[samplePlayer[i].sampleStart + dataOut]; - byte2 = RamCache[samplePlayer[i].sampleStart + dataOut + 1]; - sampleU.s16 = (((uint16_t)byte2) << 8U) + (uint16_t)byte1; - - samplePlayer[i].signal = (float)(samplePlayer[i].volume) * ((float)sampleU.s16) * 0.00005f; - - signal_l += samplePlayer[i].signal * samplePlayer[i].vel * ( 1- samplePlayer[i].pan ); - - signal_r += samplePlayer[i].signal * samplePlayer[i].vel * samplePlayer[i].pan; - - samplePlayer[i].vel *= samplePlayer[i].decay; - - samplePlayer[i].samplePos += 2; // we have consumed two bytes - - if ( samplePlayer[i].pitchdecay > 0.0f ) { - samplePlayer[i].samplePosF += 2.0f * sampler_playback * ( samplePlayer[i].pitch + samplePlayer[i].pitchdecay*samplePlayer[i].vel ); // we have consumed two bytes - } else { - samplePlayer[i].samplePosF += 2.0f * sampler_playback * ( samplePlayer[i].pitch + samplePlayer[i].pitchdecay*(1-samplePlayer[i].vel) ); // we have consumed two bytes - } - - if( samplePlayer[i].samplePos >= samplePlayer[i].sampleSize ){ - samplePlayer[i].active = false; - samplePlayer[i].samplePos= 0; - samplePlayer[i].samplePosF= 0.0f; - } - } + if ( progNumber != program_tmp ) { + SetProgram( program_tmp ); + } + + float signal_l = 0.0f; + signal_l += slowRelease; + float signal_r = 0.0f; + signal_r += slowRelease; + + slowRelease = slowRelease * 0.99; // go slowly to zero + + for ( int i = 0; i < SAMPLECNT; i++ ) { + + if ( samplePlayer[i].active ) { + samplePlayer[i].samplePos = samplePlayer[i].samplePosF; + samplePlayer[i].samplePos -= samplePlayer[i].samplePos % 2; + + uint32_t dataOut = samplePlayer[i].samplePos; + // DEBUG(dataOut); + + // + // reconstruct signal from data + // + uint8_t byte2 , byte1; + union { + uint16_t u16; + int16_t s16; + } sampleU; + byte1 = RamCache[samplePlayer[i].sampleStart + dataOut]; + byte2 = RamCache[samplePlayer[i].sampleStart + dataOut + 1]; + sampleU.s16 = (((uint16_t)byte2) << 8U) + (uint16_t)byte1; + + samplePlayer[i].signal = (float)(samplePlayer[i].volume) * ((float)sampleU.s16) * 0.00005f; + + signal_l += samplePlayer[i].signal * samplePlayer[i].vel * ( 1 - samplePlayer[i].pan ); + + signal_r += samplePlayer[i].signal * samplePlayer[i].vel * samplePlayer[i].pan; + + samplePlayer[i].vel *= samplePlayer[i].decay; + + samplePlayer[i].samplePos += 2; // we have consumed two bytes + + if ( samplePlayer[i].pitchdecay > 0.0f ) { + samplePlayer[i].samplePosF += 2.0f * sampler_playback * ( samplePlayer[i].pitch + samplePlayer[i].pitchdecay * samplePlayer[i].vel ); // we have consumed two bytes + } else { + samplePlayer[i].samplePosF += 2.0f * sampler_playback * ( samplePlayer[i].pitch + samplePlayer[i].pitchdecay * (1 - samplePlayer[i].vel) ); // we have consumed two bytes + } + + if ( samplePlayer[i].samplePos >= samplePlayer[i].sampleSize ) { + samplePlayer[i].active = false; + samplePlayer[i].samplePos = 0; + samplePlayer[i].samplePosF = 0.0f; + } } - Effects.Process( &signal_l, &signal_r ); - signal_l *= _volume; - signal_r *= _volume; - // *left = fclamp(signal_l, -1.0f, 1.0f); - // *right = fclamp(signal_r, -1.0f, 1.0f); - *left = fast_tanh(signal_l); - *right = fast_tanh(signal_r); + } + Effects.Process( &signal_l, &signal_r ); + signal_l *= _volume; + signal_r *= _volume; + *left = fclamp(signal_l, -1.0f, 1.0f); + *right = fclamp(signal_r, -1.0f, 1.0f); + // *left = fast_tanh(signal_l); + // *right = fast_tanh(signal_r); } diff --git a/synthvoice.h b/synthvoice.h index c7c6b81..a55faf7 100644 --- a/synthvoice.h +++ b/synthvoice.h @@ -3,7 +3,7 @@ #define FILTER_TYPE 2 // 0 = Moogladder by Victor Lazzarini // 1 = Tim Stilson's model by Aaron Krajeski - // 2 = open303 filter + // 2 = Open303 filter /* * You shouldn't need to change something below this line */ @@ -86,7 +86,8 @@ class SynthVoice { bool _slide = false; bool _portamento = false; // slide, but managed by CC 65 float _tempo = 100.0f; - float _waveMix = 1.0f ; // square = 0.0f and saw = 1.0f + float _waveMix = 1.0f ; // exp-square = 0.0f and exp-saw = 1.0f + int8_t _waveBase = 0; // calculate pointers to the two tables to blend float _sampleRate = (float)SAMPLE_RATE; uint16_t bufSize = DMA_BUF_LEN; float _detuneCents = 0.0f; @@ -110,12 +111,16 @@ class SynthVoice { uint32_t _noteStartTime = 0; uint8_t _midiNote = 69; float _currentStep = 1.0f; + float _currentPeriod = 1.0f; // should be int (trying to come exactly to 0 phase) + float _avgStep = 1.0f; + float _avgPeriod = 1.0f; // measured in samples float _targetStep = 0.0f; + float _targetPeriod = 0.0f; float _deltaStep = 0.0f; float _slideMs = 60.0f; float _phaze = 0.0f; - // MEG and FEG params + // parameters of envelopes float _ampEnvPosition = 0.0f; float _filterEnvPosition = 0.0f; float _ampAttackMs = 3.0f; diff --git a/synthvoice.ino b/synthvoice.ino index f35dcbd..3dcee81 100644 --- a/synthvoice.ino +++ b/synthvoice.ino @@ -2,8 +2,8 @@ void SynthVoice::Init() { - _envMod = 0.5f; - _accentLevel = 0.5f; + _envMod = 0.5f; + _accentLevel = 0.5f; _cutoff = 0.2f; // 0..1 normalized freq range. Keep in mind that EnvMod set to max practically floats this range _filter_freq = linToExp(_cutoff, 0.0f, 1.0f, MIN_CUTOFF_FREQ, MAX_CUTOFF_FREQ); _reso = 0.4f; @@ -11,8 +11,8 @@ void SynthVoice::Init() { _drive = 0.0f; _eAmpEnvState = ENV_IDLE; _eFilterEnvState = ENV_IDLE; -// midiNotes[0] = -1; -// midiNotes[1] = -1; + // midiNotes[0] = -1; + // midiNotes[1] = -1; _midiNote = 69; _currentStep = 1.0f; _targetStep = 1.0f; @@ -20,8 +20,8 @@ void SynthVoice::Init() { _slideMs = 60.0f; _phaze = 0.0f; mva1.n = 0 ; - - // MEG and FEG params + + // parameters of envelopes _ampEnvPosition = 0.0; _filterEnvPosition = 0.0; _ampAttackMs = 3.0; @@ -33,13 +33,13 @@ void SynthVoice::Init() { _filterAttackMs = 5.0; _filterDecayMs = 200.0; _filterEnvAttackStep = 15.0; - _filterEnvDecayStep = 1.0; - + _filterEnvDecayStep = 1.0; + Wfolder.Init(); Drive.Init(); - Filter.Init((float)SAMPLE_RATE); - Filter.SetMode(TeeBeeFilter::LP_18); + Filter.Init((float)SAMPLE_RATE); + Filter.SetMode(TeeBeeFilter::LP_18); highpass1.setMode(OnePoleFilter::HIGHPASS); highpass1.setCutoff(44.486f); highpass2.setMode(OnePoleFilter::HIGHPASS); @@ -53,50 +53,51 @@ void SynthVoice::Init() { inline void SynthVoice::Generate() { float samp = 0.0f, filtEnv = 0.0f, ampEnv = 0.0f, final_cut = 0.0f; for (uint16_t i = 0; i < DMA_BUF_LEN; ++i) { - if (_index==0) prescaler++; + prescaler += (1 - _index); filtEnv = GetFilterEnv(); ampEnv = GetAmpEnv(); if (_eAmpEnvState != ENV_IDLE) { - samp = (float)(1.0f - _waveMix) * (float)lookupTable(square_2048,_phaze) + (float)_waveMix * (float)lookupTable(exp_2048,_phaze) ; // lookup and mix waveforms + // samp = (float)((1.0f - _waveMix) * lookupTable(*(tables[_waveBase]), _phaze)) + (float)(_waveMix * lookupTable(*(tables[_waveBase+1]), _phaze)) ; // lookup and blend waveforms + samp = (float)((1.0f - _waveMix) * lookupTable(exp_square_tbl, _phaze)) + (float)(_waveMix * lookupTable(exp_tbl, _phaze)) ; // lookup and blend waveforms } else { - samp = 0.0f; + samp = 0.0f; } -// if (i % 4 == 0) { - final_cut = (float)_filter_freq * ( (float)_envMod * ((float)filtEnv - 0.2f) + 0.3f * (float)_accentation + 1.0f ); - final_cut = filtDeclicker.Process( final_cut); - Filter.SetCutoff( final_cut ); -// } - + // if (i % 4 == 0) { + final_cut = (float)_filter_freq * ( (float)_envMod * ((float)filtEnv - 0.2f) + 0.3f * (float)_accentation + 1.0f ); + final_cut = filtDeclicker.Process( final_cut); + Filter.SetCutoff( final_cut ); + // } + samp = highpass1.getSample(samp); // pre-filter highpass - samp = Filter.Process(samp); + samp = Filter.Process(samp); samp = allpass.getSample(samp); - samp = highpass2.getSample(samp); - -/* - if ( i % DMA_BUF_LEN == 0 && _index == 0) { - avgmax = avgmax*0.999 - (avgmax - avgmid) * 0.001f ; - avgmin = avgmin*0.999 + (avgmid - avgmin) * 0.001f ; - if (samp > avgmax) avgmax = samp ; - else if (samp < avgmin) avgmin = samp ; - // avgmid = 0.5f * (avgmax+avgmin) * 0.001f + 0.999f * avgmid; - // DEBF( " %0.3f %0.3f %0.3f\r\n", avgmin, avgmid, avgmax ); - DEBF("%0.3f\r\n", avgmax-avgmin); - // samp -= avgmid; - } - -*/ + samp = highpass2.getSample(samp); + + /* + if ( i % DMA_BUF_LEN == 0 && _index == 0) { + avgmax = avgmax*0.999 - (avgmax - avgmid) * 0.001f ; + avgmin = avgmin*0.999 + (avgmid - avgmin) * 0.001f ; + if (samp > avgmax) avgmax = samp ; + else if (samp < avgmin) avgmin = samp ; + // avgmid = 0.5f * (avgmax+avgmin) * 0.001f + 0.999f * avgmid; + // DEBF( " %0.3f %0.3f %0.3f\r\n", avgmin, avgmid, avgmax ); + DEBF("%0.3f\r\n", avgmax-avgmin); + // samp -= avgmid; + } + + */ ampEnv = ampDeclicker.Process(ampEnv); samp *= ampEnv; - + samp = Drive.Process(samp); samp = Wfolder.Process(samp); samp *= volume; - -// if ( prescaler % DMA_BUF_LEN == 0 && _index == 0 ) DEBF( "off(Hz) %0.3f\r\n", _offset ); - + + // if ( prescaler % DMA_BUF_LEN == 0 && _index == 0 ) DEBF( "off(Hz) %0.3f\r\n", _offset ); + if ((_slide || _portamento) && _deltaStep != 0.0f) { // portamento / slide processing if (fabs(_targetStep - _currentStep) >= fabs(_deltaStep)) { _currentStep += _deltaStep; @@ -105,15 +106,18 @@ inline void SynthVoice::Generate() { _deltaStep = 0.0f; } } + // Increment and wrap phase _phaze += _currentStep; if ( _phaze >= WAVE_SIZE) { - _phaze -= WAVE_SIZE ; + // _phaze -= WAVE_SIZE ; + _phaze = 0.0f; } - //synth_buf[_index][i] = fast_tanh(samp); // mono + + //synth_buf[_index][i] = fast_tanh(samp); // mono //synth_buf[_index][i] = ampDeclicker.Process(samp); synth_buf[_index][i] = samp; - + } //DEBF("synt%d = %0.5f\r\n", _index , samp); } @@ -121,7 +125,7 @@ inline void SynthVoice::Generate() { inline void SynthVoice::ParseCC(uint8_t cc_number , uint8_t cc_value) { switch (cc_number) { - + case CC_303_PORTATIME: _slideMs = (float)cc_value; break; @@ -130,17 +134,22 @@ inline void SynthVoice::ParseCC(uint8_t cc_number , uint8_t cc_value) { break; case CC_303_PAN: pan = (float)cc_value * MIDI_NORM; - break; + break; case CC_303_PORTAMENTO: - _portamento = (cc_value >= 64); - break; + _portamento = (cc_value >= 64); + break; case CC_303_WAVEFORM: + /* + _waveBase = (uint8_t)(((float)cc_value * 2.99999f * MIDI_NORM)) ; // 0, 1, 2 range + DEBF("base %d\r\n", _waveBase ); + _waveMix = ((float)cc_value - (float)(_waveBase*42.33333f)) * MIDI_NORM * 3.0f; + DEBF("mix %0.5f\r\n", _waveMix );*/ _waveMix = (float)cc_value * MIDI_NORM; break; case CC_303_RESO: _reso = cc_value * MIDI_NORM ; Filter.SetResonance(_reso); - break; + break; case CC_303_DECAY: // Env release _filterDecayMs = 15.0f + (float)cc_value * MIDI_NORM * 5000.0f ; _ampDecayMs = 15.0f + (float)cc_value * MIDI_NORM * 7500.0f; @@ -161,8 +170,6 @@ inline void SynthVoice::ParseCC(uint8_t cc_number , uint8_t cc_value) { break; case CC_303_ENVMOD_LVL: _envMod = (float)cc_value * MIDI_NORM; - // calcEnvModScalerAndOffset(); - // DEBF("%0.3f %0.3f\r\n", _envScaler, _envOffset); break; case CC_303_ACCENT_LVL: _accentLevel = (float)cc_value * MIDI_NORM; @@ -173,7 +180,7 @@ inline void SynthVoice::ParseCC(uint8_t cc_number , uint8_t cc_value) { break; case CC_303_OVERDRIVE: _drive = (float)cc_value * MIDI_NORM ; - Drive.SetDrive(_drive ); + Drive.SetDrive(_drive ); break; case CC_303_SATURATOR: _saturator = (float)cc_value * MIDI_NORM; @@ -192,24 +199,24 @@ float SynthVoice::GetAmpEnv() { case ENV_INIT: k_acc = (1.0f + 0.3f * _accentation); _ampEnvPosition = 0.0f; - _ampEnvAttackStep = _msToSteps * one_div( _ampAttackMs+0.0001f); - _ampEnvDecayStep = _msToSteps * one_div(_ampDecayMs+0.0001f); - _ampEnvReleaseStep = _msToSteps * one_div(_ampReleaseMs+0.0001f); + _ampEnvAttackStep = _msToSteps * one_div( _ampAttackMs + 0.0001f); + _ampEnvDecayStep = _msToSteps * one_div(_ampDecayMs + 0.0001f); + _ampEnvReleaseStep = _msToSteps * one_div(_ampReleaseMs + 0.0001f); if (_accent) { _ampEnvDecayStep *= 3.0f; - _ampEnvReleaseStep = _msToSteps * one_div(50.0f +0.0001f); + _ampEnvReleaseStep = _msToSteps * one_div(50.0f + 0.0001f); } _eAmpEnvState = ENV_ATTACK; - ret_val = (-exp_2048[ 0 ] + 1.0f) * 0.5f; + ret_val = (-exp_tbl[ 0 ] + 1.0f) * 0.5f; break; case ENV_ATTACK: _ampEnvPosition += _ampEnvAttackStep; if (_ampEnvPosition >= WAVE_SIZE) { _eAmpEnvState = ENV_DECAY; _ampEnvPosition = 0; - ret_val = (-exp_2048[ WAVE_SIZE-1 ] + 1.0f) * 0.5f * k_acc; + ret_val = (-exp_tbl[ WAVE_SIZE - 1 ] + 1.0f) * 0.5f * k_acc; } else { - ret_val = (-lookupTable(exp_2048, _ampEnvPosition ) + 1.0f) * 0.5f * k_acc; + ret_val = (-lookupTable(exp_tbl, _ampEnvPosition ) + 1.0f) * 0.5f * k_acc; if (pass_val > ret_val) ret_val = pass_val; } pass_val = ret_val; @@ -221,62 +228,62 @@ float SynthVoice::GetAmpEnv() { _ampEnvPosition = 0; ret_val = sust_level; } else { - ret_val = sust_level + k_sust * (lookupTable(exp_2048, _ampEnvPosition) + 1.0f) * 0.5f * k_acc; + ret_val = sust_level + k_sust * (lookupTable(exp_tbl, _ampEnvPosition) + 1.0f) * 0.5f * k_acc; } pass_val = ret_val; break; - case ENV_SUSTAIN: + case ENV_SUSTAIN: ret_val = sust_level; // asuming sustain to be endless pass_val = ret_val; _ampEnvPosition = 0; break; - case ENV_RELEASE: + case ENV_RELEASE: if (_ampEnvPosition >= WAVE_SIZE) { _eAmpEnvState = ENV_IDLE; _ampEnvPosition = 0; ret_val = 0.0f; } else { if (_ampEnvPosition <= _ampEnvReleaseStep) release_lvl = pass_val; - ret_val = release_lvl * (lookupTable(exp_2048, _ampEnvPosition) + 1.0f) * 0.5f * k_acc; + ret_val = release_lvl * (lookupTable(exp_tbl, _ampEnvPosition) + 1.0f) * 0.5f * k_acc; } _ampEnvPosition += _ampEnvReleaseStep; - pass_val=ret_val; + pass_val = ret_val; break; case ENV_IDLE: ret_val = 0.0f; break; default: - ret_val = 0.0f; + ret_val = 0.0f; } if (_accent) { - + } return ret_val; } inline float SynthVoice::GetFilterEnv() { static volatile float ret_val = 0.0f; - switch(_eFilterEnvState) { + switch (_eFilterEnvState) { case ENV_INIT: - // k_acc = (1.0f + 0.45f * _accentation); + // k_acc = (1.0f + 0.45f * _accentation); _offset = max(ret_val, _offset); _filterEnvPosition = 0.0f; - _filterEnvAttackStep = _msToSteps * one_div(_filterAttackMs+0.0001f); - _filterEnvDecayStep = _msToSteps * one_div(_filterDecayMs+0.0001f); + _filterEnvAttackStep = _msToSteps * one_div(_filterAttackMs + 0.0001f); + _filterEnvDecayStep = _msToSteps * one_div(_filterDecayMs + 0.0001f); if (_accent) { _filterEnvAttackStep *= 1.4f; _filterEnvDecayStep *= 5.0f; } _eFilterEnvState = ENV_ATTACK; - ret_val = (-exp_2048[ 0 ] + 1.0f) * 0.5f ; + ret_val = (-exp_tbl[ 0 ] + 1.0f) * 0.5f ; break; case ENV_ATTACK: if (_filterEnvPosition >= (float)WAVE_SIZE) { _eFilterEnvState = ENV_DECAY; _filterEnvPosition = 0.0f; - ret_val = (-exp_2048[ WAVE_SIZE-1 ] + 1.0f) * 0.5f ; + ret_val = (-exp_tbl[ WAVE_SIZE - 1 ] + 1.0f) * 0.5f ; } else { - ret_val = (-lookupTable(exp_2048, _filterEnvPosition) + 1.0f) * 0.5f ; + ret_val = (-lookupTable(exp_tbl, _filterEnvPosition) + 1.0f) * 0.5f ; } _filterEnvPosition += _filterEnvAttackStep; break; @@ -286,7 +293,7 @@ inline float SynthVoice::GetFilterEnv() { _filterEnvPosition = 0.0f; ret_val = 0.0f; } else { - ret_val = (lookupTable(exp_2048, _filterEnvPosition) + 1.0f) * 0.5f ; + ret_val = (lookupTable(exp_tbl, _filterEnvPosition) + 1.0f) * 0.5f ; } _filterEnvPosition += _filterEnvDecayStep; _offset *= _offset_leak; @@ -295,7 +302,7 @@ inline float SynthVoice::GetFilterEnv() { ret_val = 0.0f; _offset *= _offset_leak; break; - default: + default: ret_val = 0.0f; } ret_val += _offset; @@ -304,24 +311,24 @@ inline float SynthVoice::GetFilterEnv() { // calcEnvModScalerAndOffset() taken from open303 code inline void SynthVoice::calcEnvModScalerAndOffset() { - // define some constants that arise from the measurements: - const float c0 = 313.0f; // lowest nominal cutoff - const float c1 = 2394.0f; // highest nominal cutoff - const float oF = 0.048292930943553f; // factor in line equation for offset - const float oC = 0.294391201442418f; // constant in line equation for offset - const float sLoF = 3.773996325111173f; // factor in line eq. for scaler at low cutoff - const float sLoC = 0.736965594166206f; // constant in line eq. for scaler at low cutoff - const float sHiF = 4.194548788411135f; // factor in line eq. for scaler at high cutoff - const float sHiC = 0.864344900642434f; // constant in line eq. for scaler at high cutoff - - // do the calculation of the scaler and offset: - float e = _envMod; - // float c = expToLin(_filter_freq, c0, c1, 0.0, 1.0); - float c = _cutoff; - float sLo = sLoF*e + sLoC; - float sHi = sHiF*e + sHiC; - _envScaler = (1-c)*sLo + c*sHi; - _envOffset = oF*c + oC; + // define some constants that arise from the measurements: + const float c0 = 313.0f; // lowest nominal cutoff + const float c1 = 2394.0f; // highest nominal cutoff + const float oF = 0.048292930943553f; // factor in line equation for offset + const float oC = 0.294391201442418f; // constant in line equation for offset + const float sLoF = 3.773996325111173f; // factor in line eq. for scaler at low cutoff + const float sLoC = 0.736965594166206f; // constant in line eq. for scaler at low cutoff + const float sHiF = 4.194548788411135f; // factor in line eq. for scaler at high cutoff + const float sHiC = 0.864344900642434f; // constant in line eq. for scaler at high cutoff + + // do the calculation of the scaler and offset: + float e = _envMod; + // float c = expToLin(_filter_freq, c0, c1, 0.0, 1.0); + float c = _cutoff; + float sLo = sLoF * e + sLoC; + float sHi = sHiF * e + sHiC; + _envScaler = (1 - c) * sLo + c * sHi; + _envOffset = oF * c + oC; } // The following code initially written by Anton Savov, @@ -332,102 +339,108 @@ inline void SynthVoice::calcEnvModScalerAndOffset() { inline void SynthVoice::on_midi_noteON(uint8_t note, uint8_t velocity) { - mva_note_on(&mva1, note, (velocity >= 80)); + mva_note_on(&mva1, note, (velocity >= 80)); - bool slide = (mva1.n > 1); - bool accent = (mva1.accents[0]); - note = mva1.notes[0] ; - note_on(note, slide, accent); + bool slide = (mva1.n > 1); + bool accent = (mva1.accents[0]); + note = mva1.notes[0] ; + note_on(note, slide, accent); } inline void SynthVoice::on_midi_noteOFF(uint8_t note, uint8_t velocity) { - if (mva1.n == 0) { return; } - uint8_t tmp_note = mva1.notes[0]; - uint8_t tmp_accent = mva1.accents[0]; - mva_note_off(&mva1, note); + if (mva1.n == 0) { + return; + } + uint8_t tmp_note = mva1.notes[0]; + uint8_t tmp_accent = mva1.accents[0]; + mva_note_off(&mva1, note); - if (mva1.n > 0) + if (mva1.n > 0) + { + if (mva1.notes[0] != tmp_note) { - if (mva1.notes[0] != tmp_note) - { - bool accent = (mva1.accents[0] ); - bool slide = 1; - note = mva1.notes[0]; - - note_on(note, slide, accent); - } + bool accent = (mva1.accents[0] ); + bool slide = 1; + note = mva1.notes[0]; + + note_on(note, slide, accent); } - else { note_off(); } + } + else { + note_off(); + } } void SynthVoice::mva_note_on(mva_data *p, uint8_t note, uint8_t accent) { - uint8_t s = 0; - uint8_t i = 0; - - // shift all notes back - uint8_t m = p->n + 1; - m = (m > MIDI_MVA_SZ ? MIDI_MVA_SZ : m); - s = m; - i = m; - while (i > 0) - { - --s; - p->notes[i] = p->notes[s]; - p->accents[i] = p->accents[s]; - i = s; - } - // put the new note first - p->notes[0] = note; - p->accents[0] = accent; - // update the voice counter - p->n = m; + uint8_t s = 0; + uint8_t i = 0; + + // shift all notes back + uint8_t m = p->n + 1; + m = (m > MIDI_MVA_SZ ? MIDI_MVA_SZ : m); + s = m; + i = m; + while (i > 0) + { + --s; + p->notes[i] = p->notes[s]; + p->accents[i] = p->accents[s]; + i = s; + } + // put the new note first + p->notes[0] = note; + p->accents[0] = accent; + // update the voice counter + p->n = m; } void SynthVoice::mva_note_off(mva_data *p, uint8_t note) { - uint8_t s = 0; + uint8_t s = 0; - // find if the note is actually in the buffer - uint8_t m = p->n; - uint8_t i = m; - while (i) // count backwards (oldest notes first) + // find if the note is actually in the buffer + uint8_t m = p->n; + uint8_t i = m; + while (i) // count backwards (oldest notes first) + { + --i; + if (note == p->notes[i] ) { - --i; - if (note == p->notes[i] ) + // found it! + if (i < (p->n - 1)) // don't shift if this was the last note.. + { + // remove it now.. just shift everything after it + s = i; + while (i < m) { - // found it! - if (i < (p->n - 1)) // don't shift if this was the last note.. - { - // remove it now.. just shift everything after it - s = i; - while (i < m) - { - ++s; - p->notes[i] = p->notes[s]; - p->accents[i] = p->accents[s]; - i = s; - } - } - // update the voice counter - if (m > 0) { p->n = m - 1; } - break; + ++s; + p->notes[i] = p->notes[s]; + p->accents[i] = p->accents[s]; + i = s; } + } + // update the voice counter + if (m > 0) { + p->n = m - 1; + } + break; } + } } void SynthVoice::mva_reset(mva_data *p) { - p->n = 0; + p->n = 0; } -void SynthVoice::note_on(uint8_t midiNote, bool slide, bool accent) +void SynthVoice::note_on(uint8_t midiNote, bool slide, bool accent) { _accent = accent; _slide = slide || _portamento; - _targetStep = midi_2048_steps[midiNote]; + _targetStep = midi_tbl_steps[midiNote]; if (_slide) { _deltaStep = (_targetStep - _currentStep) * (1000.0f * DIV_SAMPLE_RATE / _slideMs ); } else { @@ -435,14 +448,14 @@ void SynthVoice::note_on(uint8_t midiNote, bool slide, bool accent) _deltaStep = 0.0f ; _eAmpEnvState = ENV_INIT; _eFilterEnvState = ENV_INIT; - + } if (mva1.n == 1) { if (_accent) _accentation = _accentLevel; else _accentation = 0.0f; } } -void SynthVoice::note_off() +void SynthVoice::note_off() { _eAmpEnvState = ENV_RELEASE; _ampEnvPosition = 0; diff --git a/tables.ino b/tables.ino index c9f0f7e..eee500b 100644 --- a/tables.ino +++ b/tables.ino @@ -3,20 +3,22 @@ float noteToFreq(int note) { return (440.0f / 32.0f) * pow(2, ((float)(note - 9) / 12.0)); } -float expSaw2048(uint16_t i) { // this one contains a piece of exp(-x) normalized to fit into (-1.0 .. 1.0) , "saw", "square" and envelopes are generated basing on this table - float res = exp((float)(-i)/486.0f) * 2.03f - 1.03f; +float expSaw_fill(uint16_t i) { // this one contains a piece of exp(-x) normalized to fit into (-1.0 .. 1.0) , "saw", "square" and envelopes are generated basing on this table + float x = (float)i * 2048.0f / (float)WAVE_SIZE; + float res = exp((float)(-x)/486.0f) * 2.03f - 1.03f; return res; } -float tanh2048(uint16_t i) { + +float tanh_fill(uint16_t i) { float res = tanh( (float)i * TANH_LOOKUP_MAX / (float)WAVE_SIZE); // 0.0 -- 5.0 argument return res; } -float expSquare2048(uint16_t i) { // requires exp() table +float expSquare_fill(uint16_t i) { // requires exp() table uint16_t j = i + WAVE_SIZE/2; if (j>=WAVE_SIZE) j = j - WAVE_SIZE; - float res = 0.685f * (exp_2048[i]-exp_2048[j]); + float res = 0.685f * (exp_tbl[i]-exp_tbl[j]); return res; } @@ -28,17 +30,18 @@ void buildTables() { for (uint8_t i = 0 ; i<128; ++i) { midi_pitches[i] = noteToFreq(i); midi_phase_steps[i] = noteToFreq(i) * PI * 2.0f / (float)SAMPLE_RATE; - midi_2048_steps[i] = noteToFreq(i) * (float)WAVE_SIZE / (float)SAMPLE_RATE; + midi_tbl_steps[i] = noteToFreq(i) * (float)WAVE_SIZE / (float)SAMPLE_RATE; } + + // one extra value is tor linear 'extrapolation' for (uint16_t i = 0; i < WAVE_SIZE; i++) { - exp_2048[i] = expSaw2048(i); - } - - for (uint16_t i = 0 ; i < WAVE_SIZE; i++) { - square_2048[i] = expSquare2048(i); + exp_tbl[i] = expSaw_fill(i); } - - for (uint16_t i = 0 ; i < WAVE_SIZE; i++) { - tanh_2048[i] = tanh2048(i); + + for (uint16_t i = 0; i < WAVE_SIZE; i++) { + exp_square_tbl[i] = expSquare_fill(i); + tanh_tbl[i] = tanh_fill(i); + // saw_tbl[i] = 1.0f - 2.0f * (float)i / (float)WAVE_SIZE; + // square_tbl[i] = (i>WAVE_SIZE/2) ? 1.0f : -1.0f; } }