From 07d5e7e9dc973827e4bcc6508a941db87c955d17 Mon Sep 17 00:00:00 2001 From: pio Date: Fri, 29 Mar 2024 22:23:37 +0100 Subject: [PATCH] major update and fixes --- keywords.txt | 62 ++- src/basic_DSPutils.cpp | 42 ++ src/basic_DSPutils.h | 42 ++ src/basic_components.h | 1 + src/basic_delay.h | 18 +- src/basic_shelvFilter.h | 5 + src/basic_tempBuffer.h | 37 ++ src/control_SGTL5000_F32.cpp | 16 + src/control_SGTL5000_F32.h | 40 ++ src/control_WM8731_F32.cpp | 333 +++++++++++++ src/control_WM8731_F32.h | 69 +++ src/effect_compressorStereo_F32.h | 447 ++++++++++++++++++ ...ystereo.cpp => effect_delaystereo_F32.cpp} | 208 ++++++-- ...delaystereo.h => effect_delaystereo_F32.h} | 78 ++- src/effect_gainStereo_F32.h | 79 +++- src/effect_guitarBooster_F32.cpp | 339 +++++++++++++ src/effect_guitarBooster_F32.h | 175 +++++++ src/effect_noiseGateStereo_F32.h | 139 ++++-- src/effect_platereverb_F32.cpp | 63 +-- src/effect_platereverb_F32.h | 161 +++++-- src/effect_reverbsc_F32.cpp | 152 ++++-- src/effect_reverbsc_F32.h | 73 ++- src/effect_springreverb_F32.cpp | 60 ++- src/effect_springreverb_F32.h | 35 +- src/effect_xfaderStereo_F32.h | 114 +++++ src/filter_3bandeq.h | 2 +- src/filter_biquadStereo_F32.cpp | 59 +++ src/filter_biquadStereo_F32.h | 258 ++++++++++ src/filter_equalizer_F32.h | 4 +- src/filter_ir_cabsim_F32.cpp | 40 +- src/filter_ir_cabsim_F32.h | 14 + src/hexefx_audio_F32.h | 11 +- src/input_i2s2_F32.cpp | 212 ++++----- src/input_i2s2_F32.h | 14 +- src/output_i2s2_F32.cpp | 171 ++----- src/output_i2s2_F32.h | 18 +- src/switch_selectorStereo_F32.h | 93 ++++ src/wavetables.c | 1 - 38 files changed, 3130 insertions(+), 555 deletions(-) create mode 100644 src/basic_DSPutils.cpp create mode 100644 src/basic_tempBuffer.h create mode 100644 src/control_SGTL5000_F32.cpp create mode 100644 src/control_SGTL5000_F32.h create mode 100644 src/control_WM8731_F32.cpp create mode 100644 src/control_WM8731_F32.h create mode 100644 src/effect_compressorStereo_F32.h rename src/{effect_delaystereo.cpp => effect_delaystereo_F32.cpp} (54%) rename src/{effect_delaystereo.h => effect_delaystereo_F32.h} (79%) create mode 100644 src/effect_guitarBooster_F32.cpp create mode 100644 src/effect_guitarBooster_F32.h create mode 100644 src/effect_xfaderStereo_F32.h create mode 100644 src/filter_biquadStereo_F32.cpp create mode 100644 src/filter_biquadStereo_F32.h create mode 100644 src/switch_selectorStereo_F32.h diff --git a/keywords.txt b/keywords.txt index 9c4ace6..6651818 100644 --- a/keywords.txt +++ b/keywords.txt @@ -27,6 +27,8 @@ setMix KEYWORD2 AudioFilterShelvingLPHP KEYWORD1 AudioFilterLP KEYWORD1 +AudioBasicTempBuffer_F32 KEYWORD1 + AudioEffectInfinitePhaser_F32 KEYWORD1 depth KEYWORD2 depth_top KEYWORD2 @@ -43,7 +45,6 @@ AudioEffectMonoToStereo_F32 KEYWORD1 stereo_set KEYWORD2 pan_set KEYWORD2 - AudioEffectPhaserStereo_F32 KEYWORD1 top KEYWORD2 btm KEYWORD2 @@ -99,7 +100,7 @@ bass_cut KEYWORD2 AudioEffectDelayStereo_F32 KEYWORD1 delay KEYWORD2 feedback KEYWORD2 -inertia +inertia KEYWORD2 mod_rateHz KEYWORD2 mod_rate KEYWORD2 mod_depth KEYWORD2 @@ -108,9 +109,66 @@ tap_tempo KEYWORD2 AudioEffectReverbSc_F32 KEYWORD1 lowpass KEYWORD2 +AudioEffectCompressorStereo_F32 KEYWORD1 +setDefaultValues KEYWORD2 +calcAudioLevel_dB KEYWORD2 +calcGain KEYWORD2 +calcInstantaneousTargetGain KEYWORD2 +calcSmoothedGain_dB KEYWORD2 +resetStates KEYWORD2 +setPreGain KEYWORD2 +setPreGain_dB KEYWORD2 +setPostGain KEYWORD2 +setPostGain_dB KEYWORD2 +setCompressionRatio KEYWORD2 +setAttack_sec KEYWORD2 +setRelease_sec KEYWORD2 +setLevelTimeConst_sec KEYWORD2 +setThresh_dBFS KEYWORD2 +enableHPFilter KEYWORD2 +getPreGain_dB KEYWORD2 +getAttack_sec KEYWORD2 +getRelease_sec KEYWORD2 +getLevelTimeConst_sec KEYWORD2 +getThresh_dBFS KEYWORD2 +getCompressionRatio KEYWORD2 +getCurrentLevel_dBFS KEYWORD2 +getCurrentGain_dB KEYWORD2 +setHPFilterCoeff_N2IIR_Matlab +bypass_get KEYWORD2 +bypass_set KEYWORD2 +bypass_tgl KEYWORD2 +setSideChainMode KEYWORD2 + +AudioEffectGainStereo_F32 KEYWORD1 +setGain KEYWORD2 +setGain_dB KEYWORD2 +getGain KEYWORD2 +getGain_dB KEYWORD2 +setPan KEYWORD2 +getPan KEYWORD2 + +AudioEffectGuitarBooster_F32 KEYWORD1 +drive KEYWORD2 +bottom KEYWORD2 +tone KEYWORD2 +bias KEYWORD2 +mix KEYWORD2 +volume KEYWORD2 +octave_get KEYWORD2 +octave_set KEYWORD2 +octave_tgl KEYWORD2 + memcpyInterleave_f32 KEYWORD2 memcpyDeinterleave_f32 KEYWORD2 AudioInputI2S2_F32 KEYWORD1 AudioOutputI2S2_F32 KEYWORD1 + +AudioControlSGTL5000_Ext KEYWORD1 + +AudioControlWM8731_Ext KEYWORD1 +dac_mute KEYWORD2 +HPfilter KEYWORD2 + diff --git a/src/basic_DSPutils.cpp b/src/basic_DSPutils.cpp new file mode 100644 index 0000000..7317174 --- /dev/null +++ b/src/basic_DSPutils.cpp @@ -0,0 +1,42 @@ +#include "basic_DSPutils.h" + + +/** + * @brief scale a float vector (range -1.0 - 1.0) to a new float vector + * in range of int32_t + saturation. + * based on arm_float_to_31 + * + * @param pSrc pointer to the source vector + * @param pDst pointer to the destination verctor + * @param blockSize + */ +void scale_float_to_int32range(const float32_t *pSrc, float32_t *pDst, uint32_t blockSize) +{ + uint32_t blkCnt; /* Loop counter */ + const float32_t *pIn = pSrc; /* Source pointer */ + /* Loop unrolling: Compute 4 outputs at a time */ + blkCnt = blockSize >> 2U; + + while (blkCnt > 0U) + { + /* C = A * 2147483648 */ + /* Convert from float to Q31 and then store the results in the destination buffer */ + *pDst++ = (float32_t)clip_q63_to_q31((q63_t)(*pIn++ * 2147483648.0f)); + *pDst++ = (float32_t)clip_q63_to_q31((q63_t)(*pIn++ * 2147483648.0f)); + *pDst++ = (float32_t)clip_q63_to_q31((q63_t)(*pIn++ * 2147483648.0f)); + *pDst++ = (float32_t)clip_q63_to_q31((q63_t)(*pIn++ * 2147483648.0f)); + blkCnt--; + } + + /* Loop unrolling: Compute remaining outputs */ + blkCnt = blockSize % 0x4U; + + while (blkCnt > 0U) + { + /* C = A * 2147483648 */ + *pDst++ = (float32_t)clip_q63_to_q31((q63_t)(*pIn++ * 2147483648.0f)); + /* Decrement loop counter */ + blkCnt--; + } +} + diff --git a/src/basic_DSPutils.h b/src/basic_DSPutils.h index 06b6553..2c3750c 100644 --- a/src/basic_DSPutils.h +++ b/src/basic_DSPutils.h @@ -1,14 +1,19 @@ #ifndef _BASIC_DSPUTILS_H_ #define _BASIC_DSPUTILS_H_ +#include #include +#define F32_TO_I32_NORM_FACTOR (2147483647) // which is 2^31-1 +#define I32_TO_F32_NORM_FACTOR (4.656612875245797e-10) //which is 1/(2^31 - 1) + static inline void mix_pwr(float32_t mix, float32_t *wetMix, float32_t *dryMix); static inline void mix_pwr(float32_t mix, float32_t *wetMix, float32_t *dryMix) { // Calculate mix parameters // A cheap mostly energy constant crossfade from SignalSmith Blog // https://signalsmith-audio.co.uk/writing/2021/cheap-energy-crossfade/ + mix = constrain(mix, 0.0f, 1.0f); float32_t x2 = 1.0f - mix; float32_t A = mix*x2; float32_t B = A * (1.0f + 1.4186f * A); @@ -19,4 +24,41 @@ static inline void mix_pwr(float32_t mix, float32_t *wetMix, float32_t *dryMix) *dryMix = D * D; } +void scale_float_to_int32range(const float32_t *pSrc, float32_t *pDst, uint32_t blockSize); + +/** + * @brief combine two separate buffers into interleaved one + * @param sz - samples per output buffer (divisible by 2) + * @param dst - pointer to source buffer + * @param srcA - pointer to A source buffer (even samples) + * @param srcB - pointer to B source buffer (odd samples) + * @retval none + */ +inline void memcpyInterleave_f32(float32_t *srcA, float32_t *srcB, float32_t *dst, int16_t sz) +{ + while(sz) + { + *dst++ = *srcA++; + *dst++ = *srcB++; + sz--; + *dst++ = *srcA++; + *dst++ = *srcB++; + sz--; + } +} +inline void memcpyInterleave_f32(float32_t *srcA, float32_t *srcB, float32_t *dst, int16_t sz); + +inline void memcpyDeinterleave_f32(float32_t *src, float32_t *dstA, float32_t *dstB, int16_t sz) +{ + while(sz) + { + *dstA++ = *src++; + *dstB++ = *src++; + sz--; + *dstA++ = *src++; + *dstB++ = *src++; + sz--; + } +} +inline void memcpyDeinterleave_f32(float32_t *src, float32_t *dstA, float32_t *dstB, int16_t sz); #endif // _BASIC_DSPUTILS_H_ diff --git a/src/basic_components.h b/src/basic_components.h index cc11721..5f2ddb7 100644 --- a/src/basic_components.h +++ b/src/basic_components.h @@ -7,5 +7,6 @@ #include "basic_shelvFilter.h" #include "basic_pitch.h" #include "basic_DSPutils.h" +#include "basic_tempBuffer.h" #endif // _BASIC_COMPONENTS_H_ diff --git a/src/basic_delay.h b/src/basic_delay.h index 5d264d2..7c549d6 100644 --- a/src/basic_delay.h +++ b/src/basic_delay.h @@ -30,9 +30,10 @@ public: bool init(uint32_t size_samples, bool psram=false) { if(bf) free(bf); + use_psram = psram; size = size_samples; - if (psram) bf = (float *)extmem_malloc(size * sizeof(float)); // allocate buffer - else bf = (float *)malloc(size * sizeof(float)); // allocate buffer + if (use_psram) bf = (float *)extmem_malloc(size * sizeof(float)); // allocate buffer in PSRAM + else bf = (float *)malloc(size * sizeof(float)); // allocate buffer in DMARAM if (!bf) return false; idx = 0; reset(); @@ -40,7 +41,17 @@ public: } void reset() { - memset(bf, 0, size * sizeof(float)); + memset(bf, 0, size * sizeof(float32_t)); + if (use_psram) arm_dcache_flush_delete(&bf[0], size * sizeof(float32_t)); + } + void reset(uint32_t startAddr, uint32_t endAddr) + { + if (startAddr > endAddr) return; + if (endAddr > (uint32_t)size) endAddr = size; + float32_t* memPtr = &bf[0]+startAddr; + uint32_t l = (endAddr - startAddr) * sizeof(float32_t); + memset(memPtr, 0, l); + if (use_psram) arm_dcache_flush_delete(memPtr, l); } /** * @brief get the tap from the delay buffer @@ -106,6 +117,7 @@ private: int32_t size; float *bf; int32_t idx; + bool use_psram = false; }; #endif // _BASIC_DELAY_H_ diff --git a/src/basic_shelvFilter.h b/src/basic_shelvFilter.h index eb37fee..5b75e89 100644 --- a/src/basic_shelvFilter.h +++ b/src/basic_shelvFilter.h @@ -59,6 +59,11 @@ public: hpreg += tmp1 * hp_f; return (lpreg + hidamp*tmp2 + lodamp * hpreg); } + void reset() + { + lpreg = 0.0f; + hpreg = 0.0f; + } private: float lpreg; float hpreg; diff --git a/src/basic_tempBuffer.h b/src/basic_tempBuffer.h new file mode 100644 index 0000000..03ab7f9 --- /dev/null +++ b/src/basic_tempBuffer.h @@ -0,0 +1,37 @@ +#ifndef _BASIC_TEMPBUFFER_H_ +#define _BASIC_TEMPBUFFER_H_ + +#include +#include "AudioStream_F32.h" + +class AudioBasicTempBuffer_F32 : public AudioStream_F32 +{ +public: + AudioBasicTempBuffer_F32() : AudioStream_F32(1, inputQueueArray_f32) + { + blockSize = AUDIO_BLOCK_SAMPLES; + memset(&data[0], 0, AUDIO_BLOCK_SAMPLES * sizeof(float32_t)); + dataPtr = &data[0]; + }; + AudioBasicTempBuffer_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1, inputQueueArray_f32) + { + blockSize = settings.audio_block_samples; + memset(&data[0], 0, AUDIO_BLOCK_SAMPLES * sizeof(float32_t)); + dataPtr = &data[0]; + }; + ~AudioBasicTempBuffer_F32(){}; + void update(void) + { + audio_block_f32_t* block = AudioStream_F32::receiveReadOnly_f32(); + if (!block) return; + memcpy(&data[0], block->data, blockSize * sizeof(float32_t)); + AudioStream_F32::release(block); + } + float32_t* dataPtr; +private: + audio_block_f32_t *inputQueueArray_f32[1]; + uint16_t blockSize = AUDIO_BLOCK_SAMPLES; + float32_t data[AUDIO_BLOCK_SAMPLES]; +}; + +#endif // _BASIC_TEMPBUFFER_H_ diff --git a/src/control_SGTL5000_F32.cpp b/src/control_SGTL5000_F32.cpp new file mode 100644 index 0000000..9ebd0eb --- /dev/null +++ b/src/control_SGTL5000_F32.cpp @@ -0,0 +1,16 @@ +#include "control_SGTL5000_F32.h" +#include + +#define CHIP_I2S_CTRL 0x0006 +#define CHIP_ADCDAC_CTRL 0x000E + +void AudioControlSGTL5000_F32::set_bitDepth(bit_depth_t bits) +{ + uint16_t regTmp = read(CHIP_I2S_CTRL); + regTmp &= ~(0x30); // clear bit 5:4 (DLEN) + regTmp |= ((uint8_t)bits << 4) & 0x30; // update DLEN + + write(CHIP_ADCDAC_CTRL, 0x000C); // mute DAC + write(CHIP_I2S_CTRL, regTmp); // write new config + write(CHIP_ADCDAC_CTRL, 0x0000); // unmute DAC +} diff --git a/src/control_SGTL5000_F32.h b/src/control_SGTL5000_F32.h new file mode 100644 index 0000000..dcbf776 --- /dev/null +++ b/src/control_SGTL5000_F32.h @@ -0,0 +1,40 @@ +#ifndef _CONTROL_SGTL5000_F32_H_ +#define _CONTROL_SGTL5000_F32_H_ +/** + * @file control_SGTL5000_ext.h + * @author Piotr Zapart + * @brief enables the bit depth setting for the SGTL5000 codec chip + * @version 0.1 + * @date 2024-03-20 + * + * @copyright Copyright (c) 2024 www.hexefx.com + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see ." + */ + +#include +#include + +class AudioControlSGTL5000_F32 : public AudioControlSGTL5000 +{ + //GUI: inputs:0, outputs:0 //this line used for automatic generation of GUI node + public: + AudioControlSGTL5000_F32(void) {}; + typedef enum + { + I2S_BITS_32 = 0, + I2S_BITS_24, + I2S_BITS_20, + I2S_BITS_16 + }bit_depth_t; + + void set_bitDepth(bit_depth_t bits); +}; + +#endif // _CONTROL_SGTL5000_EXT_H_ diff --git a/src/control_WM8731_F32.cpp b/src/control_WM8731_F32.cpp new file mode 100644 index 0000000..41978ef --- /dev/null +++ b/src/control_WM8731_F32.cpp @@ -0,0 +1,333 @@ +#include "control_WM8731_F32.h" +#include + +#define WM8731_I2C_ADDR_A 0x1A +#define WM8731_I2C_ADDR_B 0x1B + +#define WM8731_MUTE_ON (1) +#define WM8731_MUTE_OFF (0) + +#define WM8731_REG_LLINEIN (0) + #define WM8731_BITS_LINVOL_SHIFT (0) + #define WM8731_BITS_LINVOL_MASK (0x1F) + #define WM8731_BITS_LINVOL(x) (((x)<> 8) & 1)); + Wire.write(val & 0xFF); + int status = Wire.endTransmission(); + if (status == 0) return true; + if (attempt >= 12) return false; + delayMicroseconds(80); + } +} + + +bool AudioControlWM8731_F32::volumeInteger(unsigned int n) +{ + // n = 127 for max volume (+6 dB) + // n = 48 for min volume (-73 dB) + // n = 0 to 47 for mute + if (n > 127) + n = 127; + // Serial.print("volumeInteger, n = "); + // Serial.println(n); + write(WM8731_REG_LHEADOUT, n | 0x180); + write(WM8731_REG_RHEADOUT, n | 0x80); + return true; +} + +bool AudioControlWM8731_F32::inputLevel(float n) +{ + // range is 0x00 (min) - 0x1F (max) + + int _level = int(n * 31.f); + + _level = _level > 0x1F ? 0x1F : _level; + write(WM8731_REG_LLINEIN, _level); + write(WM8731_REG_RLINEIN, _level); + return true; +} + +bool AudioControlWM8731_F32::inputSelect(input_select_t n) +{ + if (n == INPUT_SELECT_LINEIN) write(WM8731_REG_ANALOG, 0x12); + else if (n == INPUT_SELECT_MIC) write(WM8731_REG_ANALOG, 0x15); + else return false; + return true; +} + +/******************************************************************/ + +bool AudioControlWM8731_F32_master::enable(bit_depth_t bits, uint8_t addr) +{ + i2c_addr = addr; + Wire.begin(); + delay(5); + // write(WM8731_REG_RESET, 0); + write(WM8731_REG_INTERFACE, + WM8731_BITS_FORMAT(WM8731_FORMAT_I2S_MSB_LEFT) | + WM8731_BITS_IWL(bits)| + WM8731_BITS_MS(1)); // I2S, x bit, MCLK slave + write(WM8731_REG_SAMPLING, 0x20); // 256*Fs, 44.1 kHz, MCLK/1 + + // In order to prevent pops, the DAC should first be soft-muted (DACMU), + // the output should then be de-selected from the line and headphone output + // (DACSEL), then the DAC powered down (DACPD). + + write(WM8731_REG_DIGITAL, 0x08); // DAC soft mute + write(WM8731_REG_ANALOG, 0x00); // disable all + + write(WM8731_REG_POWERDOWN, 0x00); // codec powerdown + + write(WM8731_REG_LHEADOUT, 0x80); // volume off + write(WM8731_REG_RHEADOUT, 0x80); + + delay(100); // how long to power up? + + write(WM8731_REG_ACTIVE, 1); + delay(5); + write(WM8731_REG_DIGITAL, 0x00); // DAC unmuted + write(WM8731_REG_ANALOG, 0x10); // DAC selected + + return true; +} diff --git a/src/control_WM8731_F32.h b/src/control_WM8731_F32.h new file mode 100644 index 0000000..0f367be --- /dev/null +++ b/src/control_WM8731_F32.h @@ -0,0 +1,69 @@ +/** + * @file control_WM8731_ext.h + * @author Piotr Zapart + * @brief Alternative WM8731 driver with configurable bit depth + * @version 1.0 + * @date 2024-03-21 + * + * @copyright Copyright (c) 2024 www.hexefx.com + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see ." + */ +#ifndef _CONTROL_WM8731_F32_H_ +#define _CONTROL_WM8731_F32_H_ + +#include + +#define WM8731_I2C_ADDR_CSB0 0x1A +#define WM8731_I2C_ADDR_CSB1 0x1B + +class AudioControlWM8731_F32 +{ +public: + AudioControlWM8731_F32(){}; + + typedef enum + { + I2S_BITS_16 = 0, + I2S_BITS_20, + I2S_BITS_24, + I2S_BITS_32 + }bit_depth_t; + + typedef enum + { + INPUT_SELECT_LINEIN = 0, + INPUT_SELECT_MIC + }input_select_t; + + bool enable(bit_depth_t bits = I2S_BITS_16, uint8_t addr=WM8731_I2C_ADDR_CSB0); + bool disable(void) { return false; } + bool volume(float n) { return volumeInteger(n * 80.0f + 47.499f); } + bool inputLevel(float n); // range: 0.0f to 1.0f + bool inputSelect(input_select_t n=INPUT_SELECT_LINEIN); + void dac_mute(bool m); + void HPfilter(bool state); + +protected: + bool write(unsigned int reg, unsigned int val); + bool volumeInteger(unsigned int n); // range: 0x2F to 0x7F +private: + uint8_t bit_depth = I2S_BITS_16; + uint8_t i2c_addr; + bool DACmute = false; +}; + +class AudioControlWM8731_F32_master : public AudioControlWM8731_F32 +{ +public: + bool enable(bit_depth_t bits = I2S_BITS_16, uint8_t addr=WM8731_I2C_ADDR_CSB0); +private: + uint8_t i2c_addr; +}; +#endif // _CONTROL_WM8731_EXTENDED_H_ diff --git a/src/effect_compressorStereo_F32.h b/src/effect_compressorStereo_F32.h new file mode 100644 index 0000000..77e57b3 --- /dev/null +++ b/src/effect_compressorStereo_F32.h @@ -0,0 +1,447 @@ +/* + AudioEffectCompressor + + Created: Chip Audette, Dec 2016 - Jan 2017 + Purpose; Apply dynamic range compression to the audio stream. + Assumes floating-point data. + + This processes a single stream fo audio data (ie, it is mono) + + MIT License. use at your own risk. + + Stereo version - Piotr Zapart www.hexefx.com 03.2024 + +*/ + +#ifndef _EFFECT_COMPRESSORSTEREO_F32 +#define _EFFECT_COMPRESSORSTEREO_F32 + +#include //ARM DSP extensions. https://www.keil.com/pack/doc/CMSIS/DSP/html/index.html +#include + +class AudioEffectCompressorStereo_F32 : public AudioStream_F32 +{ + // GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node +public: + // constructor + AudioEffectCompressorStereo_F32(void) : AudioStream_F32(2, inputQueueArray_f32) + { + setDefaultValues(AUDIO_SAMPLE_RATE); + resetStates(); + }; + + AudioEffectCompressorStereo_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray_f32) + { + setDefaultValues(settings.sample_rate_Hz); + resetStates(); + }; + + typedef enum + { + COMP_SIDECHAIN_SRC_LR, // l + r separate + COMP_SIDECHAIN_SRC_LRSUM // l + r sum / 2 + }sideChainMode_t; + + void setDefaultValues(const float sample_rate_Hz) + { + fs_Hz = sample_rate_Hz; + setThresh_dBFS(-20.0f); // set the default value for the threshold for compression + setCompressionRatio(5.0f); // set the default copression ratio + setAttack_sec(0.005f); // default to this value + setRelease_sec(0.200f); // default to this value + setHPFilterCoeff(); + enableHPFilter(true); // enable the HP filter to remove any DC offset from the audio + sidechainMode = COMP_SIDECHAIN_SRC_LRSUM; + } + + // here's the method that does all the work + void update(void) + { + audio_block_f32_t *blockL, *blockR; + if (bp) // handle bypass + { + blockL = AudioStream_F32::receiveReadOnly_f32(0); + blockR = AudioStream_F32::receiveReadOnly_f32(1); + if (!blockL || !blockR) + { + if (blockL) AudioStream_F32::release(blockL); + if (blockR) AudioStream_F32::release(blockR); + return; + } + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + return; + } + blockL = AudioStream_F32::receiveWritable_f32(0); + blockR = AudioStream_F32::receiveWritable_f32(1); + if (!blockL || !blockR) + { + if (blockL) AudioStream_F32::release(blockL); + if (blockR) AudioStream_F32::release(blockR); + return; + } + // allocate blocks required for gain calculations + audio_block_f32_t* audio_level_dB_blockL = AudioStream_F32::allocate_f32(); + audio_block_f32_t* audio_level_dB_blockR = AudioStream_F32::allocate_f32(); + audio_block_f32_t *gain_blockL = AudioStream_F32::allocate_f32(); + audio_block_f32_t *gain_blockR = AudioStream_F32::allocate_f32(); + // no memory for the audio gain blocks + if ( !audio_level_dB_blockL || !audio_level_dB_blockR || !gain_blockL || !gain_blockL) + { + if (audio_level_dB_blockL) AudioStream_F32::release(audio_level_dB_blockL); + if (audio_level_dB_blockR) AudioStream_F32::release(audio_level_dB_blockR); + if (gain_blockL) AudioStream_F32::release(gain_blockL); + if (gain_blockR) AudioStream_F32::release(gain_blockR); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + return; + } + // apply a high-pass filter to get rid of the DC offset + if (use_HP_prefilter) + { + arm_biquad_cascade_df1_f32(&hp_filt_structL, blockL->data, blockL->data, blockL->length); + arm_biquad_cascade_df1_f32(&hp_filt_structR, blockR->data, blockR->data, blockR->length); + } + // apply the pre-gain...a negative gain value will disable + if (pre_gain > 0.0f) + { + arm_scale_f32(blockL->data, pre_gain, blockL->data, blockL->length); // use ARM DSP for speed! + arm_scale_f32(blockR->data, pre_gain, blockR->data, blockR->length); + } + // Side chain processing + switch (sidechainMode) + { + case COMP_SIDECHAIN_SRC_LR: // l + r separate + calcAudioLevel_dB(blockL, audio_level_dB_blockL); + calcAudioLevel_dB(blockR, audio_level_dB_blockR); + calcGain(audio_level_dB_blockL, gain_blockL); + calcGain(audio_level_dB_blockR, gain_blockR); + arm_mult_f32(blockL->data, gain_blockL->data, blockL->data, blockL->length); + arm_mult_f32(blockR->data, gain_blockR->data, blockR->data, blockR->length); + break; + case COMP_SIDECHAIN_SRC_LRSUM: // l + r sum / 2 + arm_add_f32(blockL->data, blockR->data, audio_level_dB_blockL->data, audio_level_dB_blockL->length); // L+R -> db_L + arm_scale_f32(audio_level_dB_blockL->data, 0.5f, audio_level_dB_blockL->data, audio_level_dB_blockL->length); // L+R / 2 + calcAudioLevel_dB(audio_level_dB_blockL, audio_level_dB_blockL); // chn L used for L&R + calcGain(audio_level_dB_blockL, gain_blockL); + arm_mult_f32(blockL->data, gain_blockL->data, blockL->data, blockL->length); + arm_mult_f32(blockR->data, gain_blockL->data, blockR->data, blockR->length); + break; + default: + break; + } + if (post_gain > 0.0f) + { + arm_scale_f32(blockL->data, post_gain, blockL->data, blockL->length); // use ARM DSP for speed! + arm_scale_f32(blockR->data, post_gain, blockR->data, blockR->length); + } + // transmit the block and release memory + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + AudioStream_F32::release(gain_blockL); + AudioStream_F32::release(gain_blockR); + AudioStream_F32::release(audio_level_dB_blockL); + AudioStream_F32::release(audio_level_dB_blockR); + } + + // Here's the method that estimates the level of the audio (in dB) + // It squares the signal and low-pass filters to get a time-averaged + // signal power. It then + void calcAudioLevel_dB(audio_block_f32_t *wav_block, audio_block_f32_t *level_dB_block) + { + // calculate the instantaneous signal power (square the signal) + audio_block_f32_t *wav_pow_block = AudioStream_F32::allocate_f32(); + arm_mult_f32(wav_block->data, wav_block->data, wav_pow_block->data, wav_block->length); + + // low-pass filter and convert to dB + float c1 = level_lp_const, c2 = 1.0f - c1; // prepare constants + for (int i = 0; i < wav_pow_block->length; i++) + { + // first-order low-pass filter to get a running estimate of the average power + wav_pow_block->data[i] = c1 * prev_level_lp_pow + c2 * wav_pow_block->data[i]; + + // save the state of the first-order low-pass filter + prev_level_lp_pow = wav_pow_block->data[i]; + + // now convert the signal power to dB (but not yet multiplied by 10.0) + level_dB_block->data[i] = log10f_approx(wav_pow_block->data[i]); + } + + // limit the amount that the state of the smoothing filter can go toward negative infinity + if (prev_level_lp_pow < (1.0E-13)) + prev_level_lp_pow = 1.0E-13; // never go less than -130 dBFS + + // scale the wav_pow_block by 10.0 to complete the conversion to dB + arm_scale_f32(level_dB_block->data, 10.0f, level_dB_block->data, level_dB_block->length); // use ARM DSP for speed! + + // release memory and return + AudioStream_F32::release(wav_pow_block); + return; // output is passed through level_dB_block + } + + // This method computes the desired gain from the compressor, given an estimate + // of the signal level (in dB) + void calcGain(audio_block_f32_t *audio_level_dB_block, audio_block_f32_t *gain_block) + { + // first, calculate the instantaneous target gain based on the compression ratio + audio_block_f32_t *inst_targ_gain_dB_block = AudioStream_F32::allocate_f32(); + calcInstantaneousTargetGain(audio_level_dB_block, inst_targ_gain_dB_block); + + // second, smooth in time (attack and release) by stepping through each sample + audio_block_f32_t *gain_dB_block = AudioStream_F32::allocate_f32(); + calcSmoothedGain_dB(inst_targ_gain_dB_block, gain_dB_block); + + // finally, convert from dB to linear gain: gain = 10^(gain_dB/20); (ie this takes care of the sqrt, too!) + arm_scale_f32(gain_dB_block->data, 1.0f / 20.0f, gain_dB_block->data, gain_dB_block->length); // divide by 20 + for (int i = 0; i < gain_dB_block->length; i++) + gain_block->data[i] = pow10f(gain_dB_block->data[i]); // do the 10^(x) + + // release memory and return + AudioStream_F32::release(gain_dB_block); + AudioStream_F32::release(inst_targ_gain_dB_block); + return; // output is passed through gain_block + } + + // Compute the instantaneous desired gain, including the compression ratio and + // threshold for where the comrpession kicks in + void calcInstantaneousTargetGain(audio_block_f32_t *audio_level_dB_block, audio_block_f32_t *inst_targ_gain_dB_block) + { + // how much are we above the compression threshold? + audio_block_f32_t *above_thresh_dB_block = AudioStream_F32::allocate_f32(); + arm_offset_f32(audio_level_dB_block->data, // CMSIS DSP for "add a constant value to all elements" + -thresh_dBFS, // this is the value to be added + above_thresh_dB_block->data, // this is the output + audio_level_dB_block->length); + + // scale by the compression ratio...this is what the output level should be (this is our target level) + arm_scale_f32(above_thresh_dB_block->data, // CMSIS DSP for "multiply all elements by a constant value" + 1.0f / comp_ratio, // this is the value to be multiplied + inst_targ_gain_dB_block->data, // this is the output + above_thresh_dB_block->length); + + // compute the instantaneous gain...which is the difference between the target level and the original level + arm_sub_f32(inst_targ_gain_dB_block->data, // CMSIS DSP for "subtract two vectors element-by-element" + above_thresh_dB_block->data, // this is the vector to be subtracted + inst_targ_gain_dB_block->data, // this is the output + inst_targ_gain_dB_block->length); + + // limit the target gain to attenuation only (this part of the compressor should not make things louder!) + for (int i = 0; i < inst_targ_gain_dB_block->length; i++) + { + if (inst_targ_gain_dB_block->data[i] > 0.0f) + inst_targ_gain_dB_block->data[i] = 0.0f; + } + + // release memory before returning + AudioStream_F32::release(above_thresh_dB_block); + return; // output is passed through inst_targ_gain_dB_block + } + + // this method applies the "attack" and "release" constants to smooth the + // target gain level through time. + void calcSmoothedGain_dB(audio_block_f32_t *inst_targ_gain_dB_block, audio_block_f32_t *gain_dB_block) + { + float32_t gain_dB; + float32_t one_minus_attack_const = 1.0f - attack_const; + float32_t one_minus_release_const = 1.0f - release_const; + for (int i = 0; i < inst_targ_gain_dB_block->length; i++) + { + gain_dB = inst_targ_gain_dB_block->data[i]; + + // smooth the gain using the attack or release constants + if (gain_dB < prev_gain_dB) + { // are we in the attack phase? + gain_dB_block->data[i] = attack_const * prev_gain_dB + one_minus_attack_const * gain_dB; + } + else + { // or, we're in the release phase + gain_dB_block->data[i] = release_const * prev_gain_dB + one_minus_release_const * gain_dB; + } + + // save value for the next time through this loop + prev_gain_dB = gain_dB_block->data[i]; + } + + // return + return; // the output here is gain_block + } + + // methods to set parameters of this module + void resetStates(void) + { + prev_level_lp_pow = 1.0f; + prev_gain_dB = 0.0f; + + // initialize the HP filter. (This also resets the filter states,) + arm_biquad_cascade_df1_init_f32(&hp_filt_structL, hp_nstages, hp_coeff, hp_stateL); + arm_biquad_cascade_df1_init_f32(&hp_filt_structR, hp_nstages, hp_coeff, hp_stateR); + } + void setPreGain(float g) { pre_gain = g; } + void setPreGain_dB(float gain_dB) { setPreGain(pow(10.0f, gain_dB / 20.0f)); } + void setPostGain(float g) { post_gain = g; } + void setPostGain_dB(float gain_dB) { setPostGain(pow(10.0f, gain_dB / 20.0f)); } + void setCompressionRatio(float cr) + { + comp_ratio = max(0.001f, cr); // limit to positive values + updateThresholdAndCompRatioConstants(); + } + void setAttack_sec(float a) + { + attack_sec = a; + attack_const = expf(-1.0f / (attack_sec * fs_Hz)); // expf() is much faster than exp() + + // also update the time constant for the envelope extraction + setLevelTimeConst_sec(min(attack_sec, release_sec) / 5.0f); // make the level time-constant one-fifth the gain time constants + } + void setRelease_sec(float r) + { + release_sec = r; + release_const = expf(-1.0f / (release_sec * fs_Hz)); // expf() is much faster than exp() + + // also update the time constant for the envelope extraction + setLevelTimeConst_sec(min(attack_sec, release_sec) / 5.0f); // make the level time-constant one-fifth the gain time constants + } + void setLevelTimeConst_sec(float t_sec) + { + const float min_t_sec = 0.002f; // this is the minimum allowed value + level_lp_sec = max(min_t_sec, t_sec); + level_lp_const = expf(-1.0f / (level_lp_sec * fs_Hz)); // expf() is much faster than exp() + } + void setThresh_dBFS(float val) + { + thresh_dBFS = val; + setThreshPow(pow(10.0f, thresh_dBFS / 10.0f)); + } + void enableHPFilter(boolean flag) { use_HP_prefilter = flag; }; + + // methods to return information about this module + float32_t getPreGain_dB(void) { return 20.0 * log10f_approx(pre_gain); } + float32_t getAttack_sec(void) { return attack_sec; } + float32_t getRelease_sec(void) { return release_sec; } + float32_t getLevelTimeConst_sec(void) { return level_lp_sec; } + float32_t getThresh_dBFS(void) { return thresh_dBFS; } + float32_t getCompressionRatio(void) { return comp_ratio; } + float32_t getCurrentLevel_dBFS(void) { return 10.0 * log10f_approx(prev_level_lp_pow); } + float32_t getCurrentGain_dB(void) { return prev_gain_dB; } + + void setHPFilterCoeff_N2IIR_Matlab(float32_t b[], float32_t a[]) + { + // https://www.keil.com/pack/doc/CMSIS/DSP/html/group__BiquadCascadeDF1.html#ga8e73b69a788e681a61bccc8959d823c5 + // Use matlab to compute the coeff for HP at 20Hz: [b,a]=butter(2,20/(44100/2),'high'); %assumes fs_Hz = 44100 + hp_coeff[0] = b[0]; + hp_coeff[1] = b[1]; + hp_coeff[2] = b[2]; // here are the matlab "b" coefficients + hp_coeff[3] = -a[1]; + hp_coeff[4] = -a[2]; // the DSP needs the "a" terms to have opposite sign vs Matlab + } + bool bypass_get(void) {return bp;} + void bypass_set(bool state) {bp = state;} + bool bypass_tgl(void) + { + bp ^= 1; + return bp; + } + void setSideChainMode(sideChainMode_t newMode) {sidechainMode = newMode;} +private: + // state-related variables + audio_block_f32_t *inputQueueArray_f32[2]; // memory pointer for the input to this module + float32_t prev_level_lp_pow = 1.0f; + float32_t prev_gain_dB = 0.0f; // last gain^2 used + float32_t fs_Hz = AUDIO_SAMPLE_RATE_EXACT; + bool bp = true; // bypass flag + sideChainMode_t sidechainMode = COMP_SIDECHAIN_SRC_LRSUM; + // HP filter state-related variables + arm_biquad_casd_df1_inst_f32 hp_filt_structL; + arm_biquad_casd_df1_inst_f32 hp_filt_structR; + static const uint8_t hp_nstages = 1; + float32_t hp_coeff[5 * hp_nstages] = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f}; // no filtering. actual filter coeff set later + float32_t hp_stateL[4 * hp_nstages]; + float32_t hp_stateR[4 * hp_nstages]; + void setHPFilterCoeff(void) + { + // https://www.keil.com/pack/doc/CMSIS/DSP/html/group__BiquadCascadeDF1.html#ga8e73b69a788e681a61bccc8959d823c5 + // Use matlab to compute the coeff for HP at 20Hz: [b,a]=butter(2,20/(44100/2),'high'); %assumes fs_Hz = 44100 + const float32_t b[] = {9.979871156751189e-01, -1.995974231350238e+00, 9.979871156751189e-01}; // from Matlab + const float32_t a[] = {1.000000000000000e+00, -1.995970179642828e+00, 9.959782830576472e-01}; // from Matlab + setHPFilterCoeff_N2IIR_Matlab((float32_t *)b, (float32_t *)a); + } + + // private parameters related to gain calculation + float32_t attack_const, release_const, level_lp_const; // used in calcGain(). set by setAttack_sec() and setRelease_sec(); + float32_t comp_ratio_const, thresh_pow_FS_wCR; // used in calcGain(); set in updateThresholdAndCompRatioConstants() + void updateThresholdAndCompRatioConstants(void) + { + comp_ratio_const = 1.0f - (1.0f / comp_ratio); + thresh_pow_FS_wCR = powf(thresh_pow_FS, comp_ratio_const); + } + + // settings + float32_t attack_sec = 0.002f, release_sec = 0.2f, level_lp_sec; + float32_t thresh_dBFS = 0.0f; // threshold for compression, relative to digital full scale + float32_t thresh_pow_FS = 1.0f; // same as above, but not in dB + void setThreshPow(float t_pow) + { + thresh_pow_FS = t_pow; + updateThresholdAndCompRatioConstants(); + } + float32_t comp_ratio = 1.0f; // compression ratio + float32_t pre_gain = -1.0f; // gain to apply before the compression. negative value disables + float32_t post_gain = -1.0f; + boolean use_HP_prefilter; + + // Accelerate the powf(10.0,x) function + static float32_t pow10f(float x) + { + // return powf(10.0f,x) //standard, but slower + return expf(2.302585092994f * x); // faster: exp(log(10.0f)*x) + } + + // Accelerate the log10f(x) function? + static float32_t log10f_approx(float x) + { + // return log10f(x); //standard, but slower + return log2f_approx(x) * 0.3010299956639812f; // faster: log2(x)/log2(10) + } + + /* ---------------------------------------------------------------------- + ** Fast approximation to the log2() function. It uses a two step + ** process. First, it decomposes the floating-point number into + ** a fractional component F and an exponent E. The fraction component + ** is used in a polynomial approximation and then the exponent added + ** to the result. A 3rd order polynomial is used and the result + ** when computing db20() is accurate to 7.984884e-003 dB. + ** ------------------------------------------------------------------- */ + // https://community.arm.com/tools/f/discussions/4292/cmsis-dsp-new-functionality-proposal/22621#22621 + // float log2f_approx_coeff[4] = {1.23149591368684f, -4.11852516267426f, 6.02197014179219f, -3.13396450166353f}; + static float log2f_approx(float X) + { + // float *C = &log2f_approx_coeff[0]; + float Y; + float F; + int E; + + // This is the approximation to log2() + F = frexpf(fabsf(X), &E); + // Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E; + // Y = *C++; + Y = 1.23149591368684f; + Y *= F; + // Y += (*C++); + Y += -4.11852516267426f; + Y *= F; + // Y += (*C++); + Y += 6.02197014179219f; + Y *= F; + // Y += (*C++); + Y += -3.13396450166353f; + Y += E; + + return (Y); + } +}; + +#endif diff --git a/src/effect_delaystereo.cpp b/src/effect_delaystereo_F32.cpp similarity index 54% rename from src/effect_delaystereo.cpp rename to src/effect_delaystereo_F32.cpp index 39ce79a..9d4fea6 100644 --- a/src/effect_delaystereo.cpp +++ b/src/effect_delaystereo_F32.cpp @@ -23,17 +23,28 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#include "effect_delaystereo.h" +#include "effect_delaystereo_F32.h" #define TREBLE_LOSS_FREQ (0.20f) #define BASS_LOSS_FREQ (0.05f) #define BASS_FREQ (0.15f) - +extern uint8_t external_psram_size; AudioEffectDelayStereo_F32::AudioEffectDelayStereo_F32(uint32_t dly_range_ms, bool use_psram) : AudioStream_F32(2, inputQueueArray) { - psram_mode = use_psram; + begin(dly_range_ms, use_psram); +} + +void AudioEffectDelayStereo_F32::begin(uint32_t dly_range_ms, bool use_psram) +{ + // failsafe if psram is required but not found + // limit the delay time to 500ms (88200 bytes at 44.1kHz) + if (psram_mode && external_psram_size == 0) + { + psram_mode = false; + if (dly_range_ms > 500) dly_range_ms = 500; + } bool memOk = true; dly_length = ((float32_t)(dly_range_ms+500)/1000.0f) * AUDIO_SAMPLE_RATE_EXACT; if (!dly0a.init(dly_length, use_psram)) memOk = false; @@ -55,11 +66,7 @@ void AudioEffectDelayStereo_F32::update() if (!initialized) return; if (!memsetup_done) { - dly0a.reset(); - dly0b.reset(); - dly1a.reset(); - dly1b.reset(); - memsetup_done = true; + memsetup_done = memCleanup(); return; } @@ -71,41 +78,49 @@ void AudioEffectDelayStereo_F32::update() if (bp) { - if (!cleanup_done) + // mem cleanup not required in TRAILS mode + if (!cleanup_done && bp_mode != BYPASS_MODE_TRAILS) { - dly0a.reset(); - dly0b.reset(); - dly1a.reset(); - dly1b.reset(); - cleanup_done = true; + cleanup_done = memCleanup(); tap_active = false; // reset tap tempo tap_counter = 0; } - if (dry_gain > 0.0f) // if dry/wet mixer is used + if (infinite) freeze(false); + switch(bp_mode) { - blockL = AudioStream_F32::receiveReadOnly_f32(0); - blockR = AudioStream_F32::receiveReadOnly_f32(1); - if (!blockL || !blockR) - { - if (blockL) AudioStream_F32::release(blockL); - if (blockR) AudioStream_F32::release(blockR); + case BYPASS_MODE_PASS: + blockL = AudioStream_F32::receiveReadOnly_f32(0); + blockR = AudioStream_F32::receiveReadOnly_f32(1); + if (!blockL || !blockR) + { + if (blockL) AudioStream_F32::release(blockL); + if (blockR) AudioStream_F32::release(blockR); + return; + } + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); return; - } - AudioStream_F32::transmit(blockL, 0); - AudioStream_F32::transmit(blockR, 1); - AudioStream_F32::release(blockL); - AudioStream_F32::release(blockR); - return; + break; + case BYPASS_MODE_OFF: + blockL = AudioStream_F32::allocate_f32(); + if (!blockL) return; + memset(&blockL->data[0], 0, blockL->length*sizeof(float32_t)); + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockL, 1); + AudioStream_F32::release(blockL); + return; + break; + case BYPASS_MODE_TRAILS: + inputGainSet = 0.0f; + tap_active = false; // reset tap tempo + tap_counter = 0; + break; + default: + break; } - blockL = AudioStream_F32::allocate_f32(); - if (!blockL) return; - memset(&blockL->data[0], 0, blockL->length*sizeof(float32_t)); - AudioStream_F32::transmit(blockL, 0); - AudioStream_F32::transmit(blockL, 1); - AudioStream_F32::release(blockL); - return; } - cleanup_done = false; blockL = AudioStream_F32::receiveWritable_f32(0); blockR = AudioStream_F32::receiveWritable_f32(1); if (!blockL || !blockR) @@ -116,8 +131,11 @@ void AudioEffectDelayStereo_F32::update() AudioStream_F32::release(blockR); return; } + cleanup_done = false; + for (i=0; i < blockL->length; i++) { + inputGain += (inputGainSet - inputGain) * 0.25f; // tap tempo if (tap_active) { @@ -138,7 +156,7 @@ void AudioEffectDelayStereo_F32::update() dly_time -= dly_time_step; if (dly_time < dly_time_set) dly_time = dly_time_set; } - // lowpass the dely time + // lowpass the delay time acc1 = dly_time - dly_time_flt; dly_time_flt += acc1 * 0.1f; dly_time = dly_time_flt; @@ -165,12 +183,10 @@ void AudioEffectDelayStereo_F32::update() acc2 = (float32_t)dly_length - 1.0f - (dly_time + mod_fr[3]); if (acc2 < 0.0f) mod_fr[3] += acc2; - float32_t idx = dly_time + mod_fr[0]; - acc1 = dly0b.getTapHermite(dly_time+mod_fr[0]); outR = acc1 * 0.6f; acc1 = flt0R.process(acc1) * feedb; - acc1 += blockR->data[i] * input_attn; + acc1 += blockR->data[i] * inputGain; acc1 = flt1R.process(acc1); acc2 = dly0a.getTapHermite(dly_time+mod_fr[1]); dly0b.write_toOffset(acc2, 0); @@ -180,7 +196,7 @@ void AudioEffectDelayStereo_F32::update() acc1 = dly1b.getTapHermite(dly_time+mod_fr[2]); outR += acc1 * 0.6f; acc1 = flt0L.process(acc1) * feedb; - acc1 += blockL->data[i] * input_attn; + acc1 += blockL->data[i] * inputGain; acc1 = flt1L.process(acc1); acc2 = dly1a.getTapHermite(dly_time+mod_fr[3]); dly1b.write_toOffset(acc2, 0); @@ -199,4 +215,116 @@ void AudioEffectDelayStereo_F32::update() AudioStream_F32::transmit(blockR, 1); AudioStream_F32::release(blockL); AudioStream_F32::release(blockR); +} +void AudioEffectDelayStereo_F32::freeze(bool state) +{ + if (infinite == state) return; + infinite = state; + if (state) + { + feedb_tmp = feedb; // store the settings + inputGain_tmp = inputGainSet; + bassCut_k_tmp = bassCut_k; + trebleCut_k_tmp = trebleCut_k; + __disable_irq(); + feedb = 1.0f; // infinite echo + inputGainSet = freeze_ingain; + __enable_irq(); + } + else + { + __disable_irq(); + feedb = feedb_tmp; + inputGainSet = inputGain_tmp; + bassCut_k = bassCut_k_tmp; + trebleCut_k = trebleCut_k_tmp; + __enable_irq(); + } +} + +/** + * @brief Partial memory clear + * Clearing all the delay buffers at once, esp. if + * the PSRAM is used takes too long for the audio ISR. + * Hence the buffer clear is done in configurable portions + * spread over a few audio update routines. + * + * @return true Memory clean is complete + * @return false Memory clean still in progress + */ +bool AudioEffectDelayStereo_F32::memCleanup() +{ + static uint8_t dlyIdx = 0; + bool result = false; + if (dlyIdx == 0) // value 0 is used to reset the addr + { + memCleanupStart = 0; + memCleanupEnd = memCleanupStep; + flt0L.reset(); + flt0R.reset(); + flt1L.reset(); + flt1R.reset(); + dlyIdx = 1; + } + if (memCleanupEnd > dly_length) // last segment + { + memCleanupEnd = dly_length; + result = true; + } + switch(dlyIdx) + { + case 1: + dly0a.reset(memCleanupStart, memCleanupEnd); + memCleanupStart = memCleanupEnd; + memCleanupEnd += memCleanupStep; + if (result) // if done, reset the mem addr + { + memCleanupStart = 0; + memCleanupEnd = memCleanupStep; + dlyIdx = 2; + result = false; + } + break; + case 2: + dly0b.reset(memCleanupStart, memCleanupEnd); + memCleanupStart = memCleanupEnd; + memCleanupEnd += memCleanupStep; + if (result) // if done, reset the mem addr + { + memCleanupStart = 0; + memCleanupEnd = memCleanupStep; + dlyIdx = 3; + result = false; + } + break; + case 3: + dly1a.reset(memCleanupStart, memCleanupEnd); + memCleanupStart = memCleanupEnd; + memCleanupEnd += memCleanupStep; + if (result) // if done, reset the mem addr + { + memCleanupStart = 0; + memCleanupEnd = memCleanupStep; + dlyIdx = 4; + result = false; + } + break; + case 4: + dly1b.reset(memCleanupStart, memCleanupEnd); + memCleanupStart = memCleanupEnd; + memCleanupEnd += memCleanupStep; + if (result) // if done, reset the mem addr + { + dlyIdx = 0; + result = true; + } + break; + default: + dlyIdx = 0; // cleanup done, reset the dly line idx + result = false; + break; + } + + + return result; } \ No newline at end of file diff --git a/src/effect_delaystereo.h b/src/effect_delaystereo_F32.h similarity index 79% rename from src/effect_delaystereo.h rename to src/effect_delaystereo_F32.h index b3dc131..e495f37 100644 --- a/src/effect_delaystereo.h +++ b/src/effect_delaystereo_F32.h @@ -40,16 +40,18 @@ public: ~AudioEffectDelayStereo_F32(){}; virtual void update(); /** - * @brief delay time + * @brief set the delay time * - * @param t scaled to 0.0f-1.0f range + * @param t delay time scaled to range 0.0 to 1.0 + * @param force bypass the smoothing, immediate change */ - void time(float t) + void time(float t, bool force = false) { t = constrain(t, 0.0f, 1.0f); t = t * t; t = map(t, 0.0f, 1.0f, (float32_t)(dly_length-dly_time_min), 0.0f); __disable_irq(); + if (force) dly_time = t; dly_time_set = t; __enable_irq(); } @@ -73,13 +75,15 @@ public: */ void feedback(float n) { + if (infinite) return; float32_t fb, attn; n = constrain(n, 0.0f, 1.0f); fb = map(n, 0.0f, 1.0f, 0.0f, feedb_max) * hp_feedb_limit; attn = map(n*n*n, 0.0f, 1.0f, 1.0f, 0.4f); + inputGain_tmp = attn; __disable_irq(); feedb = fb; - input_attn = attn; + inputGainSet = attn; __enable_irq(); } /** @@ -116,7 +120,9 @@ public: */ void treble_cut(float n) { + if (infinite) return; n = 1.0f - constrain(n, 0.0f, 1.0f); + trebleCut_k_tmp = n; __disable_irq(); trebleCut_k = n; __enable_irq(); @@ -141,8 +147,10 @@ public: */ void bass_cut(float n) { + if (infinite) return; n = constrain(n, 0.0f, 1.0f); n = 2.0f * n - (n*n); + bassCut_k_tmp = -n; __disable_irq(); bassCut_k = -n; __enable_irq(); @@ -201,15 +209,45 @@ public: lfo.setDepth(d); __enable_irq(); } - + typedef enum + { + BYPASS_MODE_PASS, // pass the input signal to the output + BYPASS_MODE_OFF, // mute the output + BYPASS_MODE_TRAILS // mutes the input only + }bypass_mode_t; + void bypass_setMode(bypass_mode_t m) + { + if (m <= BYPASS_MODE_TRAILS) bp_mode = m; + } + bypass_mode_t bypass_geMode() {return bp_mode;} bool bypass_get(void) {return bp;} - void bypass_set(bool state) {bp = state;} + void bypass_set(bool state) + { + if (bp == state) return; + bp = state; + if (bp) + { + __disable_irq(); + memCleanupStart = 0; + memCleanupEnd = memCleanupStep; + __enable_irq(); + freeze(false); + } + else + { + __disable_irq(); + inputGainSet = inputGain_tmp; + __enable_irq(); + } + } bool bypass_tgl(void) { - bp ^= 1; + bypass_set(bp ^ 1); return bp; } - + void freeze(bool state); + bool freeze_tgl() {freeze(infinite^1); return infinite;} + bool freeze_get() {return infinite;} uint32_t tap_tempo(bool avg=true) { int32_t delta; @@ -243,7 +281,6 @@ public: } return tempo_ticks; } - private: audio_block_f32_t *inputQueueArray[2]; @@ -264,15 +301,19 @@ private: AudioBasicLfo lfo = AudioBasicLfo(0.0f, lfo_ampl); bool psram_mode; bool memsetup_done = false; - bool bp = false; + bool bp = true; + bypass_mode_t bp_mode = BYPASS_MODE_TRAILS; bool cleanup_done = false; - + bool infinite = false; + bool extInputMode = false; // external input via pointers passed to constructor + static constexpr float32_t feedb_max = 0.96f; float32_t feedb = 0; float32_t hp_feedb_limit = 1.0f; float32_t wet_gain; float32_t dry_gain; - float32_t input_attn = 1.0f; + float32_t inputGainSet = 1.0f; + float32_t inputGain = 1.0f; float32_t trebleCut_k = 1.0f; float32_t bassCut_k = 0.0f; float32_t treble_k = 1.0f; @@ -282,11 +323,24 @@ private: static const uint32_t dly_time_min = 128; bool initialized = false; + // freeze variables + float32_t freeze_ingain = 0.00f; + float32_t inputGain_tmp = 1.0f; + float32_t bassCut_k_tmp = 0.0f; + float32_t trebleCut_k_tmp = 1.0f; + float32_t feedb_tmp = 0; + bool tap_active = false; uint32_t tap_counter = 0; uint32_t tap_counter_last=0, tap_counter_new=0; static const uint32_t tap_counter_max = 3000*AUDIO_SAMPLE_RATE; // 3 sec static const int32_t tap_counter_deltamax = 0.3f*AUDIO_SAMPLE_RATE_EXACT; + + bool memCleanup(void); + void begin(uint32_t dly_range_ms, bool use_psram); + const uint32_t memCleanupStep = 2048; + uint32_t memCleanupStart = 0; + uint32_t memCleanupEnd = memCleanupStep; }; #endif // _EFFECT_DELAYSTEREO_H_ diff --git a/src/effect_gainStereo_F32.h b/src/effect_gainStereo_F32.h index 7f8afa2..fee317c 100644 --- a/src/effect_gainStereo_F32.h +++ b/src/effect_gainStereo_F32.h @@ -1,12 +1,20 @@ -/* - * AudioEffectGain_F32 - * - * Created: Chip Audette, November 2016 - * Purpose; Apply digital gain to the audio data. Assumes floating-point data. - * - * This processes a single stream fo audio data (ie, it is mono) - * - * MIT License. use at your own risk. +/** + * @file effect_gainStereo_F32.h + * @author Piotr Zapart + * @brief Stereo volume + pan control + * @version 0.1 + * @date 2024-03-20 + * + * @copyright Copyright (c) 2024 www.hexefx.com + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see ." */ #ifndef _AudioEffectGainStereo_F32_h @@ -14,14 +22,13 @@ #include //ARM DSP extensions. for speed! #include +#include class AudioEffectGainStereo_F32 : public AudioStream_F32 { public: - // constructor - AudioEffectGainStereo_F32(void) : AudioStream_F32(2, inputQueueArray_f32){}; - AudioEffectGainStereo_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray_f32){}; - + AudioEffectGainStereo_F32(void) : AudioStream_F32(2, inputQueueArray_f32) { setPan(0.0f);}; + AudioEffectGainStereo_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray_f32){setPan(0.0f);}; void update(void) { audio_block_f32_t *blockL, *blockR; @@ -35,29 +42,59 @@ public: AudioStream_F32::release(blockR); return; } - arm_scale_f32(blockL->data, gain, blockL->data, blockL->length); // use ARM DSP for speed! - arm_scale_f32(blockR->data, gain, blockR->data, blockR->length); + gainL += (gainLset - gainL) * 0.25f; + gainR += (gainRset - gainR) * 0.25f; + + arm_scale_f32(blockL->data, gainL, blockL->data, blockL->length); // use ARM DSP for speed! + arm_scale_f32(blockR->data, gainR, blockR->data, blockR->length); AudioStream_F32::transmit(blockL, 0); AudioStream_F32::transmit(blockR, 1); AudioStream_F32::release(blockL); AudioStream_F32::release(blockR); } + void setGain(float g) + { + float32_t gL, gR; + gain = g; + gL = panL * gain; + gR = panR * gain; + __disable_irq(); + gainLset = gL; + gainRset = gR; + __enable_irq(); - // methods to set parameters of this module - void setGain(float g) { gain = g; } + } void setGain_dB(float gain_dB) { - float gain = pow(10.0, gain_dB / 20.0); + float gain = powf(10.0f, gain_dB / 20.0f); setGain(gain); } - - // methods to return information about this module float getGain(void) { return gain; } float getGain_dB(void) { return 20.0 * log10(gain); } + void setPan(float32_t p) + { + float32_t tmp, gL, gR; + pan = constrain(p, -1.0f, 1.0f); + tmp = (pan + 1.0f) * 0.5f; // map to 0..1 + + mix_pwr(tmp, &panR, &panL); + + + gL = panL * gain; + gR = panR * gain; + __disable_irq(); + gainLset = gL; + gainRset = gR; + __enable_irq(); + + } + float32_t getPan() { return pan;} private: audio_block_f32_t *inputQueueArray_f32[2]; // memory pointer for the input to this module - float gain = 1.0f; // default value + float32_t gain = 1.0f; // default value + float32_t gainL, gainR, gainLset, gainRset; + float32_t pan, panL, panR; }; #endif \ No newline at end of file diff --git a/src/effect_guitarBooster_F32.cpp b/src/effect_guitarBooster_F32.cpp new file mode 100644 index 0000000..cd039b2 --- /dev/null +++ b/src/effect_guitarBooster_F32.cpp @@ -0,0 +1,339 @@ +/** + * @file effect_guitarBooster_F32.cpp + * @author Piotr Zapart + * @brief Oversampled Waveshaper based overdrive effect + * Stereo IO and bypass, the processing is mono + * @version 0.1 + * @date 2024-03-20 + * + * @copyright Copyright (c) 2024 + * + */ +#include "effect_guitarBooster_F32.h" + +void AudioEffectGuitarBooster_F32::update() +{ + audio_block_f32_t *blockL, *blockR; + uint16_t i; + float32_t sampleWet, sampleDry; + float32_t *samplePtr; + float32_t _hpPre1_reg;// = hpPre1_reg; + float32_t _hpPre2_reg;// = hpPre2_reg; + float32_t _lp1_reg; + float32_t _lp2_reg; + float32_t _hpPost_reg; + float32_t _hpPre1_k = hpPre1_k; + float32_t _hpPre2_k = hpPre2_k; + float32_t _lp1_k = lp1_k; + float32_t _lp2_k = lp2_k; + float32_t _hpPost_k = hpPost_k; + float32_t _gainSet = gainSet; + float32_t _gain_hp = gain_hp; + float32_t _gain = gain; + float32_t _levelSet = levelSet; + float32_t _level = level; + + const uint32_t blockLenInterpolated = upsample_k * AUDIO_BLOCK_SAMPLES; + + if (bp) // handle bypass + { + blockL = AudioStream_F32::receiveReadOnly_f32(0); + blockR = AudioStream_F32::receiveReadOnly_f32(1); + if (!blockL || !blockR) + { + if (blockL) + AudioStream_F32::release(blockL); + if (blockR) + AudioStream_F32::release(blockR); + return; + } + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + return; + } + blockL = AudioStream_F32::receiveWritable_f32(0); + blockR = AudioStream_F32::receiveWritable_f32(1); + if (!blockL || !blockR) + { + if (blockL) + AudioStream_F32::release(blockL); + if (blockR) + AudioStream_F32::release(blockR); + return; + } + _hpPre1_reg = hpPre1_reg; + _hpPre2_reg = hpPre2_reg; + _lp1_reg = lp1_reg; + _lp2_reg = lp2_reg; + _hpPost_reg = hpPost_reg; + + arm_add_f32(blockL->data, blockR->data, blockL->data, blockL->length); // add two channels + arm_fir_interpolate_f32(&interpolator, blockL->data, blockInterpolated, blockL->length); + samplePtr = blockInterpolated; + for (i = 0; i < blockLenInterpolated; i++) + { + sampleWet = *samplePtr; + sampleDry = sampleWet; + _gain += (_gainSet - _gain) * 0.25f; + // octave up + if (octave) sampleWet = 2.0f * fabsf(sampleWet) - 1.0f; + // input high pass + sampleWet -= (_hpPre1_reg += (sampleWet - _hpPre1_reg) * _hpPre1_k); + sampleWet -= (_hpPre2_reg += (sampleWet - _hpPre2_reg) * _hpPre2_k); + sampleWet *= _gain * _gain_hp; + // waveshaper + sampleWet = arm_linear_interp_f32(&waveshaper, sampleWet + DCbias) * -1.0f; + // lowpass + sampleWet = (_lp1_reg += (sampleWet - _lp1_reg) * _lp1_k); + sampleWet = (_lp2_reg += (sampleWet - _lp2_reg) * _lp2_k); + // output highpass + sampleWet -= (_hpPost_reg += (sampleWet - _hpPost_reg) * _hpPost_k); + _level += (_levelSet - _level) * 0.25f; + *samplePtr++ = (sampleWet * wetGain + sampleDry * dryGain) * level; + } + arm_fir_decimate_f32(&decimator, blockInterpolated, blockL->data, blockLenInterpolated); + + hpPre1_reg = _hpPre1_reg; + hpPre2_reg = _hpPre2_reg; + lp1_reg = _lp1_reg; + lp2_reg = _lp2_reg; + hpPost_reg = _hpPost_reg; + gain = _gain; + level = _level; + + AudioStream_F32::transmit(blockL, 0); // send blockL on both output channels + AudioStream_F32::transmit(blockL, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); +} + +void AudioEffectGuitarBooster_F32::bottom(float32_t b) +{ + b = constrain(b, 0.0f, 1.0f); + gain_hp = 1.0f + b * 2.0f; + float32_t hp = map(b, 0.0f, 1.0f, GBOOST_BOTTOM_MAXF, GBOOST_BOTTOM_MINF); + hp = omega(hp); + __disable_irq(); + hpPre1_k = hp; + hpPre2_k = hpPre1_k; + __enable_irq(); +} + +void AudioEffectGuitarBooster_F32::tone(float32_t t) +{ + t = constrain(t, 0.0f, 1.0f); + t = t * t; + float32_t lp = map(t, 0.0f, 1.0f, GBOOST_TONE_MINF, GBOOST_TONE_MAXF); + + lp = omega(lp); + __disable_irq(); + lp1_k = lp; + __enable_irq(); +} + +float32_t AudioEffectGuitarBooster_F32::driveWaveform[2001]= +{ + -0.34169694, -0.34162512, -0.34155323, -0.34148127, -0.34140924, -0.34133714, -0.34126496, -0.34119272, -0.34112041, -0.34104802, + -0.34097557, -0.34090304, -0.34083044, -0.34075777, -0.34068503, -0.34061222, -0.34053933, -0.34046637, -0.34039334, -0.34032024, + -0.34024707, -0.34017382, -0.34010050, -0.34002711, -0.33995364, -0.33988010, -0.33980649, -0.33973281, -0.33965905, -0.33958521, + -0.33951131, -0.33943733, -0.33936327, -0.33928914, -0.33921494, -0.33914066, -0.33906631, -0.33899188, -0.33891738, -0.33884280, + -0.33876814, -0.33869341, -0.33861861, -0.33854373, -0.33846877, -0.33839374, -0.33831863, -0.33824344, -0.33816818, -0.33809284, + -0.33801743, -0.33794193, -0.33786636, -0.33779072, -0.33771499, -0.33763919, -0.33756331, -0.33748735, -0.33741131, -0.33733520, + -0.33725901, -0.33718273, -0.33710638, -0.33702995, -0.33695345, -0.33687686, -0.33680019, -0.33672344, -0.33664662, -0.33656971, + -0.33649273, -0.33641566, -0.33633851, -0.33626128, -0.33618398, -0.33610659, -0.33602912, -0.33595157, -0.33587393, -0.33579622, + -0.33571842, -0.33564054, -0.33556258, -0.33548454, -0.33540642, -0.33532821, -0.33524992, -0.33517155, -0.33509309, -0.33501455, + -0.33493593, -0.33485722, -0.33477843, -0.33469956, -0.33462060, -0.33454156, -0.33446243, -0.33438322, -0.33430393, -0.33422454, + -0.33414508, -0.33406553, -0.33398589, -0.33390617, -0.33382636, -0.33374646, -0.33366648, -0.33358641, -0.33350626, -0.33342602, + -0.33334569, -0.33326527, -0.33318477, -0.33310418, -0.33302350, -0.33294274, -0.33286188, -0.33278094, -0.33269991, -0.33261879, + -0.33253758, -0.33245628, -0.33237490, -0.33229342, -0.33221185, -0.33213020, -0.33204845, -0.33196662, -0.33188469, -0.33180267, + -0.33172056, -0.33163836, -0.33155607, -0.33147369, -0.33139122, -0.33130865, -0.33122600, -0.33114325, -0.33106041, -0.33097747, + -0.33089444, -0.33081132, -0.33072811, -0.33064480, -0.33056140, -0.33047791, -0.33039432, -0.33031064, -0.33022686, -0.33014299, + -0.33005902, -0.32997496, -0.32989080, -0.32980655, -0.32972220, -0.32963776, -0.32955322, -0.32946858, -0.32938385, -0.32929901, + -0.32921409, -0.32912906, -0.32904394, -0.32895872, -0.32887340, -0.32878799, -0.32870247, -0.32861686, -0.32853115, -0.32844534, + -0.32835943, -0.32827342, -0.32818731, -0.32810110, -0.32801479, -0.32792838, -0.32784187, -0.32775526, -0.32766855, -0.32758174, + -0.32749482, -0.32740781, -0.32732069, -0.32723347, -0.32714614, -0.32705872, -0.32697119, -0.32688356, -0.32679582, -0.32670798, + -0.32662004, -0.32653199, -0.32644384, -0.32635558, -0.32626722, -0.32617876, -0.32609019, -0.32600151, -0.32591273, -0.32582384, + -0.32573484, -0.32564574, -0.32555653, -0.32546722, -0.32537779, -0.32528826, -0.32519862, -0.32510888, -0.32501902, -0.32492906, + -0.32483899, -0.32474880, -0.32465851, -0.32456811, -0.32447760, -0.32438698, -0.32429625, -0.32420541, -0.32411446, -0.32402339, + -0.32393222, -0.32384093, -0.32374953, -0.32365802, -0.32356639, -0.32347466, -0.32338281, -0.32329084, -0.32319877, -0.32310657, + -0.32301427, -0.32292185, -0.32282932, -0.32273667, -0.32264390, -0.32255102, -0.32245802, -0.32236491, -0.32227168, -0.32217834, + -0.32208487, -0.32199129, -0.32189760, -0.32180378, -0.32170985, -0.32161579, -0.32152162, -0.32142733, -0.32133292, -0.32123839, + -0.32114374, -0.32104898, -0.32095409, -0.32085907, -0.32076394, -0.32066869, -0.32057331, -0.32047782, -0.32038220, -0.32028646, + -0.32019059, -0.32009460, -0.31999849, -0.31990225, -0.31980589, -0.31970941, -0.31961280, -0.31951606, -0.31941920, -0.31932222, + -0.31922511, -0.31912787, -0.31903050, -0.31893301, -0.31883539, -0.31873764, -0.31863977, -0.31854176, -0.31844363, -0.31834537, + -0.31824698, -0.31814845, -0.31804980, -0.31795102, -0.31785211, -0.31775307, -0.31765389, -0.31755458, -0.31745515, -0.31735558, + -0.31725587, -0.31715603, -0.31705606, -0.31695596, -0.31685572, -0.31675535, -0.31665484, -0.31655420, -0.31645342, -0.31635250, + -0.31625145, -0.31615026, -0.31604894, -0.31594748, -0.31584588, -0.31574414, -0.31564226, -0.31554025, -0.31543809, -0.31533580, + -0.31523337, -0.31513079, -0.31502808, -0.31492522, -0.31482222, -0.31471908, -0.31461580, -0.31451237, -0.31440881, -0.31430510, + -0.31420124, -0.31409724, -0.31399310, -0.31388881, -0.31378438, -0.31367980, -0.31357507, -0.31347020, -0.31336518, -0.31326001, + -0.31315470, -0.31304923, -0.31294362, -0.31283786, -0.31273195, -0.31262589, -0.31251968, -0.31241332, -0.31230681, -0.31220015, + -0.31209333, -0.31198637, -0.31187925, -0.31177197, -0.31166455, -0.31155697, -0.31144923, -0.31134134, -0.31123330, -0.31112509, + -0.31101674, -0.31090822, -0.31079955, -0.31069072, -0.31058174, -0.31047259, -0.31036329, -0.31025382, -0.31014420, -0.31003442, + -0.30992447, -0.30981437, -0.30970410, -0.30959367, -0.30948308, -0.30937233, -0.30926141, -0.30915033, -0.30903908, -0.30892767, + -0.30881609, -0.30870435, -0.30859244, -0.30848036, -0.30836812, -0.30825571, -0.30814313, -0.30803038, -0.30791746, -0.30780438, + -0.30769112, -0.30757769, -0.30746409, -0.30735032, -0.30723638, -0.30712226, -0.30700797, -0.30689351, -0.30677887, -0.30666406, + -0.30654907, -0.30643391, -0.30631857, -0.30620305, -0.30608735, -0.30597148, -0.30585543, -0.30573920, -0.30562279, -0.30550620, + -0.30538943, -0.30527247, -0.30515534, -0.30503802, -0.30492052, -0.30480284, -0.30468497, -0.30456692, -0.30444869, -0.30433026, + -0.30421165, -0.30409286, -0.30397388, -0.30385470, -0.30373534, -0.30361580, -0.30349606, -0.30337613, -0.30325601, -0.30313570, + -0.30301519, -0.30289450, -0.30277361, -0.30265252, -0.30253124, -0.30240977, -0.30228810, -0.30216623, -0.30204417, -0.30192191, + -0.30179945, -0.30167680, -0.30155394, -0.30143088, -0.30130762, -0.30118416, -0.30106050, -0.30093664, -0.30081257, -0.30068830, + -0.30056382, -0.30043914, -0.30031426, -0.30018916, -0.30006386, -0.29993835, -0.29981263, -0.29968671, -0.29956057, -0.29943422, + -0.29930766, -0.29918089, -0.29905391, -0.29892671, -0.29879930, -0.29867167, -0.29854383, -0.29841577, -0.29828749, -0.29815900, + -0.29803029, -0.29790136, -0.29777221, -0.29764284, -0.29751325, -0.29738343, -0.29725339, -0.29712313, -0.29699265, -0.29686194, + -0.29673100, -0.29659984, -0.29646845, -0.29633683, -0.29620498, -0.29607291, -0.29594060, -0.29580806, -0.29567529, -0.29554229, + -0.29540905, -0.29527558, -0.29514188, -0.29500793, -0.29487376, -0.29473934, -0.29460468, -0.29446979, -0.29433466, -0.29419928, + -0.29406366, -0.29392780, -0.29379170, -0.29365535, -0.29351876, -0.29338192, -0.29324484, -0.29310751, -0.29296992, -0.29283209, + -0.29269401, -0.29255568, -0.29241709, -0.29227826, -0.29213917, -0.29199982, -0.29186022, -0.29172036, -0.29158024, -0.29143987, + -0.29129923, -0.29115834, -0.29101718, -0.29087577, -0.29073408, -0.29059214, -0.29044993, -0.29030745, -0.29016471, -0.29002170, + -0.28987842, -0.28973487, -0.28959105, -0.28944695, -0.28930259, -0.28915795, -0.28901303, -0.28886784, -0.28872237, -0.28857663, + -0.28843060, -0.28828430, -0.28813771, -0.28799085, -0.28784369, -0.28769626, -0.28754854, -0.28740053, -0.28725224, -0.28710365, + -0.28695478, -0.28680562, -0.28665616, -0.28650641, -0.28635637, -0.28620603, -0.28605540, -0.28590447, -0.28575324, -0.28560171, + -0.28544988, -0.28529774, -0.28514531, -0.28499257, -0.28483952, -0.28468617, -0.28453251, -0.28437854, -0.28422426, -0.28406966, + -0.28391476, -0.28375954, -0.28360401, -0.28344816, -0.28329199, -0.28313550, -0.28297869, -0.28282157, -0.28266411, -0.28250634, + -0.28234824, -0.28218981, -0.28203105, -0.28187197, -0.28171255, -0.28155280, -0.28139272, -0.28123231, -0.28107155, -0.28091046, + -0.28074904, -0.28058727, -0.28042516, -0.28026271, -0.28009991, -0.27993677, -0.27977328, -0.27960944, -0.27944525, -0.27928071, + -0.27911582, -0.27895057, -0.27878497, -0.27861901, -0.27845269, -0.27828601, -0.27811896, -0.27795156, -0.27778379, -0.27761565, + -0.27744715, -0.27727828, -0.27710903, -0.27693941, -0.27676942, -0.27659905, -0.27642831, -0.27625718, -0.27608568, -0.27591379, + -0.27574152, -0.27556886, -0.27539582, -0.27522238, -0.27504856, -0.27487434, -0.27469973, -0.27452472, -0.27434932, -0.27417351, + -0.27399731, -0.27382070, -0.27364369, -0.27346627, -0.27328844, -0.27311020, -0.27293156, -0.27275249, -0.27257301, -0.27239312, + -0.27221280, -0.27203207, -0.27185091, -0.27166932, -0.27148731, -0.27130487, -0.27112200, -0.27093870, -0.27075496, -0.27057078, + -0.27038617, -0.27020111, -0.27001562, -0.26982967, -0.26964328, -0.26945644, -0.26926916, -0.26908141, -0.26889321, -0.26870456, + -0.26851544, -0.26832587, -0.26813583, -0.26794532, -0.26775435, -0.26756290, -0.26737098, -0.26717859, -0.26698572, -0.26679237, + -0.26659854, -0.26640423, -0.26620942, -0.26601413, -0.26581835, -0.26562208, -0.26542530, -0.26522804, -0.26503027, -0.26483199, + -0.26463321, -0.26443393, -0.26423413, -0.26403382, -0.26383299, -0.26363165, -0.26342978, -0.26322739, -0.26302448, -0.26282103, + -0.26261706, -0.26241255, -0.26220750, -0.26200192, -0.26179579, -0.26158912, -0.26138190, -0.26117413, -0.26096581, -0.26075693, + -0.26054749, -0.26033749, -0.26012693, -0.25991579, -0.25970409, -0.25949182, -0.25927896, -0.25906553, -0.25885152, -0.25863692, + -0.25842173, -0.25820595, -0.25798958, -0.25777261, -0.25755503, -0.25733686, -0.25711807, -0.25689868, -0.25667867, -0.25645804, + -0.25623679, -0.25601492, -0.25579242, -0.25556929, -0.25534553, -0.25512113, -0.25489608, -0.25467040, -0.25444406, -0.25421708, + -0.25398944, -0.25376113, -0.25353217, -0.25330254, -0.25307224, -0.25284127, -0.25260962, -0.25237729, -0.25214427, -0.25191057, + -0.25167617, -0.25144107, -0.25120528, -0.25096878, -0.25073157, -0.25049365, -0.25025501, -0.25001565, -0.24977557, -0.24953476, + -0.24929321, -0.24905092, -0.24880790, -0.24856412, -0.24831960, -0.24807432, -0.24782828, -0.24758148, -0.24733391, -0.24708557, + -0.24683644, -0.24658654, -0.24633585, -0.24608436, -0.24583208, -0.24557900, -0.24532512, -0.24507042, -0.24481490, -0.24455857, + -0.24430141, -0.24404341, -0.24378458, -0.24352491, -0.24326440, -0.24300303, -0.24274080, -0.24247772, -0.24221376, -0.24194893, + -0.24168323, -0.24141663, -0.24114915, -0.24088078, -0.24061150, -0.24034131, -0.24007022, -0.23979820, -0.23952526, -0.23925139, + -0.23897658, -0.23870083, -0.23842414, -0.23814648, -0.23786787, -0.23758829, -0.23730773, -0.23702620, -0.23674368, -0.23646017, + -0.23617565, -0.23589013, -0.23560360, -0.23531605, -0.23502747, -0.23473786, -0.23444720, -0.23415550, -0.23386275, -0.23356893, + -0.23327404, -0.23297808, -0.23268103, -0.23238289, -0.23208366, -0.23178331, -0.23148186, -0.23117928, -0.23087557, -0.23057072, + -0.23026472, -0.22995758, -0.22964927, -0.22933978, -0.22902913, -0.22871728, -0.22840424, -0.22808999, -0.22777453, -0.22745785, + -0.22713994, -0.22682078, -0.22650038, -0.22617872, -0.22585580, -0.22553160, -0.22520611, -0.22487932, -0.22455123, -0.22422183, + -0.22389110, -0.22355904, -0.22322563, -0.22289087, -0.22255474, -0.22221723, -0.22187834, -0.22153805, -0.22119636, -0.22085325, + -0.22050870, -0.22016272, -0.21981528, -0.21946638, -0.21911601, -0.21876415, -0.21841080, -0.21805593, -0.21769954, -0.21734162, + -0.21698215, -0.21662112, -0.21625852, -0.21589434, -0.21552856, -0.21516117, -0.21479216, -0.21442152, -0.21404922, -0.21367526, + -0.21329962, -0.21292230, -0.21254327, -0.21216252, -0.21178003, -0.21139580, -0.21100981, -0.21062203, -0.21023247, -0.20984110, + -0.20944790, -0.20905286, -0.20865597, -0.20825721, -0.20785656, -0.20745401, -0.20704954, -0.20664313, -0.20623477, -0.20582443, + -0.20541211, -0.20499778, -0.20458142, -0.20416303, -0.20374257, -0.20332003, -0.20289540, -0.20246865, -0.20203976, -0.20160872, + -0.20117550, -0.20074008, -0.20030245, -0.19986258, -0.19942046, -0.19897606, -0.19852935, -0.19808033, -0.19762896, -0.19717523, + -0.19671911, -0.19626058, -0.19579962, -0.19533620, -0.19487030, -0.19440189, -0.19393096, -0.19345747, -0.19298140, -0.19250273, + -0.19202143, -0.19153748, -0.19105085, -0.19056150, -0.19006942, -0.18957458, -0.18907695, -0.18857650, -0.18807320, -0.18756703, + -0.18705795, -0.18654593, -0.18603095, -0.18551298, -0.18499198, -0.18446791, -0.18394076, -0.18341049, -0.18287706, -0.18234045, + -0.18180061, -0.18125751, -0.18071113, -0.18016141, -0.17960834, -0.17905187, -0.17849196, -0.17792858, -0.17736170, -0.17679126, + -0.17621724, -0.17563959, -0.17505828, -0.17447325, -0.17388449, -0.17329193, -0.17269554, -0.17209527, -0.17149109, -0.17088294, + -0.17027079, -0.16965458, -0.16903428, -0.16840982, -0.16778118, -0.16714829, -0.16651111, -0.16586959, -0.16522368, -0.16457333, + -0.16391848, -0.16325908, -0.16259509, -0.16192643, -0.16125307, -0.16057494, -0.15989199, -0.15920416, -0.15851139, -0.15781362, + -0.15711079, -0.15640284, -0.15568971, -0.15497133, -0.15424765, -0.15351859, -0.15278409, -0.15204408, -0.15129850, -0.15054728, + -0.14979034, -0.14902762, -0.14825905, -0.14748454, -0.14670404, -0.14591745, -0.14512471, -0.14432574, -0.14352046, -0.14270879, + -0.14189065, -0.14106596, -0.14023463, -0.13939659, -0.13855174, -0.13770000, -0.13684128, -0.13597550, -0.13510256, -0.13422237, + -0.13333483, -0.13243987, -0.13153737, -0.13062725, -0.12970940, -0.12878374, -0.12785014, -0.12690853, -0.12595879, -0.12500082, + -0.12403451, -0.12305977, -0.12207647, -0.12108452, -0.12008381, -0.11907421, -0.11805562, -0.11702794, -0.11599103, -0.11494479, + -0.11388910, -0.11282384, -0.11174890, -0.11066415, -0.10956947, -0.10846475, -0.10734985, -0.10622466, -0.10508905, -0.10394290, + -0.10278608, -0.10161846, -0.10043992, -0.09925033, -0.09804956, -0.09683749, -0.09561399, -0.09437892, -0.09313217, -0.09187360, + -0.09060308, -0.08932048, -0.08802568, -0.08671855, -0.08539897, -0.08406680, -0.08272193, -0.08136422, -0.07999357, -0.07860983, + -0.07721290, -0.07580265, -0.07437897, -0.07294175, -0.07149087, -0.07002622, -0.06854769, -0.06705518, -0.06554859, -0.06402781, + -0.06249275, -0.06094333, -0.05937944, -0.05780101, -0.05620796, -0.05460021, -0.05297768, -0.05134032, -0.04968806, -0.04802084, + -0.04633862, -0.04464134, -0.04292897, -0.04120148, -0.03945883, -0.03770101, -0.03592801, -0.03413981, -0.03233641, -0.03051782, + -0.02868406, -0.02683514, -0.02497110, -0.02309197, -0.02119779, -0.01928861, -0.01736449, -0.01542551, -0.01347172, -0.01150322, + -0.00952010, -0.00752244, -0.00551037, -0.00348398, -0.00144340, 0.00061124, 0.00267982, 0.00476218, 0.00685818, 0.00896766, + 0.01109047, 0.01322643, 0.01537537, 0.01753711, 0.01971146, 0.02189824, 0.02409725, 0.02630828, 0.02853113, 0.03076559, + 0.03301145, 0.03526850, 0.03753650, 0.03981524, 0.04210449, 0.04440403, 0.04671362, 0.04903302, 0.05136202, 0.05370037, + 0.05604784, 0.05840419, 0.06076919, 0.06314260, 0.06552419, 0.06791371, 0.07031095, 0.07271566, 0.07512762, 0.07754659, + 0.07997234, 0.08240466, 0.08484332, 0.08728809, 0.08973876, 0.09219511, 0.09465693, 0.09712400, 0.09959613, 0.10207310, + 0.10455472, 0.10704079, 0.10953111, 0.11202550, 0.11452376, 0.11702571, 0.11953118, 0.12203998, 0.12455195, 0.12706691, + 0.12958471, 0.13210517, 0.13462814, 0.13715347, 0.13968101, 0.14221060, 0.14474211, 0.14727539, 0.14981031, 0.15234674, + 0.15488454, 0.15742360, 0.15996378, 0.16250497, 0.16504705, 0.16758990, 0.17013342, 0.17267751, 0.17522205, 0.17776694, + 0.18031209, 0.18285740, 0.18540277, 0.18794812, 0.19049336, 0.19303840, 0.19558317, 0.19812757, 0.20067153, 0.20321498, + 0.20575784, 0.20830003, 0.21084150, 0.21338217, 0.21592198, 0.21846086, 0.22099875, 0.22353559, 0.22607133, 0.22860590, + 0.23113925, 0.23367132, 0.23620208, 0.23873145, 0.24125940, 0.24378588, 0.24631084, 0.24883424, 0.25135603, 0.25387617, + 0.25639462, 0.25891133, 0.26142627, 0.26393941, 0.26645069, 0.26896010, 0.27146758, 0.27397311, 0.27647666, 0.27897818, + 0.28147766, 0.28397505, 0.28647033, 0.28896347, 0.29145443, 0.29394320, 0.29642974, 0.29891403, 0.30139604, 0.30387575, + 0.30635312, 0.30882814, 0.31130078, 0.31377102, 0.31623883, 0.31870419, 0.32116708, 0.32362748, 0.32608537, 0.32854072, + 0.33099352, 0.33344374, 0.33589137, 0.33833639, 0.34077877, 0.34321850, 0.34565556, 0.34808993, 0.35052159, 0.35295053, + 0.35537672, 0.35780016, 0.36022082, 0.36263869, 0.36505375, 0.36746599, 0.36987538, 0.37228192, 0.37468558, 0.37708636, + 0.37948423, 0.38187919, 0.38427121, 0.38666028, 0.38904638, 0.39142951, 0.39380965, 0.39618678, 0.39856089, 0.40093196, + 0.40329999, 0.40566495, 0.40802684, 0.41038563, 0.41274133, 0.41509390, 0.41744334, 0.41978964, 0.42213278, 0.42447275, + 0.42680954, 0.42914313, 0.43147351, 0.43380067, 0.43612459, 0.43844526, 0.44076267, 0.44307681, 0.44538766, 0.44769521, + 0.44999945, 0.45230037, 0.45459794, 0.45689217, 0.45918303, 0.46147052, 0.46375462, 0.46603532, 0.46831261, 0.47058648, + 0.47285691, 0.47512388, 0.47738740, 0.47964744, 0.48190400, 0.48415705, 0.48640660, 0.48865262, 0.49089511, 0.49313404, + 0.49536942, 0.49760123, 0.49982944, 0.50205407, 0.50427508, 0.50649247, 0.50870622, 0.51091633, 0.51312278, 0.51532556, + 0.51752466, 0.51972006, 0.52191175, 0.52409972, 0.52628395, 0.52846444, 0.53064118, 0.53281414, 0.53498332, 0.53714870, + 0.53931028, 0.54146803, 0.54362196, 0.54577203, 0.54791825, 0.55006060, 0.55219907, 0.55433364, 0.55646430, 0.55859104, + 0.56071385, 0.56283271, 0.56494761, 0.56705854, 0.56916549, 0.57126844, 0.57336737, 0.57546229, 0.57755317, 0.57964000, + 0.58172277, 0.58380147, 0.58587608, 0.58794659, 0.59001298, 0.59207525, 0.59413338, 0.59618736, 0.59823717, 0.60028281, + 0.60232425, 0.60436149, 0.60639451, 0.60842330, 0.61044784, 0.61246813, 0.61448415, 0.61649588, 0.61850331, 0.62050644, + 0.62250524, 0.62449970, 0.62648981, 0.62847556, 0.63045693, 0.63243390, 0.63440648, 0.63637463, 0.63833835, 0.64029763, + 0.64225244, 0.64420279, 0.64614864, 0.64809000, 0.65002684, 0.65195915, 0.65388692, 0.65581014, 0.65772879, 0.65964285, + 0.66155232, 0.66345717, 0.66535740, 0.66725300, 0.66914393, 0.67103021, 0.67291180, 0.67478869, 0.67666088, 0.67852834, + 0.68039107, 0.68224905, 0.68410226, 0.68595069, 0.68779433, 0.68963316, 0.69146716, 0.69329633, 0.69512066, 0.69694011, + 0.69875469, 0.70056437, 0.70236915, 0.70416901, 0.70596392, 0.70775389, 0.70953890, 0.71131892, 0.71309395, 0.71486398, + 0.71662898, 0.71838894, 0.72014386, 0.72189371, 0.72363848, 0.72537815, 0.72711272, 0.72884217, 0.73056647, 0.73228563, + 0.73399962, 0.73570843, 0.73741204, 0.73911045, 0.74080363, 0.74249158, 0.74417427, 0.74585169, 0.74752384, 0.74919069, + 0.75085223, 0.75250845, 0.75415932, 0.75580485, 0.75744501, 0.75907979, 0.76070917, 0.76233315, 0.76395170, 0.76556481, + 0.76717247, 0.76877467, 0.77037138, 0.77196261, 0.77354832, 0.77512851, 0.77670317, 0.77827227, 0.77983582, 0.78139378, + 0.78294616, 0.78449293, 0.78603408, 0.78756960, 0.78909947, 0.79062368, 0.79214222, 0.79365508, 0.79516223, 0.79666367, + 0.79815939, 0.79964936, 0.80113359, 0.80261204, 0.80408472, 0.80555161, 0.80701269, 0.80846796, 0.80991739, 0.81136098, + 0.81279872, 0.81423059, 0.81565658, 0.81707668, 0.81849088, 0.81989915, 0.82130150, 0.82269791, 0.82408837, 0.82547286, + 0.82685138, 0.82822391, 0.82959044, 0.83095096, 0.83230547, 0.83365394, 0.83499637, 0.83633274, 0.83766305, 0.83898729, + 0.84030544, 0.84161750, 0.84292346, 0.84422329, 0.84551701, 0.84680459, 0.84808602, 0.84936131, 0.85063043, 0.85189338, + 0.85315014, 0.85440072, 0.85564511, 0.85688329, 0.85811525, 0.85934099, 0.86056050, 0.86177378, 0.86298081, 0.86418159, + 0.86537611, 0.86656437, 0.86774635, 0.86892205, 0.87009147, 0.87125460, 0.87241143, 0.87356196, 0.87470618, 0.87584409, + 0.87697569, 0.87810096, 0.87921990, 0.88033251, 0.88143879, 0.88253873, 0.88363233, 0.88471958, 0.88580049, 0.88687504, + 0.88794324, 0.88900509, 0.89006058, 0.89110971, 0.89215248, 0.89318889, 0.89421893, 0.89524262, 0.89625994, 0.89727090, + 0.89827550, 0.89927374, 0.90026561, 0.90125113, 0.90223028, 0.90320309, 0.90416953, 0.90512963, 0.90608338, 0.90703078, + 0.90797184, 0.90890656, 0.90983495, 0.91075701, 0.91167274, 0.91258216, 0.91348525, 0.91438204, 0.91527253, 0.91615672, + 0.91703461, 0.91790623, 0.91877157, 0.91963064, 0.92048345, 0.92133001, 0.92217033, 0.92300441, 0.92383227, 0.92465392, + 0.92546936, 0.92627861, 0.92708167, 0.92787857, 0.92866930, 0.92945388, 0.93023233, 0.93100466, 0.93177087, 0.93253099, + 0.93328503, 0.93403299, 0.93477491, 0.93551078, 0.93624063, 0.93696446, 0.93768231, 0.93839417, 0.93910008, 0.93980004, + 0.94049407, 0.94118220, 0.94186443, 0.94254079, 0.94321130, 0.94387597, 0.94453483, 0.94518788, 0.94583516, 0.94647669, + 0.94711248, 0.94774255, 0.94836693, 0.94898564, 0.94959870, 0.95020613, 0.95080795, 0.95140420, 0.95199488, 0.95258003, + 0.95315966, 0.95373381, 0.95430249, 0.95486573, 0.95542356, 0.95597600, 0.95652308, 0.95706482, 0.95760125, 0.95813239, + 0.95865827, 0.95917892, 0.95969437, 0.96020464, 0.96070976, 0.96120975, 0.96170465, 0.96219449, 0.96267929, 0.96315908, + 0.96363389, 0.96410374, 0.96456868, 0.96502872, 0.96548391, 0.96593425, 0.96637980, 0.96682058, 0.96725661, 0.96768793, + 0.96811457, 0.96853656, 0.96895393, 0.96936672, 0.96977495, 0.97017865, 0.97057786, 0.97097261, 0.97136293, 0.97174885, + 0.97213040, 0.97250762, 0.97288054, 0.97324919, 0.97361360, 0.97397381, 0.97432985, 0.97468174, 0.97502953, 0.97537324, + 0.97571291, 0.97604857, 0.97638025, 0.97670799, 0.97703182, 0.97735177, 0.97766787, 0.97798016, 0.97828867, 0.97859343, + 0.97889448, 0.97919184, 0.97948556, 0.97977565, 0.98006216, 0.98034512, 0.98062456, 0.98090052, 0.98117301, 0.98144209, + 0.98170778, 0.98197010, 0.98222910, 0.98248481, 0.98273726, 0.98298647, 0.98323249, 0.98347534, 0.98371505, 0.98395166, + 0.98418520, 0.98441569, 0.98464318, 0.98486768, 0.98508924, 0.98530788, 0.98552363, 0.98573652, 0.98594659, 0.98615386, + 0.98635836, 0.98656013, 0.98675919, 0.98695557, 0.98714930, 0.98734042, 0.98752894, 0.98771490, 0.98789833, 0.98807925, + 0.98825770, 0.98843370, 0.98860728, 0.98877847, 0.98894729, 0.98911378, 0.98927795, 0.98943985, 0.98959948, 0.98975689, + 0.98991209, 0.99006512, 0.99021599, 0.99036474, 0.99051139, 0.99065597, 0.99079850, 0.99093900, 0.99107750, 0.99121403, + 0.99134861, 0.99148127, 0.99161202, 0.99174089, 0.99186791, 0.99199310, 0.99211648, 0.99223808, 0.99235791, 0.99247600, + 0.99259238, 0.99270706, 0.99282006, 0.99293142, 0.99304114, 0.99314926, 0.99325579, 0.99336075, 0.99346416, 0.99356605, + 0.99366643, 0.99376533, 0.99386276, 0.99395874, 0.99405330, 0.99414644, 0.99423820, 0.99432859, 0.99441763, 0.99450533, + 0.99459172, 0.99467682, 0.99476063, 0.99484318, 0.99492449, 0.99500457, 0.99508344, 0.99516112, 0.99523762, 0.99531296, + 0.99538716, 0.99546022, 0.99553218, 0.99560304, 0.99567282, 0.99574153, 0.99580919, 0.99587582, 0.99594143, 0.99600603, + 0.99606963, 0.99613226, 0.99619393, 0.99625465, 0.99631443, 0.99637328, 0.99643123, 0.99648828, 0.99654445, 0.99659975, + 0.99665419, 0.99670779, 0.99676055, 0.99681249, 0.99686362, 0.99691396, 0.99696351, 0.99701229, 0.99706031, 0.99710758, + 0.99715410, 0.99719990, 0.99724498, 0.99728936, 0.99733304, 0.99737603, 0.99741834, 0.99745999, 0.99750099, 0.99754134, + 0.99758105, 0.99762014, 0.99765861, 0.99769647, 0.99773373, 0.99777041, 0.99780650, 0.99784202, 0.99787698, 0.99791138, + 0.99794524, 0.99797856, 0.99801135, 0.99804362, 0.99807538, 0.99810663, 0.99813738, 0.99816764, 0.99819742, 0.99822673, + 0.99825556, 0.99828394, 0.99831186, 0.99833934, 0.99836638, 0.99839298, 0.99841916, 0.99844492, 0.99847026, 0.99849520, + 0.99851974, 0.99854388, 0.99856764, 0.99859102, 0.99861402, 0.99863665, 0.99865891, 0.99868082, 0.99870237, 0.99872358, + 0.99874445, 0.99876497, 0.99878517, 0.99880504, 0.99882459, 0.99884383, 0.99886275, 0.99888137, 0.99889969, 0.99891771, + 0.99893544, 0.99895288, 0.99897004, 0.99898692, 0.99900353, 0.99901987, 0.99903594, 0.99905176, 0.99906731, 0.99908262, + 0.99909767, 0.99911249, 0.99912706, 0.99914139, 0.99915549, 0.99916936, 0.99918301, 0.99919643, 0.99920964, 0.99922263, + 0.99923540, 0.99924797, 0.99926034, 0.99927250, 0.99928447, 0.99929624, 0.99930782, 0.99931921, 0.99933041, 0.99934143, + 0.99935227, 0.99936294, 0.99937343, 0.99938375, 0.99939389, 0.99940388, 0.99941370, 0.99942336, 0.99943286, 0.99944221, + 0.99945140, 0.99946044, 0.99946934, 0.99947809, 0.99948669, 0.99949516, 0.99950348, 0.99951167, 0.99951973, 0.99952765, + 0.99953544, 0.99954311, 0.99955065, 0.99955807, 0.99956536, 0.99957253, 0.99957959, 0.99958653, 0.99959336, 0.99960007, + 0.99960668, 0.99961317, 0.99961956, 0.99962585, 0.99963203, 0.99963811, 0.99964409, 0.99964997, 0.99965575, 0.99966144, + 0.99966704, 0.99967254, 0.99967796, 0.99968328, 0.99968852, 0.99969367, 0.99969873, 0.99970371, 0.99970861, 0.99971343, + 0.99971817, 0.99972283, 0.99972742, 0.99973193, 0.99973636, 0.99974072, 0.99974501, 0.99974923, 0.99975338, 0.99975746, + 0.99976148, 0.99976543, 0.99976931, 0.99977313, 0.99977688, 0.99978058, 0.99978421, 0.99978778, 0.99979130, 0.99979475, + 0.99979815, 0.99980149, 0.99980478, 0.99980801, 0.99981119, 0.99981432, 0.99981740, 0.99982042, 0.99982339, 0.99982632, + 0.99982920, 0.99983203, 0.99983481, 0.99983755, 0.99984024, 0.99984289, 0.99984549, 0.99984805, 0.99985057, 0.99985304, + 0.99985548, 0.99985788, 0.99986023, 0.99986255, 0.99986483, 0.99986707, 0.99986927, 0.99987144, 0.99987357, 0.99987567, + 0.99987773, 0.99987975, 0.99988175, 0.99988371, 0.99988564, 0.99988753, 0.99988940, 0.99989123, 0.99989304, 0.99989481, + 0.99989655 +}; diff --git a/src/effect_guitarBooster_F32.h b/src/effect_guitarBooster_F32.h new file mode 100644 index 0000000..ed81e46 --- /dev/null +++ b/src/effect_guitarBooster_F32.h @@ -0,0 +1,175 @@ +#ifndef _EFFECT_GUITARBOOSTER_F32_H_ +#define _EFFECT_GUITARBOOSTER_F32_H_ + +/** + * @file effect_guitarBooster_F32.h + * @author Piotr Zapart + * @brief Oversampled Waveshaper based overdrive effect + * Stereo IO and bypass, but the processing is mono + * @version 0.1 + * @date 2024-03-20 + * + * @copyright Copyright (c) 2024 + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see ." + */ + +#include +#include "basic_DSPutils.h" +#include + + +#define GBOOST_TONE_MINF (800.0f) +#define GBOOST_TONE_MAXF (8000.0f) +#define GBOOST_LP2_F (10000.0f) +#define GBOOST_BOTTOM_MINF (50.0f) +#define GBOOST_BOTTOM_MAXF (350.0f) + +class AudioEffectGuitarBooster_F32 : public AudioStream_F32 +{ +public: + AudioEffectGuitarBooster_F32(void) : AudioStream_F32(2, inputQueueArray) + { + fs_Hz = AUDIO_SAMPLE_RATE_EXACT; + blockSize = AUDIO_BLOCK_SAMPLES; + begin(); + } + + AudioEffectGuitarBooster_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray) + { + fs_Hz = settings.sample_rate_Hz; + blockSize = settings.audio_block_samples; + begin(); + } + + virtual void update(); + + void begin() + { + arm_fir_interpolate_init_f32(&interpolator, upsample_k, FIR_taps, (float32_t*)FIR_coeffs, interpState, AUDIO_BLOCK_SAMPLES); + arm_fir_decimate_init_f32(&decimator, FIR_taps, upsample_k, (float32_t*)FIR_coeffs, decimState, upsample_k * AUDIO_BLOCK_SAMPLES); + bottom(1.0f); + tone(1.0f); + hpPost_k = omega(GBOOST_BOTTOM_MINF); + lp2_k = omega(GBOOST_LP2_F); + } + void drive(float32_t value) + { + value = fabs(value); + value = 1.0f + value * upsample_k; + __disable_irq() + gainSet = value; + __enable_irq(); + } + void bottom(float32_t bottom); + void tone(float32_t t); + void bias(float32_t b) + { + b = constrain(b, -1.0f, 1.0f); + __disable_irq(); + DCbias = b; + __enable_irq(); + } + + void mix(float32_t m) + { + float32_t d, w; + m = constrain(m, 0.0f, 1.0f); + mix_pwr(m, &w, &d); + __disable_irq(); + wetGain = w; + dryGain = d; + __enable_irq(); + } + void volume(float32_t l) + { + l = constrain(l, 0.0f, 1.0f); + __disable_irq(); + levelSet = l; + __enable_irq(); + } + // Bypass + bool bypass_get(void) {return bp;} + void bypass_set(bool state) {bp = state;} + bool bypass_tgl(void) + { + bp ^= 1; + return bp; + } + + bool octave_get(void) {return octave;} + void octave_set(bool state) {octave = state;} + bool octave_tgl(void) + { + octave ^= 1; + return octave; + } +private: + audio_block_f32_t *inputQueueArray[2]; + float fs_Hz; + uint16_t blockSize; + static const uint8_t upsample_k = 5; + static const uint8_t FIR_taps = 75; + static constexpr float32_t FIR_coeffs[FIR_taps] = + { + -0.000033, 0.000112,-0.000100,-0.000103, 0.000361,-0.000331,-0.000181, 0.000822,-0.000824,-0.000205, + 0.001556,-0.001737,-0.000054, 0.002607,-0.003263, 0.000461, 0.003979,-0.005641, 0.001621, 0.005626, + -0.009170, 0.003841, 0.007448,-0.014287, 0.007776, 0.009301,-0.021812, 0.014667, 0.011009,-0.033775, + 0.027639, 0.012392,-0.057262, 0.058949, 0.013293,-0.142816, 0.268001, 0.680272, 0.268001,-0.142816, + 0.013293, 0.058949,-0.057262, 0.012392, 0.027639,-0.033775, 0.011009, 0.014667,-0.021812, 0.009301, + 0.007776,-0.014287, 0.007448, 0.003841,-0.009170, 0.005626, 0.001621,-0.005641, 0.003979, 0.000461, + -0.003263, 0.002607,-0.000054,-0.001737, 0.001556,-0.000205,-0.000824, 0.000822,-0.000181,-0.000331, + 0.000361,-0.000103,-0.000100, 0.000112,-0.000033 + }; + float32_t blockInterpolated[upsample_k * AUDIO_BLOCK_SAMPLES]; + + float32_t interpState[(FIR_taps / upsample_k) + AUDIO_BLOCK_SAMPLES - 1]; + float32_t decimState[FIR_taps + (upsample_k * AUDIO_BLOCK_SAMPLES) - 1]; + + arm_fir_interpolate_instance_f32 interpolator; + arm_fir_decimate_instance_f32 decimator; + arm_linear_interp_instance_f32 waveshaper = + { + 2000, -1.0f, 2.0f/2000.0f, &driveWaveform[0] + }; + bool bp = true; // bypass flag + + bool octave = true; + + float32_t dryGain = 0.0f; + float32_t wetGain = 1.0f; + float32_t DCbias = 0.175f; + + float32_t gainSet = 1.0f; + float32_t gain = 0.0f; + float32_t gain_hp = 1.0f; + float32_t levelSet = 1.0f; + float32_t level = 1.0f; + float32_t lp1_k = 0.0f; + float32_t lp1_reg = 0.0f; + float32_t lp2_k = 0.0f; + float32_t lp2_reg = 0.0f; + float32_t hpPre1_k = 0.0f; + float32_t hpPre1_reg = 0.0f; + float32_t hpPre2_k = 0.0f; + float32_t hpPre2_reg = 0.0f; + float32_t hpPost_k = 0.0f; + float32_t hpPost_reg = 0.0f; + + static float32_t driveWaveform[2001]; + + inline float32_t omega(float f) + { + float32_t fs = fs_Hz * upsample_k; + return 1.0f - expf(-TWO_PI * f / fs); + } +}; + +#endif // _EFFECT_GUITARBOOSTER_F32_H_ diff --git a/src/effect_noiseGateStereo_F32.h b/src/effect_noiseGateStereo_F32.h index 60769cf..506c2b0 100644 --- a/src/effect_noiseGateStereo_F32.h +++ b/src/effect_noiseGateStereo_F32.h @@ -4,9 +4,10 @@ * Created: Max Huster, Feb 2021 * Purpose: This module mutes the Audio completly, when it's below a given threshold. * - * This processes a single stream fo audio data (ie, it is mono) - * * MIT License. use at your own risk. + * + * 03.2024 - stereo version with optional side chain input via pointers + * by Piotr Zapart (www.hexefx.com) */ #ifndef _AudioEffectNoiseGateStereo_F32_h @@ -18,94 +19,150 @@ class AudioEffectNoiseGateStereo_F32 : public AudioStream_F32 { public: - // constructor - AudioEffectNoiseGateStereo_F32(void) : AudioStream_F32(4, inputQueueArray_f32){}; - AudioEffectNoiseGateStereo_F32(const AudioSettings_F32 &settings) : AudioStream_F32(4, inputQueueArray_f32){}; + AudioEffectNoiseGateStereo_F32(float32_t* sideChainSrcL, float32_t* sideChainSrcR) : AudioStream_F32(2, inputQueueArray_f32) + { + p_sideChain_inL = sideChainSrcL; + p_sideChain_inR = sideChainSrcR; + setDefaults(); + }; + AudioEffectNoiseGateStereo_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray_f32) + { + fs = settings.sample_rate_Hz; + setDefaults(); + } void update(void) { - audio_block_f32_t *blockL, *blockR, *blockSideChL, *blockSideChR, *blockSideCh, *blockGain; - blockL = AudioStream_F32::receiveWritable_f32(0); - blockR = AudioStream_F32::receiveWritable_f32(1); - blockSideChL = AudioStream_F32::receiveReadOnly_f32(2); // side chain inputL - blockSideChR = AudioStream_F32::receiveReadOnly_f32(3); // side chain inputR - blockSideCh = AudioStream_F32::allocate_f32(); // allocate new block for summed L+R - blockGain = AudioStream_F32::allocate_f32(); // create a new audio block for the gain - + audio_block_f32_t *blockL, *blockR, *blockSideCh, *blockGain; + audio_block_f32_t *blockOutL, *blockOutR; - if (!blockL || !blockR || !blockSideChL || !blockSideChR || !blockSideCh || !blockGain) + blockL = AudioStream_F32::receiveReadOnly_f32(0); + blockR = AudioStream_F32::receiveReadOnly_f32(1); + // no input signal + if (!blockL || !blockR) { - if (blockSideChL) AudioStream_F32::release(blockSideChL); - if (blockSideChR) AudioStream_F32::release(blockSideChR); - if (blockSideCh) AudioStream_F32::release(blockSideCh); - if (blockGain) AudioStream_F32::release(blockGain); if (blockL) AudioStream_F32::release(blockL); if (blockR) AudioStream_F32::release(blockR); - return; - } - // sum L + R - arm_add_f32(blockSideChL->data, blockSideChR->data, blockSideCh->data, blockSideCh->length); + return; + } + if (bp) + { + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + return; + } + + blockSideCh = AudioStream_F32::allocate_f32(); // allocate new block for summed L+R + blockGain = AudioStream_F32::allocate_f32(); // create a new audio block for the gain + if (!p_sideChain_inL || !p_sideChain_inR || !blockSideCh || !blockGain) + { + if (blockSideCh) AudioStream_F32::release(blockSideCh); + if (blockGain) AudioStream_F32::release(blockGain); + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + return; + } + blockOutL = AudioStream_F32::allocate_f32(); + blockOutR = AudioStream_F32::allocate_f32(); + if (!blockOutL || !blockOutR) + { + if (blockOutL) AudioStream_F32::release(blockOutL); + if (blockOutR) AudioStream_F32::release(blockOutR); + return; + } + //sum L + R + arm_add_f32(p_sideChain_inL, p_sideChain_inR, blockSideCh->data, blockSideCh->length); arm_scale_f32(blockSideCh->data, 0.5f, blockSideCh->data, blockSideCh->length); // divide by 2 - // calculate the desired gain calcGain(blockSideCh, blockGain); - // smooth the "blocky" gain block calcSmoothedGain(blockGain); - // multiply it to the input singal - arm_mult_f32(blockGain->data, blockL->data, blockL->data, blockL->length); - arm_mult_f32(blockGain->data, blockR->data, blockR->data, blockR->length); - // release gainBlock + arm_mult_f32(blockGain->data, blockL->data, blockOutL->data, blockOutL->length); + arm_mult_f32(blockGain->data, blockR->data, blockOutR->data, blockOutR->length); + AudioStream_F32::release(blockGain); AudioStream_F32::release(blockSideCh); - AudioStream_F32::release(blockSideChL); - AudioStream_F32::release(blockSideChR); - // transmit the block and be done - AudioStream_F32::transmit(blockL, 0); - AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::transmit(blockOutL, 0); + AudioStream_F32::transmit(blockOutR, 1); AudioStream_F32::release(blockL); AudioStream_F32::release(blockR); + AudioStream_F32::release(blockOutL); + AudioStream_F32::release(blockOutR); } + /** + * @brief Set gfate threshold in decibels + * value less than -99dB turns the bypass on + * + * @param dbfs gate threshold in dB + */ void setThreshold(float dbfs) { + if (dbfs < -99.0f) bp = true; + else bp = false; // convert dbFS to linear value to comapre against later linearThreshold = pow10f(dbfs / 20.0f); } void setOpeningTime(float timeInSeconds) { - openingTimeConst = expf(-1.0f / (timeInSeconds * AUDIO_SAMPLE_RATE)); + openingTimeConst = expf(-1.0f / (timeInSeconds * fs)); } void setClosingTime(float timeInSeconds) { - closingTimeConst = expf(-1.0f / (timeInSeconds * AUDIO_SAMPLE_RATE)); + closingTimeConst = expf(-1.0f / (timeInSeconds * fs)); } void setHoldTime(float timeInSeconds) { - holdTimeNumSamples = timeInSeconds * AUDIO_SAMPLE_RATE; + holdTimeNumSamples = timeInSeconds * fs; } bool infoIsOpen() { return _isOpenDisplay; } - + void bypass_set(bool state) + { + __disable_irq(); + bp = state; + __enable_irq(); + } + bool bypass_tgl(void) + { + bool bp_new = bp ^ 1; + __disable_irq(); + bp = bp_new; + __enable_irq(); + return bp; + } private: + float32_t fs = AUDIO_SAMPLE_RATE_EXACT; + float32_t* p_sideChain_inL = NULL; + float32_t* p_sideChain_inR = NULL; float32_t linearThreshold; float32_t prev_gain_dB = 0; float32_t openingTimeConst, closingTimeConst; - float lastGainBlockValue = 0; + float32_t lastGainBlockValue = 0; int32_t counter, holdTimeNumSamples = 0; audio_block_f32_t *inputQueueArray_f32[4]; bool falling = false; - + bool bp = false; bool _isOpen = false; bool _isOpenDisplay = false; - bool _extSideChain = true; + + void setDefaults() + { + setOpeningTime(0.01f); + setClosingTime(0.05f); + setHoldTime(0.01f); + } void calcGain(audio_block_f32_t *input, audio_block_f32_t *gainBlock) { @@ -153,7 +210,6 @@ private: for (int i = 0; i < gain_block->length; i++) { gain = gain_block->data[i]; - // smooth the gain using the opening or closing constants if (gain > prev_gain_dB) { // are we in the opening phase? @@ -163,7 +219,6 @@ private: { // or, we're in the closing phase gain_block->data[i] = closingTimeConst * prev_gain_dB + one_minus_closing_const * gain; } - // save value for the next time through this loop prev_gain_dB = gain_block->data[i]; } diff --git a/src/effect_platereverb_F32.cpp b/src/effect_platereverb_F32.cpp index efae55c..659d2a3 100644 --- a/src/effect_platereverb_F32.cpp +++ b/src/effect_platereverb_F32.cpp @@ -46,7 +46,9 @@ AudioEffectPlateReverb_F32::AudioEffectPlateReverb_F32() : AudioStream_F32(2, in bool AudioEffectPlateReverb_F32::begin() { - input_attn = 0.5f; + inputGainSet = 0.5f; + inputGain = 0.5f; + inputGain_tmp = 0.5f; wet_gain = 1.0f; // default mode: wet signal only dry_gain = 0.0f; in_allp_k = INP_ALLP_COEFF; @@ -129,7 +131,7 @@ void AudioEffectPlateReverb_F32::update() // handle bypass, 1st call will clean the buffers to avoid continuing the previous reverb tail if (flags.bypass) { - if (!flags.cleanup_done) + if (!flags.cleanup_done && bp_mode != BYPASS_MODE_TRAILS) { in_allp_1L.reset(); in_allp_2L.reset(); @@ -149,33 +151,37 @@ void AudioEffectPlateReverb_F32::update() lp_dly4.reset(); flags.cleanup_done = 1; } - - if (dry_gain > 0.0f) // if dry/wet mixer is used + switch(bp_mode) { - blockL = AudioStream_F32::receiveReadOnly_f32(0); - blockR = AudioStream_F32::receiveReadOnly_f32(1); - if (!blockL || !blockR) - { - if (blockL) AudioStream_F32::release(blockL); - if (blockR) AudioStream_F32::release(blockR); + case BYPASS_MODE_PASS: + blockL = AudioStream_F32::receiveReadOnly_f32(0); + blockR = AudioStream_F32::receiveReadOnly_f32(1); + if (!blockL || !blockR) + { + if (blockL) AudioStream_F32::release(blockL); + if (blockR) AudioStream_F32::release(blockR); + return; + } + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + return; + break; + case BYPASS_MODE_OFF: + blockL = AudioStream_F32::allocate_f32(); + if (!blockL) return; + memset(&blockL->data[0], 0, blockL->length*sizeof(float32_t)); + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockL, 1); + AudioStream_F32::release(blockL); return; - } - AudioStream_F32::transmit(blockL, 0); - AudioStream_F32::transmit(blockR, 1); - AudioStream_F32::release(blockL); - AudioStream_F32::release(blockR); - return; + break; + case BYPASS_MODE_TRAILS: + default: + break; } - blockL = AudioStream_F32::allocate_f32(); - if (!blockL) return; - memset(&blockL->data[0], 0, blockL->length*sizeof(float32_t)); - AudioStream_F32::transmit(blockL, 0); - AudioStream_F32::transmit(blockL, 1); - AudioStream_F32::release(blockL); - return; } - flags.cleanup_done = 0; - blockL = AudioStream_F32::receiveWritable_f32(0); blockR = AudioStream_F32::receiveWritable_f32(1); @@ -185,6 +191,7 @@ void AudioEffectPlateReverb_F32::update() if (blockR) AudioStream_F32::release(blockR); return; } + flags.cleanup_done = 0; rv_time = rv_time_k; for (i=0; i < blockL->length; i++) @@ -193,7 +200,9 @@ void AudioEffectPlateReverb_F32::update() lfo1.update(); lfo2.update(); - acc = blockL->data[i] * input_attn; + inputGain += (inputGainSet - inputGain) * 0.25f; + + acc = blockL->data[i] * inputGain; // chained input allpasses, channel L acc = in_allp_1L.process(acc); @@ -203,7 +212,7 @@ void AudioEffectPlateReverb_F32::update() in_allp_out_L = pitchL.process(in_allp_out_L); // chained input allpasses, channel R - acc = blockR->data[i] * input_attn; + acc = blockR->data[i] * inputGain; acc = in_allp_1R.process(acc); acc = in_allp_2R.process(acc); diff --git a/src/effect_platereverb_F32.h b/src/effect_platereverb_F32.h index 6ff9c2a..311eef0 100644 --- a/src/effect_platereverb_F32.h +++ b/src/effect_platereverb_F32.h @@ -28,16 +28,6 @@ * Algorithm based on plate reverbs developed for SpinSemi FV-1 DSP chip * * Allpass + modulated delay line based lush plate reverb - * - * Input parameters are float in range 0.0 to 1.0: - * - * size - reverb time - * hidamp - hi frequency loss in the reverb tail - * lodamp - low frequency loss in the reverb tail - * lowpass - output/master lowpass filter, useful for darkening the reverb sound - * diffusion - lower settings will make the reverb tail more "echoey". - * freeze - infinite reverb tail effect - * */ #ifndef _EFFECT_PLATEREVERB_F32_H_ @@ -60,38 +50,63 @@ public: bool begin(void); + /** + * @brief sets the reverb time + * + * @param n range 0.0f - 1.0f + */ void size(float n) { n = constrain(n, 0.0f, 1.0f); n = 2*n - n*n; n = map(n, 0.0f, 1.0f, 0.2f, rv_time_k_max); - //float attn = map(n, 0.2f, rv_time_k_max, 0.5f, 0.25f); + rv_time_k_tmp = n; + inputGain_tmp = 0.5f; __disable_irq(); rv_time_k = n; - input_attn = 0.5f; + inputGainSet = 0.5f; __enable_irq(); } - + /** + * @brief returns the set reverb time + * + * @return float reverb time value + */ float size_get(void) {return rv_time_k;} + /** + * @brief Treble loss in reverb tail + * + * @param n 0.0f to 1.0f + */ void hidamp(float n) { n = 1.0f - constrain(n, 0.0f, 1.0f); + lp_hidamp_k_tmp = n; __disable_irq(); lp_hidamp_k = n; __enable_irq(); } - + /** + * @brief Bass loss in reverb tails + * + * @param n 0.0f to 1.0f + */ void lodamp(float n) { n = -constrain(n, 0.0f, 1.0f); float32_t tscal = 1.0f + n*0.12f; //n is negativbe here + lp_lodamp_k_tmp = n; __disable_irq(); lp_lodamp_k = n; rv_time_scaler = tscal; // limit the max reverb time, otherwise it will clip __enable_irq(); } - + /** + * @brief Output lowpass filter + * + * @param n 0.0f to 1.0f + */ void lowpass(float n) { n = 1.0f - constrain(n, 0.0f, 1.0f); @@ -99,6 +114,11 @@ public: master_lp_k = n; __enable_irq(); } + /** + * @brief Output highpass filter + * + * @param n 0.0f 1.0f + */ void hipass(float n) { n = -constrain(n, 0.0f, 1.0f); @@ -106,6 +126,12 @@ public: master_hp_k = n; __enable_irq(); } + /** + * @brief reverb tail diffusion, + * lower values produce more single repeats, echos + * + * @param n 0.0f - 1.0f + */ void diffusion(float n) { n = constrain(n, 0.0f, 1.0f); @@ -115,9 +141,15 @@ public: loop_allp_k = n; __enable_irq(); } - + /** + * @brief Freeze option On/Off. Freeze sets the reverb + * time to infinity and mutes (almost) the input signal + * + * @param state + */ void freeze(bool state) { + if (flags.freeze == state || flags.bypass) return; flags.freeze = state; if (state) { @@ -126,7 +158,7 @@ public: lp_hidamp_k_tmp = lp_hidamp_k; __disable_irq(); rv_time_k = freeze_rvtime_k; - input_attn = freeze_ingain; + inputGainSet = freeze_ingain; rv_time_scaler = 1.0f; lp_lodamp_k = freeze_lodamp_k; lp_hidamp_k = freeze_hidamp_k; @@ -136,11 +168,14 @@ public: } else { - //float attn = map(rv_time_k_tmp, 0.0f, rv_time_k_max, 0.5f, 0.25f); // recalc the in attenuation float sc = 1.0f - lp_lodamp_k_tmp * 0.12f; // scale up the reverb time due to bass loss __disable_irq(); rv_time_k = rv_time_k_tmp; // restore the value - input_attn = 0.5f; + if (!flags.bypass) + { + inputGainSet = 0.5f; + inputGain_tmp = 0.5f; + } rv_time_scaler = sc; lp_hidamp_k = lp_hidamp_k_tmp; lp_lodamp_k = lp_lodamp_k_tmp; @@ -160,9 +195,13 @@ public: b = constrain(b, 0.0f, 1.0f); b = map(b, 0.0f, 1.0f, 0.0f, 0.1f); freeze_ingain = b; - if (flags.freeze) input_attn = b; // update input gain if freeze is enabled + if (flags.freeze) inputGainSet = b; // update input gain if freeze is enabled } - + /** + * @brief Internal Dry / Wet mixer + * + * @param m 0.0f (full dry) - 1.0f (full wet) + */ void mix(float m) { float32_t dry, wet; @@ -173,7 +212,11 @@ public: dry_gain = dry; __enable_irq(); } - + /** + * @brief wet signal volume + * + * @param wet 0.0f - 1.0f + */ void wet_level(float wet) { wet = constrain(wet, 0.0f, 6.0f); @@ -181,7 +224,11 @@ public: wet_gain = wet; __enable_irq(); } - + /** + * @brief dry signal volume + * + * @param dry 0.0f - 1.0f + */ void dry_level(float dry) { dry = constrain(dry, 0.0f, 1.0f); @@ -189,21 +236,51 @@ public: dry_gain = dry; __enable_irq(); } - - bool freeze_tgl() {flags.freeze ^= 1; freeze(flags.freeze); return flags.freeze;} - + /** + * @brief toogle the Freeze mode + * + * @return true + * @return false + */ + bool freeze_tgl() {freeze(flags.freeze^1); return flags.freeze;} + /** + * @brief return the Freeze mode state + * + * @return true + * @return false + */ bool freeze_get() {return flags.freeze;} - + + typedef enum + { + BYPASS_MODE_PASS, // pass the input signal to the output + BYPASS_MODE_OFF, // mute the output + BYPASS_MODE_TRAILS // mutes the input only + }bypass_mode_t; + /** + * @brief sets the bypass mode (see above) + * + * @param m + */ + void bypass_setMode(bypass_mode_t m) + { + if (m <= BYPASS_MODE_TRAILS) bp_mode = m; + } + bypass_mode_t bypass_geMode() {return bp_mode;} bool bypass_get(void) {return flags.bypass;} void bypass_set(bool state) { flags.bypass = state; - if (state) freeze(false); // disable freeze in bypass mode + if (state) + { + if (bp_mode == BYPASS_MODE_TRAILS) inputGainSet = 0.0f; + freeze(false); // disable freeze in bypass mode + } + else inputGainSet = inputGain_tmp; } bool bypass_tgl(void) { - flags.bypass ^= 1; - if (flags.bypass) freeze(false); // disable freeze in bypass mode + bypass_set(flags.bypass^1); return flags.bypass; } @@ -219,9 +296,9 @@ public: } /** - * @brief + * @brief Contriols the amount of shimmer effect * - * @param s + * @param s 0.0f - 1.0f */ void shimmer(float s) { @@ -232,11 +309,21 @@ public: pitchShimR.setMix(s); shimmerRatio = s; } + /** + * @brief Sets the pitch of the shimmer effect + * + * @param ratio pitch up (>1.0f) or down (<1.0f) ratio + */ void shimmerPitch(float ratio) { pitchShimL.setPitch(ratio); pitchShimR.setPitch(ratio); } + /** + * @brief Sets the shimmer effect pitch in semitones + * + * @param semitones + */ void shimmerPitchSemitones(int8_t semitones) { pitchShimL.setPitchSemintone(semitones); @@ -252,6 +339,11 @@ public: pitchL.setPitchSemintone(semitones); pitchR.setPitchSemintone(semitones); } + /** + * @brief Reverb pitch shifter dry/wet mixer + * + * @param s 0.0f(dry reverb) - 1.0f (100% pitch shifter out) + */ void pitchMix(float s) { s = constrain(s, 0.0f, 1.0f); @@ -268,6 +360,7 @@ private: unsigned shimmer: 1; unsigned cleanup_done: 1; }flags; + bypass_mode_t bp_mode = BYPASS_MODE_PASS; audio_block_f32_t *inputQueueArray_f32[2]; static const uint16_t IN_ALLP1_BUFL_LEN = 224u; @@ -320,7 +413,9 @@ private: AudioBasicLfo lfo1 = AudioBasicLfo(1.35f, LFO_AMPL); AudioBasicLfo lfo2 = AudioBasicLfo(1.57f, LFO_AMPL); - float input_attn; + float inputGain; + float inputGainSet; + float inputGain_tmp; float wet_gain; float dry_gain; diff --git a/src/effect_reverbsc_F32.cpp b/src/effect_reverbsc_F32.cpp index e97e94a..f8532a9 100644 --- a/src/effect_reverbsc_F32.cpp +++ b/src/effect_reverbsc_F32.cpp @@ -1,7 +1,7 @@ #include "effect_reverbsc_F32.h" -#define REVERBSC_DLYBUF_SIZE 98936 + #define DELAYPOS_SHIFT 28 #define DELAYPOS_SCALE 0x10000000 #define DELAYPOS_MASK 0x0FFFFFFF @@ -30,6 +30,8 @@ static int DelayLineBytesAlloc(float32_t sr, float32_t i_pitch_mod, int n); static const float32_t kOutputGain = 0.35f; static const float32_t kJpScale = 0.25f; +extern uint8_t external_psram_size; + AudioEffectReverbSc_F32::AudioEffectReverbSc_F32(bool use_psram) : AudioStream_F32(2, inputQueueArray_f32) { sample_rate_ = AUDIO_SAMPLE_RATE_EXACT; @@ -37,13 +39,27 @@ AudioEffectReverbSc_F32::AudioEffectReverbSc_F32(bool use_psram) : AudioStream_F lpfreq_ = 10000; i_pitch_mod_ = 1; damp_fact_ = 0.195847f; // ~16kHz - + flags.mem_fail = 0; + flags.bypass = 0; + flags.freeze = 0; + flags.cleanup_done = 1; + flags.memsetup_done = 0; + int i, n_bytes = 0; n_bytes = 0; - if (use_psram) aux_ = (float32_t *) extmem_malloc(REVERBSC_DLYBUF_SIZE*sizeof(float32_t)); + if (use_psram) + { + // no PSRAM detected - enter the memoery failsafe mode = fixed bypass + if (external_psram_size == 0) + { + flags.mem_fail = 1; + return; + } + aux_ = (float32_t *) extmem_malloc(aux_size_bytes); + } else { - aux_ = (float32_t *) malloc(REVERBSC_DLYBUF_SIZE*sizeof(float32_t)); + aux_ = (float32_t *) malloc(aux_size_bytes); flags.memsetup_done = 1; } if (!aux_) return; @@ -57,8 +73,7 @@ AudioEffectReverbSc_F32::AudioEffectReverbSc_F32(bool use_psram) : AudioStream_F n_bytes += DelayLineBytesAlloc(AUDIO_SAMPLE_RATE_EXACT, 1, i); } mix(0.5f); - flags.bypass = 0; - flags.freeze = 0; + initialised = true; } @@ -135,14 +150,6 @@ void AudioEffectReverbSc_F32::InitDelayLine(ReverbScDl_t *lp, int n) void AudioEffectReverbSc_F32::update() { #if defined(__IMXRT1062__) - if (!initialised) return; - if ( !flags.memsetup_done) - { - memset(aux_, 0, REVERBSC_DLYBUF_SIZE*sizeof(float32_t)); - arm_dcache_flush_delete(aux_, REVERBSC_DLYBUF_SIZE*sizeof(float32_t)); - flags.memsetup_done = 1; - return; - } audio_block_f32_t *blockL, *blockR; int16_t i; float32_t a_in_l, a_in_r, a_out_l, a_out_r, dryL, dryR; @@ -152,32 +159,68 @@ void AudioEffectReverbSc_F32::update() uint32_t n; int buffer_size; /* Local copy */ float32_t damp_fact = damp_fact_; + + if (!initialised) return; + // special case if memory allocation failed, pass the input signal directly to the output + if (flags.mem_fail) + { + blockL = AudioStream_F32::receiveReadOnly_f32(0); + blockR = AudioStream_F32::receiveReadOnly_f32(1); + if (!blockL || !blockR) + { + if (blockL) AudioStream_F32::release(blockL); + if (blockR) AudioStream_F32::release(blockR); + return; + } + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + return; + } + if ( !flags.memsetup_done) + { + flags.memsetup_done = memCleanup(); + return; + } if (flags.bypass) { - if (dry_gain > 0.0f) // if dry/wet mixer is used + if (!flags.cleanup_done && bp_mode != BYPASS_MODE_TRAILS) { - blockL = AudioStream_F32::receiveReadOnly_f32(0); - blockR = AudioStream_F32::receiveReadOnly_f32(1); - if (!blockL || !blockR) - { - if (blockL) AudioStream_F32::release(blockL); - if (blockR) AudioStream_F32::release(blockR); + flags.cleanup_done = memCleanup(); + } + switch(bp_mode) + { + case BYPASS_MODE_PASS: + blockL = AudioStream_F32::receiveReadOnly_f32(0); + blockR = AudioStream_F32::receiveReadOnly_f32(1); + if (!blockL || !blockR) + { + if (blockL) AudioStream_F32::release(blockL); + if (blockR) AudioStream_F32::release(blockR); + return; + } + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); return; - } - AudioStream_F32::transmit(blockL, 0); - AudioStream_F32::transmit(blockR, 1); - AudioStream_F32::release(blockL); - AudioStream_F32::release(blockR); - return; + break; + case BYPASS_MODE_OFF: + blockL = AudioStream_F32::allocate_f32(); + if (!blockL) return; + memset(&blockL->data[0], 0, blockL->length*sizeof(float32_t)); + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockL, 1); + AudioStream_F32::release(blockL); + return; + break; + case BYPASS_MODE_TRAILS: + break; + default: + break; } - blockL = AudioStream_F32::allocate_f32(); - if (!blockL) return; - memset(&blockL->data[0], 0, blockL->length*sizeof(float32_t)); - AudioStream_F32::transmit(blockL, 0); - AudioStream_F32::transmit(blockL, 1); - AudioStream_F32::release(blockL); - return; } blockL = AudioStream_F32::receiveWritable_f32(0); blockR = AudioStream_F32::receiveWritable_f32(1); @@ -189,8 +232,10 @@ void AudioEffectReverbSc_F32::update() AudioStream_F32::release(blockR); return; } + flags.cleanup_done = 0; for (i = 0; i < blockL->length; i++) { + input_gain += (input_gain_set - input_gain) * 0.25f; /* calculate "resultant junction pressure" and mix to input signals */ a_in_l = a_out_l = a_out_r = 0.0f; dryL = blockL->data[i] * input_gain; @@ -290,15 +335,16 @@ void AudioEffectReverbSc_F32::update() void AudioEffectReverbSc_F32::freeze(bool state) { + if (flags.freeze == state) return; flags.freeze = state; if (state) { feedback_tmp = feedback_; // store the settings damp_fact_tmp = damp_fact_; - input_gain_tmp = input_gain; + input_gain_tmp = input_gain_set; __disable_irq(); feedback_ = 1.0f; // infinite reverb - input_gain = freeze_ingain; + input_gain_set = freeze_ingain; __enable_irq(); } else @@ -306,7 +352,39 @@ void AudioEffectReverbSc_F32::freeze(bool state) __disable_irq(); feedback_ = feedback_tmp; damp_fact_ = damp_fact_tmp; - input_gain = input_gain_tmp; + if (!flags.bypass) + { + input_gain_set = input_gain_tmp; + } __enable_irq(); } } + +/** + * @brief Partial memory clear + * Clearing all the delay buffers at once, esp. if + * the PSRAM is used takes too long for the audio ISR. + * Hence the buffer clear is done in configurable portions + * spread over a few audio update routines. + * + * @return true Memory clean is complete + * @return false Memory clean still in progress + */ +bool AudioEffectReverbSc_F32::memCleanup() +{ + bool result = false; + if (memCleanupEnd > REVERBSC_DLYBUF_SIZE) // last segment + { + memCleanupEnd = REVERBSC_DLYBUF_SIZE; + result = true; + } + uint32_t l = (memCleanupEnd - memCleanupStart) * sizeof(float32_t); + uint8_t* memPtr = (uint8_t *)&aux_[0]+(memCleanupStart*sizeof(float32_t)); + memset(memPtr, 0, l); + arm_dcache_flush_delete(memPtr, l); + + memCleanupStart = memCleanupEnd; + memCleanupEnd += memCleanupStep; + + return result; +} diff --git a/src/effect_reverbsc_F32.h b/src/effect_reverbsc_F32.h index e683b88..73c8547 100644 --- a/src/effect_reverbsc_F32.h +++ b/src/effect_reverbsc_F32.h @@ -8,13 +8,9 @@ * Year: 1999, 2005 * Ported to soundpipe by: Paul Batchelor * - * Ported to Teensy4 and OpenAudio_ArduinoLibrary: + * Ported/upgraded to Teensy4 and OpenAudio_ArduinoLibrary: * 01.2024 Piotr Zapart www.hexefx.com * - * Fixes, changes: - * - In the original code the reverb level is affected by the feedback control, fixed - * - Optional - * */ #ifndef _EFFECT_REVERBSC_F32_H_ @@ -27,6 +23,8 @@ #include "arm_math.h" #include "basic_DSPutils.h" +#define REVERBSC_DLYBUF_SIZE 98936 + class AudioEffectReverbSc_F32 : public AudioStream_F32 { public: @@ -57,7 +55,7 @@ public: feedback_tmp = feedb; inGain = map(feedb, 0.1f, feedb_max, 0.5f, 0.2f); __disable_irq(); - input_gain = inGain; + input_gain_set = inGain; feedback_ = feedb; __enable_irq(); } @@ -89,44 +87,70 @@ public: void wet_level(float32_t wet) { - wet_gain = constrain(wet, 0.0f, 1.0f); + wet = constrain(wet, 0.0f, 1.0f); + __disable_irq(); + wet_gain = wet; + __enable_irq(); } void dry_level(float32_t dry) { - dry_gain = constrain(dry, 0.0f, 1.0f); + dry = constrain(dry, 0.0f, 1.0f); + __disable_irq(); + dry_gain = dry; + __enable_irq(); } void freeze(bool state); - bool freeze_tgl() {flags.freeze ^= 1; freeze(flags.freeze); return flags.freeze;} + bool freeze_tgl() {freeze(flags.freeze^1); return flags.freeze;} bool freeze_get() {return flags.freeze;} - + typedef enum + { + BYPASS_MODE_PASS, // pass the input signal to the output + BYPASS_MODE_OFF, // mute the output + BYPASS_MODE_TRAILS // mutes the input only + }bypass_mode_t; + void bypass_setMode(bypass_mode_t m) + { + if (m <= BYPASS_MODE_TRAILS) + { + __disable_irq(); + bp_mode = m; + __enable_irq(); + } + } + bypass_mode_t bypass_geMode() {return bp_mode;} bool bypass_get(void) {return flags.bypass;} void bypass_set(bool state) { + if (flags.mem_fail) return; flags.bypass = state; - if (state) freeze(false); // disable freeze in bypass mode + if (state) + { + if (bp_mode == BYPASS_MODE_TRAILS) input_gain_set = 0.0f; + freeze(false); // disable freeze in bypass mode + __disable_irq(); + memCleanupStart = 0; + memCleanupEnd = memCleanupStep; + __enable_irq(); + } + else input_gain_set = input_gain_tmp; } bool bypass_tgl(void) { - flags.bypass ^= 1; - if (flags.bypass) freeze(false); // disable freeze in bypass mode + bypass_set(flags.bypass^1); return flags.bypass; } - uint32_t getBfAddr() - { - float32_t *addr = aux_; - return (uint32_t)addr; - } - private: struct flags_t { unsigned bypass: 1; unsigned freeze: 1; + unsigned cleanup_done: 1; unsigned memsetup_done: 1; + unsigned mem_fail: 1; }flags = {0, 0, 0}; - + bypass_mode_t bp_mode; audio_block_f32_t *inputQueueArray_f32[2]; void NextRandomLineseg(ReverbScDl_t *lp, int n); void InitDelayLine(ReverbScDl_t *lp, int n); @@ -138,12 +162,21 @@ private: bool initialised = false; ReverbScDl_t delay_lines_[8]; float32_t *aux_; // main delay line storage buffer, placed either in RAM2 or PSRAM + const uint32_t aux_size_bytes = REVERBSC_DLYBUF_SIZE*sizeof(float32_t); float32_t dry_gain = 0.5f; float32_t wet_gain = 0.5f; + float32_t input_gain_set = 0.5f; float32_t input_gain = 0.5f; float32_t input_gain_tmp = 0.5f; float32_t freeze_ingain = 0.05f; static constexpr float32_t feedb_max = 0.99f; + + bool memCleanup(void); + const uint32_t memCleanupStep = 512; + uint32_t memCleanupStart = 0; + uint32_t memCleanupEnd = memCleanupStep; + + }; #endif // _EFFECT_REVERBSC_H_ diff --git a/src/effect_springreverb_F32.cpp b/src/effect_springreverb_F32.cpp index edb81d6..570ab39 100644 --- a/src/effect_springreverb_F32.cpp +++ b/src/effect_springreverb_F32.cpp @@ -39,7 +39,7 @@ AudioEffectSpringReverb_F32::AudioEffectSpringReverb_F32() : AudioStream_F32(2, inputQueueArray) { - input_attn = 0.5f; + inputGain = 0.5f; rv_time_k = 0.8f; in_allp_k = INP_ALLP_COEFF; bool memOK = true; @@ -94,7 +94,7 @@ void AudioEffectSpringReverb_F32::update() if (!initialized) return; if (bp) { - if (!cleanup_done) + if (!cleanup_done && bp_mode != BYPASS_MODE_TRAILS) { sp_lp_allp1a.reset(); sp_lp_allp1b.reset(); @@ -113,29 +113,36 @@ void AudioEffectSpringReverb_F32::update() cleanup_done = true; } - if (dry_gain > 0.0f) // if dry/wet mixer is used + switch(bp_mode) { - blockL = AudioStream_F32::receiveReadOnly_f32(0); - blockR = AudioStream_F32::receiveReadOnly_f32(1); - if (!blockL || !blockR) - { - if (blockL) AudioStream_F32::release(blockL); - if (blockR) AudioStream_F32::release(blockR); + case BYPASS_MODE_PASS: + blockL = AudioStream_F32::receiveReadOnly_f32(0); + blockR = AudioStream_F32::receiveReadOnly_f32(1); + if (!blockL || !blockR) + { + if (blockL) AudioStream_F32::release(blockL); + if (blockR) AudioStream_F32::release(blockR); + return; + } + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); return; - } - AudioStream_F32::transmit(blockL, 0); - AudioStream_F32::transmit(blockR, 1); - AudioStream_F32::release(blockL); - AudioStream_F32::release(blockR); - return; + break; + case BYPASS_MODE_OFF: + blockL = AudioStream_F32::allocate_f32(); + if (!blockL) return; + memset(&blockL->data[0], 0, blockL->length*sizeof(float32_t)); + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockL, 1); + AudioStream_F32::release(blockL); + return; + break; + case BYPASS_MODE_TRAILS: + default: + break; } - blockL = AudioStream_F32::allocate_f32(); - if (!blockL) return; - memset(&blockL->data[0], 0, blockL->length*sizeof(float32_t)); - AudioStream_F32::transmit(blockL, 0); - AudioStream_F32::transmit(blockL, 1); - AudioStream_F32::release(blockL); - return; } cleanup_done = false; blockL = AudioStream_F32::receiveWritable_f32(0); @@ -152,13 +159,14 @@ void AudioEffectSpringReverb_F32::update() for (i=0; i < blockL->length; i++) { lfo.update(); + inputGain += (inputGainSet - inputGain) * 0.25f; dryL = blockL->data[i]; dryR = blockR->data[i]; - dry_in = (dryL + dryR) * input_attn; + dry_in = (dryL + dryR) * inputGain; - mono_in = flt_in.process(dry_in)* (1.0f + in_BassCut_k*-2.5f); // add highpass gain compaensation? - acc = lp_dly1.getTap(0) * rv_time; // get DLY1 output - lp_out1 = flt_lp1.process(acc); // filter it + mono_in = flt_in.process(dry_in)* (1.0f + in_BassCut_k*-2.5f); + acc = lp_dly1.getTap(0) * rv_time; + lp_out1 = flt_lp1.process(acc); acc = sp_lp_allp1a.process(lp_out1); acc = sp_lp_allp1b.process(acc); diff --git a/src/effect_springreverb_F32.h b/src/effect_springreverb_F32.h index d292532..c7c0de7 100644 --- a/src/effect_springreverb_F32.h +++ b/src/effect_springreverb_F32.h @@ -69,10 +69,11 @@ public: { n = constrain(n, 0.0f, 1.0f); n = map (n, 0.0f, 1.0f, 0.7f, rv_time_k_max); - float32_t attn = map(n, 0.0f, rv_time_k_max, 0.5f, 0.2f); + float32_t gain = map(n, 0.0f, rv_time_k_max, 0.5f, 0.2f); + inputGain_tmp = gain; __disable_irq(); rv_time_k = n; - input_attn = attn; + inputGainSet = gain; __enable_irq(); } @@ -119,23 +120,46 @@ public: __enable_irq(); } float32_t get_size(void) {return rv_time_k;} + + typedef enum + { + BYPASS_MODE_PASS, // pass the input signal to the output + BYPASS_MODE_OFF, // mute the output + BYPASS_MODE_TRAILS // mutes the input only + }bypass_mode_t; + void bypass_setMode(bypass_mode_t m) + { + if (m <= BYPASS_MODE_TRAILS) bp_mode = m; + } + bypass_mode_t bypass_geMode() {return bp_mode;} bool bypass_get(void) {return bp;} - void bypass_set(bool state) {bp = state;} + void bypass_set(bool state) + { + bp = state; + if (bp && bp_mode==BYPASS_MODE_TRAILS) + { + inputGainSet = 0.0f; + } + if (bp == false) inputGainSet = inputGain_tmp; + } bool bypass_tgl(void) { - bp ^= 1; + bypass_set(bp^1); return bp; } private: audio_block_f32_t *inputQueueArray[2]; - float32_t input_attn; + float32_t inputGainSet = 0.5f; + float32_t inputGain = 0.5f; + float32_t inputGain_tmp = 0.5f; float32_t wet_gain; float32_t dry_gain; float32_t in_allp_k; // input allpass coeff (default 0.6) float32_t chrp_allp_k[4] = {-0.7f, -0.65f, -0.6f, -0.5f}; bool bp = false; + bypass_mode_t bp_mode = BYPASS_MODE_PASS; bool cleanup_done = false; uint16_t chrp_alp1_idx[SPRVB_CHIRP_AMNT] = {0}; uint16_t chrp_alp2_idx[SPRVB_CHIRP_AMNT] = {0}; @@ -167,6 +191,7 @@ private: float32_t in_BassCut_k; float32_t lp_TrebleCut_k; float32_t lp_BassCut_k; + AudioFilterShelvingLPHP flt_in; AudioFilterShelvingLPHP flt_lp1; diff --git a/src/effect_xfaderStereo_F32.h b/src/effect_xfaderStereo_F32.h new file mode 100644 index 0000000..db3384f --- /dev/null +++ b/src/effect_xfaderStereo_F32.h @@ -0,0 +1,114 @@ +/** + * @file effect_xfaderStereo_F32.h + * @author Piotr Zapart + * @brief constant power crossfader for two stereo signals + * @version 0.1 + * @date 2024-03-21 + * + * @copyright Copyright (c) 2024 + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see ." + */ + +#ifndef _EFFECT_XFADERSTEREO_F32_H_ +#define _EFFECT_XFADERSTEREO_F32_H_ + +#include +#include +#include "basic_components.h" +class AudioEffectXfaderStereo_F32 : public AudioStream_F32 +{ +public: + AudioEffectXfaderStereo_F32(void) : AudioStream_F32(4, inputQueueArray_f32){}; + AudioEffectXfaderStereo_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray_f32){}; + void mix(float32_t m) + { + float32_t gA, gB; + m = constrain(m, 0.0f, 1.0f); + mix_pwr(m, &gB, &gA); + __disable_irq() + gainA = gA; + gainB = gB; + __enable_irq(); + } + void update() + { + audio_block_f32_t *blockLa, *blockRa, *blockLb, *blockRb; + audio_block_f32_t *blockOutLa, *blockOutRa,*blockOutLb, *blockOutRb; + blockLa = AudioStream_F32::receiveReadOnly_f32(0); + blockRa = AudioStream_F32::receiveReadOnly_f32(1); + blockLb = AudioStream_F32::receiveReadOnly_f32(2); + blockRb = AudioStream_F32::receiveReadOnly_f32(3); + + if (!blockLa || !blockRa || !blockLb || !blockRb) + { + if (blockLa) AudioStream_F32::release(blockLa); + if (blockRa) AudioStream_F32::release(blockRa); + if (blockLb) AudioStream_F32::release(blockLb); + if (blockRb) AudioStream_F32::release(blockRb); + return; + } + // max A, B mited + if (gainA == 1.0f) + { + AudioStream_F32::transmit(blockLa, 0); + AudioStream_F32::transmit(blockRa, 1); + AudioStream_F32::release(blockLa); + AudioStream_F32::release(blockRa); + AudioStream_F32::release(blockLb); + AudioStream_F32::release(blockRb); + return; + } + if (gainB == 1.0f) + { + AudioStream_F32::transmit(blockLb, 0); + AudioStream_F32::transmit(blockRb, 1); + AudioStream_F32::release(blockLa); + AudioStream_F32::release(blockRa); + AudioStream_F32::release(blockLb); + AudioStream_F32::release(blockRb); + return; + } + blockOutLa = AudioStream_F32::allocate_f32(); + blockOutRa = AudioStream_F32::allocate_f32(); + blockOutLb = AudioStream_F32::allocate_f32(); + blockOutRb = AudioStream_F32::allocate_f32(); + if (!blockOutLa || !blockOutRa || !blockOutLa || !blockOutRa) + { + if (blockOutLa) AudioStream_F32::release(blockOutLa); + if (blockOutRa) AudioStream_F32::release(blockOutRa); + if (blockOutLb) AudioStream_F32::release(blockOutLb); + if (blockOutRb) AudioStream_F32::release(blockOutRb); + return; + } + + arm_scale_f32(blockLa->data, gainA, blockOutLa->data, blockOutLa->length); + arm_scale_f32(blockRa->data, gainA, blockOutRa->data, blockOutRa->length); + arm_scale_f32(blockLb->data, gainB, blockOutLb->data, blockOutLb->length); + arm_scale_f32(blockRb->data, gainB, blockOutRb->data, blockOutRb->length); + arm_add_f32(blockOutLa->data, blockOutLb->data, blockOutLa->data, blockOutLa->length); + arm_add_f32(blockOutRa->data, blockOutRb->data, blockOutRa->data, blockOutRa->length); + AudioStream_F32::transmit(blockOutLa, 0); + AudioStream_F32::transmit(blockOutRa, 1); + AudioStream_F32::release(blockLa); + AudioStream_F32::release(blockRa); + AudioStream_F32::release(blockLb); + AudioStream_F32::release(blockRb); + AudioStream_F32::release(blockOutLa); + AudioStream_F32::release(blockOutRa); + AudioStream_F32::release(blockOutLb); + AudioStream_F32::release(blockOutRb); + } +private: + audio_block_f32_t *inputQueueArray_f32[4]; + float32_t gainA = 1.0f; + float32_t gainB = 0.0f; +}; +#endif // _EFFECT_XFADERSTEREO_F32_H_ diff --git a/src/filter_3bandeq.h b/src/filter_3bandeq.h index 8d337f5..f78bb3e 100644 --- a/src/filter_3bandeq.h +++ b/src/filter_3bandeq.h @@ -15,6 +15,7 @@ // - Uses 4 first order filters in series, should give 24dB per octave // - Now with P4 Denormal fix :) //---------------------------------------------------------------------------- +// Teensy OpenAudio_ArduinoLibrary_F32 port: 01.2024 Piotr Zapart www.hexefx.com #ifndef _FILTER_3BANDEQ_H_ #define _FILTER_3BANDEQ_H_ @@ -23,7 +24,6 @@ #include "AudioStream_F32.h" #include "arm_math.h" #include "mathDSP_F32.h" -#include "basic_components.h" class AudioFilterEqualizer3band_F32 : public AudioStream_F32 { diff --git a/src/filter_biquadStereo_F32.cpp b/src/filter_biquadStereo_F32.cpp new file mode 100644 index 0000000..84bc963 --- /dev/null +++ b/src/filter_biquadStereo_F32.cpp @@ -0,0 +1,59 @@ +#include "filter_biquadStereo_F32.h" + +void AudioFilterBiquadStereo_F32::update(void) +{ + audio_block_f32_t *blockL, *blockR, *blockOutL, *blockOutR; + blockL = AudioStream_F32::receiveWritable_f32(0); + blockR = AudioStream_F32::receiveWritable_f32(1); + + // no input signal + if (!blockL || !blockR) + { + if (blockL) AudioStream_F32::release(blockL); + if (blockR) AudioStream_F32::release(blockR); + return; + } + if (bp || gain_dry == 1.0f || !doBiquad) + { + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + return; + } + + blockOutL = AudioStream_F32::allocate_f32(); + blockOutR = AudioStream_F32::allocate_f32(); + if (!blockOutL || !blockOutR) + { + if (blockOutL) AudioStream_F32::release(blockOutL); + if (blockOutR) AudioStream_F32::release(blockOutR); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + return; + } + arm_biquad_cascade_df1_f32(&iirL_inst, blockL->data, blockOutL->data, blockOutL->length); + arm_biquad_cascade_df1_f32(&iirR_inst, blockR->data, blockOutR->data, blockOutR->length); + if (gain_wet != 1.0f) // transmit wet only + { + arm_scale_f32(blockL->data, gain_dry, blockL->data, blockL->length); // dryL * gain_dry + arm_scale_f32(blockR->data, gain_dry, blockR->data, blockR->length); // dryR * gain_dry + arm_scale_f32(blockOutL->data, gain_wet, blockOutL->data, blockOutL->length); // wetL * gain_wet + arm_scale_f32(blockOutR->data, gain_wet, blockOutR->data, blockOutR->length); // wetR * gain_wet + arm_add_f32(blockL->data, blockOutL->data, blockOutL->data, blockOutL->length); // dryL+wetL + arm_add_f32(blockR->data, blockOutR->data, blockOutR->data, blockOutR->length); // dryR+wetR + } + if (makeup_gain != 1.0f) + { + arm_scale_f32(blockOutL->data, makeup_gain, blockOutL->data, blockOutL->length); // wetL * makeup gain + arm_scale_f32(blockOutR->data, makeup_gain, blockOutR->data, blockOutR->length); // wetR * makeup gain + } + AudioStream_F32::transmit(blockOutL, 0); + AudioStream_F32::transmit(blockOutR, 1); + AudioStream_F32::release(blockOutL); + AudioStream_F32::release(blockOutR); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + + +} diff --git a/src/filter_biquadStereo_F32.h b/src/filter_biquadStereo_F32.h new file mode 100644 index 0000000..323f248 --- /dev/null +++ b/src/filter_biquadStereo_F32.h @@ -0,0 +1,258 @@ +#ifndef _FILTER_BIQUADSTEREO_F32_H_ +#define _FILTER_BIQUADSTEREO_F32_H_ + +#include "Arduino.h" +#include "AudioStream_F32.h" +#include "arm_math.h" +#include "basic_DSPutils.h" + +// Changed Feb 2021 +#define IIR_STEREO_MAX_STAGES 4 + +class AudioFilterBiquadStereo_F32 : public AudioStream_F32 +{ + // GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node + // GUI: shortName:IIR2 +public: + AudioFilterBiquadStereo_F32(uint8_t stages=IIR_STEREO_MAX_STAGES) : AudioStream_F32(2, inputQueueArray), numStagesUsed(stages) + { + setSampleRate_Hz(AUDIO_SAMPLE_RATE_EXACT); + doClassInit(); + } + AudioFilterBiquadStereo_F32(const AudioSettings_F32 &settings, uint8_t stages=IIR_STEREO_MAX_STAGES) : AudioStream_F32(2, inputQueueArray), numStagesUsed(stages) + { + setSampleRate_Hz(settings.sample_rate_Hz); + doClassInit(); + } + + void doClassInit(void) + { + memset(&coeff32[0], 0, 5 * IIR_STEREO_MAX_STAGES * sizeof(coeff32[0])); + for (int ii = 0; ii < 4; ii++) + { + coeff32[5 * ii] = 1.0f; // b0 = 1 for pass through + } + arm_biquad_cascade_df1_init_f32(&iirL_inst, numStagesUsed, &coeff32[0], &stateL_F32[0]); + arm_biquad_cascade_df1_init_f32(&iirR_inst, numStagesUsed, &coeff32[0], &stateR_F32[0]); + doBiquad = false; // This is the way to jump over the biquad + } + + // Up to 4 stages are allowed. Coefficients, either by design function + // or from direct setCoefficients() need to be added to the double array + // and also to the float + void setCoefficients(int iStage, double *cf) + { + if (iStage > numStagesUsed) + { + if (Serial) + { + Serial.print("AudioFilterBiquad_F32: setCoefficients:"); + Serial.println(" *** MaxStages Error"); + } + return; + } + + for (int ii = 0; ii < 5; ii++) + { + coeff32[ii + 5 * iStage] = (float)cf[ii]; // and of floats + } + + doBiquad = true; + } + + void end(void) + { + doBiquad = false; + } + + void setSampleRate_Hz(float _fs_Hz) { sampleRate_Hz = _fs_Hz; } + + // Deprecated + void setBlockDC(void) + { + // https://www.keil.com/pack/doc/CMSIS/DSP/html/group__BiquadCascadeDF1.html#ga8e73b69a788e681a61bccc8959d823c5 + // Use matlab to compute the coeff for HP at 40Hz: [b,a]=butter(2,40/(44100/2),'high'); %assumes fs_Hz = 44100 + double b[] = {8.173653471988667e-01, -1.634730694397733e+00, 8.173653471988667e-01}; // from Matlab + double a[] = {1.000000000000000e+00, -1.601092394183619e+00, 6.683689946118476e-01}; // from Matlab + setFilterCoeff_Matlab(b, a); + } + + void setFilterCoeff_Matlab(double b[], double a[]) + { // one stage of N=2 IIR + double coeff[5]; + // https://www.keil.com/pack/doc/CMSIS/DSP/html/group__BiquadCascadeDF1.html#ga8e73b69a788e681a61bccc8959d823c5 + // Use matlab to compute the coeff, such as: [b,a]=butter(2,20/(44100/2),'high'); %assumes fs_Hz = 44100 + coeff[0] = b[0]; + coeff[1] = b[1]; + coeff[2] = b[2]; // here are the matlab "b" coefficients + coeff[3] = -a[1]; + coeff[4] = -a[2]; // the DSP needs the "a" terms to have opposite sign vs Matlab + setCoefficients(0, coeff); + } + // Compute common filter functions + // http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt + // void setLowpass(uint32_t stage, float frequency, float q = 0.7071) { + void setLowpass(int stage, float frequency, float q) + { + double coeff[5]; + double w0 = frequency * (2 * 3.141592654 / sampleRate_Hz); + double sinW0 = sin(w0); + double alpha = sinW0 / ((double)q * 2.0); + double cosW0 = cos(w0); + double scale = 1.0 / (1.0 + alpha); // which is equal to 1.0 / a0 + /* b0 */ coeff[0] = ((1.0 - cosW0) / 2.0) * scale; + /* b1 */ coeff[1] = (1.0 - cosW0) * scale; + /* b2 */ coeff[2] = coeff[0]; + /* a1 */ coeff[3] = -(-2.0 * cosW0) * scale; + /* a2 */ coeff[4] = -(1.0 - alpha) * scale; + setCoefficients(stage, coeff); + } + + void setHighpass(uint32_t stage, float frequency, float q) + { + double coeff[5]; + double w0 = frequency * (2 * 3.141592654 / sampleRate_Hz); + double sinW0 = sin(w0); + double alpha = sinW0 / ((double)q * 2.0); + double cosW0 = cos(w0); + double scale = 1.0 / (1.0 + alpha); + /* b0 */ coeff[0] = ((1.0 + cosW0) / 2.0) * scale; + /* b1 */ coeff[1] = -(1.0 + cosW0) * scale; + /* b2 */ coeff[2] = coeff[0]; + /* a1 */ coeff[3] = -(-2.0 * cosW0) * scale; + /* a2 */ coeff[4] = -(1.0 - alpha) * scale; + setCoefficients(stage, coeff); + } + + void setBandpass(uint32_t stage, float frequency, float q) + { + double coeff[5]; + double w0 = frequency * (2 * 3.141592654 / sampleRate_Hz); + double sinW0 = sin(w0); + double alpha = sinW0 / ((double)q * 2.0); + double cosW0 = cos(w0); + double scale = 1.0 / (1.0 + alpha); + /* b0 */ coeff[0] = alpha * scale; + /* b1 */ coeff[1] = 0; + /* b2 */ coeff[2] = (-alpha) * scale; + /* a1 */ coeff[3] = -(-2.0 * cosW0) * scale; + /* a2 */ coeff[4] = -(1.0 - alpha) * scale; + setCoefficients(stage, coeff); + } + + // frequency in Hz. q makes the response stay close to 0.0dB until + // close to the notch frequency. q up to 100 or more seem stable. + void setNotch(uint32_t stage, float frequency, float q) + { + double coeff[5]; + double w0 = frequency * (2 * 3.141592654 / sampleRate_Hz); + double sinW0 = sin(w0); + double alpha = sinW0 / ((double)q * 2.0); + double cosW0 = cos(w0); + double scale = 1.0 / (1.0 + alpha); // which is equal to 1.0 / a0 + /* b0 */ coeff[0] = scale; + /* b1 */ coeff[1] = (-2.0 * cosW0) * scale; + /* b2 */ coeff[2] = coeff[0]; + /* a1 */ coeff[3] = -(-2.0 * cosW0) * scale; + /* a2 */ coeff[4] = -(1.0 - alpha) * scale; + setCoefficients(stage, coeff); + } + + void setLowShelf(uint32_t stage, float frequency, float gain, float slope) + { + double coeff[5]; + double a = pow(10.0, gain / 40.0); + double w0 = frequency * (2 * 3.141592654 / sampleRate_Hz); + double sinW0 = sin(w0); + // double alpha = (sinW0 * sqrt((a+1/a)*(1/slope-1)+2) ) / 2.0; + double cosW0 = cos(w0); + // generate three helper-values (intermediate results): + double sinsq = sinW0 * sqrt((pow(a, 2.0) + 1.0) * (1.0 / slope - 1.0) + 2.0 * a); + double aMinus = (a - 1.0) * cosW0; + double aPlus = (a + 1.0) * cosW0; + double scale = 1.0 / ((a + 1.0) + aMinus + sinsq); + /* b0 */ coeff[0] = a * ((a + 1.0) - aMinus + sinsq) * scale; + /* b1 */ coeff[1] = 2.0 * a * ((a - 1.0) - aPlus) * scale; + /* b2 */ coeff[2] = a * ((a + 1.0) - aMinus - sinsq) * scale; + /* a1 */ coeff[3] = 2.0 * ((a - 1.0) + aPlus) * scale; + /* a2 */ coeff[4] = -((a + 1.0) + aMinus - sinsq) * scale; + setCoefficients(stage, coeff); + } + + void setHighShelf(uint32_t stage, float frequency, float gain, float slope) + { + double coeff[5]; + double a = pow(10.0, gain / 40.0); + double w0 = frequency * (2 * 3.141592654 / sampleRate_Hz); + double sinW0 = sin(w0); + // double alpha = (sinW0 * sqrt((a+1/a)*(1/slope-1)+2) ) / 2.0; + double cosW0 = cos(w0); + // generate three helper-values (intermediate results): + double sinsq = sinW0 * sqrt((pow(a, 2.0) + 1.0) * (1.0 / slope - 1.0) + 2.0 * a); + double aMinus = (a - 1.0) * cosW0; + double aPlus = (a + 1.0) * cosW0; + double scale = 1.0 / ((a + 1.0) - aMinus + sinsq); + /* b0 */ coeff[0] = a * ((a + 1.0) + aMinus + sinsq) * scale; + /* b1 */ coeff[1] = -2.0 * a * ((a - 1.0) + aPlus) * scale; + /* b2 */ coeff[2] = a * ((a + 1.0) + aMinus - sinsq) * scale; + /* a1 */ coeff[3] = -2.0 * ((a - 1.0) - aPlus) * scale; + /* a2 */ coeff[4] = -((a + 1.0) - aMinus - sinsq) * scale; + setCoefficients(stage, coeff); + } + void update(void); + + void mix(float m) + { + float g_wet, g_dry; + m = constrain(m, 0.0f, 1.0f); + mix_pwr(m, &g_wet, &g_dry); + __disable_irq(); + gain_wet = g_wet; + gain_dry = g_dry; + __enable_irq(); + } + void makeupGain(float g) + { + __disable_irq(); + makeup_gain = g; + __enable_irq(); + } + + void bypass_set(bool state) + { + __disable_irq(); + bp = state; + __enable_irq(); + } + bool bypass_tgl(void) + { + bool bp_new = bp ^ 1; + __disable_irq(); + bp = bp_new; + __enable_irq(); + return bp; + } +private: + audio_block_f32_t *inputQueueArray[2]; + bool bp = false; + float coeff32[5 * IIR_STEREO_MAX_STAGES]; // Local copies to be transferred with begin() + float stateL_F32[4 * IIR_STEREO_MAX_STAGES]; + float stateR_F32[4 * IIR_STEREO_MAX_STAGES]; + float sampleRate_Hz = AUDIO_SAMPLE_RATE_EXACT; // default. from AudioStream.h?? + const uint8_t numStagesUsed; + bool doBiquad = false; + float gain_dry = 0.0f; + float gain_wet = 1.0f; + float makeup_gain = 1.0f; + + /* Info - The structure from arm_biquad_casd_df1_inst_f32 consists of + * uint32_t numStages; + * const float32_t *pCoeffs; //Points to the array of coefficients, length 5*numStages. + * float32_t *pState; //Points to the array of state variables, length 4*numStages. + */ + // ARM DSP Math library filter instance. + arm_biquad_casd_df1_inst_f32 iirL_inst; + arm_biquad_casd_df1_inst_f32 iirR_inst; +}; + +#endif // _FILTER_BIQUADSTEREO_F32_H_ diff --git a/src/filter_equalizer_F32.h b/src/filter_equalizer_F32.h index 818cb7b..811fb7c 100644 --- a/src/filter_equalizer_F32.h +++ b/src/filter_equalizer_F32.h @@ -113,9 +113,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * - * 01.2024 - added - * - * + * 01.2024 - added bypass system, Piot rZapart www.hexefx.com */ #ifndef _FILTEREQUALIZER_F32_H_ diff --git a/src/filter_ir_cabsim_F32.cpp b/src/filter_ir_cabsim_F32.cpp index d737cf0..d20696a 100644 --- a/src/filter_ir_cabsim_F32.cpp +++ b/src/filter_ir_cabsim_F32.cpp @@ -56,7 +56,30 @@ void AudioFilterIRCabsim_F32::update() if (blockR) AudioStream_F32::release(blockR); return; } +#ifdef USE_IR_ISR_LOAD + switch(ir_loadState) + { + case IR_LOAD_START: + ptr_fmask = &fmask[0][0]; + ptr_fftout = &fftout[0]; + memset(ptr_fftout, 0, nfor*512*4); // clear fftout array + memset(fftin, 0, 512 * 4); // clear fftin array + ir_loadState = IR_LOAD_STEP1; + break; + case IR_LOAD_STEP1: + init_partitioned_filter_masks(irPtrTable[ir_idx]); + ir_loadState = IR_LOAD_STEP2; + break; + case IR_LOAD_STEP2: + delay.reset(); + ir_loaded = 1; + ir_loadState = IR_LOAD_FINISHED; + break; + case IR_LOAD_FINISHED: + default: break; + } +#endif if (!ir_loaded) // ir not loaded yet or bypass mode { // bypass clean signal @@ -66,6 +89,7 @@ void AudioFilterIRCabsim_F32::update() AudioStream_F32::release(blockR); return; } + if (first_block) // fill real & imaginaries with zeros for the first BLOCKSIZE samples { memset(&fftin[0], 0, blockL->length*sizeof(float32_t)*4); @@ -136,7 +160,6 @@ void AudioFilterIRCabsim_F32::update() // apply post EQ, restore the channel R phase, reduce the gain a bit if (doubleTrack) { - arm_fir_f32(&FIR_postL, blockL->data, blockL->data, blockL->length); arm_fir_f32(&FIR_postR, blockR->data, blockR->data, blockR->length); arm_scale_f32(blockR->data, -doubler_gainR, blockR->data, blockR->length); @@ -156,6 +179,7 @@ void AudioFilterIRCabsim_F32::ir_register(const float32_t *irPtr, uint8_t positi irPtrTable[position] = irPtr; } + void AudioFilterIRCabsim_F32::ir_load(uint8_t idx) { const float32_t *newIrPtr = NULL; @@ -173,6 +197,17 @@ void AudioFilterIRCabsim_F32::ir_load(uint8_t idx) { return; } +#ifdef USE_IR_ISR_LOAD + nc = newIrPtr[0]; + uint32_t _nfor = nc / IR_BUFFER_SIZE; + if (_nfor > nforMax) _nfor = nforMax; + __disable_irq() + nfor = _nfor; + ir_loadState = IR_LOAD_START; + __enable_irq(); + ir_length_ms = (1000.0f * nfor * (float32_t)AUDIO_BLOCK_SAMPLES) / AUDIO_SAMPLE_RATE_EXACT; +#else + AudioNoInterrupts(); nc = newIrPtr[0]; nfor = nc / IR_BUFFER_SIZE; @@ -188,8 +223,11 @@ void AudioFilterIRCabsim_F32::ir_load(uint8_t idx) delay.reset(); ir_loaded = 1; AudioInterrupts(); + #endif + } + void AudioFilterIRCabsim_F32::init_partitioned_filter_masks(const float32_t *irPtr) { const static arm_cfft_instance_f32 *maskS; diff --git a/src/filter_ir_cabsim_F32.h b/src/filter_ir_cabsim_F32.h index 3a66955..e83745e 100644 --- a/src/filter_ir_cabsim_F32.h +++ b/src/filter_ir_cabsim_F32.h @@ -41,6 +41,9 @@ #define IR_N_B (1) #define IR_MAX_REG_NUM 11 // max number of registered IRs + +#define USE_IR_ISR_LOAD + class AudioFilterIRCabsim_F32 : public AudioStream_F32 { public: @@ -155,6 +158,17 @@ private: 0.154734746f, 0.35352844f, 0.441179603f, 0.35352844f, 0.154734746f, -0.0208595414f, -0.0834295526f, -0.0470990688f, 0.0108086104f, 0.0319586769f, 0.0158470348f, -0.00560652791f, -0.0112718018f, -0.00476867426f, 0.0013392308f, 0.00207542209f, 0.000503875781f, 0.0f }; + + + typedef enum + { + IR_LOAD_START, + IR_LOAD_STEP1, + IR_LOAD_STEP2, + IR_LOAD_FINISHED + }ir_loadState_t; + ir_loadState_t ir_loadState = IR_LOAD_FINISHED; + }; diff --git a/src/hexefx_audio_F32.h b/src/hexefx_audio_F32.h index b9de132..49784ed 100644 --- a/src/hexefx_audio_F32.h +++ b/src/hexefx_audio_F32.h @@ -1,13 +1,19 @@ #ifndef _HEXEFX_AUDIO_H #define _HEXEFX_AUDIO_H +#include "control_WM8731_F32.h" +#include "control_SGTL5000_F32.h" + #include "input_i2s2_F32.h" #include "output_i2s2_F32.h" +#include "switch_selectorStereo_F32.h" + #include "filter_ir_cabsim_F32.h" #include "filter_tonestackStereo_F32.h" #include "filter_equalizer_F32.h" #include "filter_3bandeq.h" +#include "filter_biquadStereo_F32.h" #include "effect_gainStereo_F32.h" #include "effect_platereverb_F32.h" @@ -17,6 +23,9 @@ #include "effect_infphaser_F32.h" #include "effect_phaserStereo_F32.h" #include "effect_noiseGateStereo_F32.h" -#include "effect_delaystereo.h" +#include "effect_delaystereo_F32.h" +#include "effect_compressorStereo_F32.h" +#include "effect_guitarBooster_F32.h" +#include "effect_xfaderStereo_F32.h" #endif // _HEXEFX_AUDIO_H diff --git a/src/input_i2s2_F32.cpp b/src/input_i2s2_F32.cpp index 3376509..b965ee1 100644 --- a/src/input_i2s2_F32.cpp +++ b/src/input_i2s2_F32.cpp @@ -1,6 +1,6 @@ /* * input_i2s2_f32.cpp - * + * * Audio Library for Teensy 3.X * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com * @@ -26,23 +26,23 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - /* +/* * Extended by Chip Audette, OpenAudio, May 2019 * Converted to F32 and to variable audio block length * The F32 conversion is under the MIT License. Use at your own risk. */ // Updated OpenAudio F32 with this version from Chip Audette's Tympan Library Jan 2021 RSL -#include //do we really need this? (Chip: 2020-10-31) +#include //do we really need this? (Chip: 2020-10-31) #include "input_i2s2_F32.h" #include "output_i2s2_F32.h" - +#include "basic_DSPutils.h" #include -//DMAMEM __attribute__((aligned(32))) -static uint32_t i2s2_rx_buffer[AUDIO_BLOCK_SAMPLES]; //good for 16-bit audio samples coming in from teh AIC. 32-bit transfers will need this to be bigger. -audio_block_f32_t * AudioInputI2S2_F32::block_left_f32 = NULL; -audio_block_f32_t * AudioInputI2S2_F32::block_right_f32 = NULL; +// DMAMEM __attribute__((aligned(32))) +static uint64_t i2s2_rx_buffer[AUDIO_BLOCK_SAMPLES] __attribute__((aligned(32))); // good for 16-bit audio samples coming in from teh AIC. 32-bit transfers will need this to be bigger. +audio_block_f32_t *AudioInputI2S2_F32::block_left_f32 = NULL; +audio_block_f32_t *AudioInputI2S2_F32::block_right_f32 = NULL; uint16_t AudioInputI2S2_F32::block_offset = 0; bool AudioInputI2S2_F32::update_responsibility = false; DMAChannel AudioInputI2S2_F32::dma(false); @@ -53,164 +53,123 @@ unsigned long AudioInputI2S2_F32::update_counter = 0; float AudioInputI2S2_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; int AudioInputI2S2_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; -//#for 16-bit transfers -#define I2S2_BUFFER_TO_USE_BYTES (AudioOutputI2S2_F32::audio_block_samples*sizeof(i2s2_rx_buffer[0])) - -//#for 32-bit transfers -//#define I2S2_BUFFER_TO_USE_BYTES (AudioOutputI2S_F32::audio_block_samples*2*sizeof(i2s_rx_buffer[0])) +#define I2S2_BUFFER_TO_USE_BYTES (AudioOutputI2S2_F32::audio_block_samples * sizeof(i2s2_rx_buffer[0])) -void AudioInputI2S2_F32::begin(void) { - bool transferUsing32bit = false; - begin(transferUsing32bit); -} - -void AudioInputI2S2_F32::begin(bool transferUsing32bit) { +// -------------------------------------------------------------------------------- +void AudioInputI2S2_F32::begin() +{ dma.begin(true); // Allocate the DMA channel first - AudioOutputI2S2_F32::sample_rate_Hz = sample_rate_Hz; //these were given in the AudioSettings in the contructor - AudioOutputI2S2_F32::audio_block_samples = audio_block_samples;//these were given in the AudioSettings in the contructor - - //block_left_1st = NULL; - //block_right_1st = NULL; + AudioOutputI2S2_F32::sample_rate_Hz = sample_rate_Hz; // these were given in the AudioSettings in the contructor + AudioOutputI2S2_F32::audio_block_samples = audio_block_samples; // these were given in the AudioSettings in the contructor // TODO: should we set & clear the I2S_RCSR_SR bit here? - AudioOutputI2S2_F32::config_i2s(transferUsing32bit); + AudioOutputI2S2_F32::config_i2s(); -#if defined(__IMXRT1062__) - CORE_PIN5_CONFIG = 2; //EMC_08, 2=SAI2_RX_DATA, page 434 + CORE_PIN5_CONFIG = 2; // EMC_08, 2=SAI2_RX_DATA, page 434 IOMUXC_SAI2_RX_DATA0_SELECT_INPUT = 0; // 0=GPIO_EMC_08_ALT2, page 876 - - dma.TCD->SADDR = (void *)((uint32_t)&I2S2_RDR0 + 2); + + dma.TCD->SADDR = (void *)((uint32_t)&I2S2_RDR0); dma.TCD->SOFF = 0; - dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); - dma.TCD->NBYTES_MLNO = 2; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLNO = 4; dma.TCD->SLAST = 0; dma.TCD->DADDR = i2s2_rx_buffer; - dma.TCD->DOFF = 2; - //dma.TCD->CITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; //original from Teensy Audio Library - dma.TCD->CITER_ELINKNO = I2S2_BUFFER_TO_USE_BYTES / 2; - //dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer); //original from Teensy Audio Library + dma.TCD->DOFF = 4; + dma.TCD->CITER_ELINKNO = I2S2_BUFFER_TO_USE_BYTES / 4; dma.TCD->DLASTSGA = -I2S2_BUFFER_TO_USE_BYTES; - //dma.TCD->BITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; //original from Teensy Audio Library - dma.TCD->BITER_ELINKNO = I2S2_BUFFER_TO_USE_BYTES / 2; + dma.TCD->BITER_ELINKNO = I2S2_BUFFER_TO_USE_BYTES / 4; dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_RX); I2S2_RCSR = I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR; // page 2099 - I2S2_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE; // page 2087 -#endif + update_responsibility = update_setup(); dma.enable(); dma.attachInterrupt(isr); - + update_counter = 0; } - - +// ------------------------------ RX DMA ISR ------------------------------------------ void AudioInputI2S2_F32::isr(void) { uint32_t daddr, offset; - const int16_t *src, *end; - //int16_t *dest_left, *dest_right; - //audio_block_t *left, *right; + const int32_t *src, *end; float32_t *dest_left_f32, *dest_right_f32; audio_block_f32_t *left_f32, *right_f32; -#if defined(KINETISK) || defined(__IMXRT1062__) daddr = (uint32_t)(dma.TCD->DADDR); -#endif dma.clearInterrupt(); - //Serial.println("isr"); - - //if (daddr < (uint32_t)i2s_rx_buffer + sizeof(i2s_rx_buffer) / 2) { //original Teensy Audio Library - if (daddr < (uint32_t)i2s2_rx_buffer + I2S2_BUFFER_TO_USE_BYTES / 2) { + if (daddr < (uint32_t)i2s2_rx_buffer + I2S2_BUFFER_TO_USE_BYTES / 2) + { // DMA is receiving to the first half of the buffer // need to remove data from the second half - //src = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original Teensy Audio Library - //end = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES]; //original Teensy Audio Library - src = (int16_t *)&i2s2_rx_buffer[audio_block_samples/2]; - end = (int16_t *)&i2s2_rx_buffer[audio_block_samples]; - update_counter++; //let's increment the counter here to ensure that we get every ISR resulting in audio + src = (int32_t *)&i2s2_rx_buffer[audio_block_samples / 2]; + end = (int32_t *)&i2s2_rx_buffer[audio_block_samples]; + update_counter++; // let's increment the counter here to ensure that we get every ISR resulting in audio if (AudioInputI2S2_F32::update_responsibility) AudioStream_F32::update_all(); - } else { + } + else + { // DMA is receiving to the second half of the buffer // need to remove data from the first half - src = (int16_t *)&i2s2_rx_buffer[0]; - //end = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original Teensy Audio Library - end = (int16_t *)&i2s2_rx_buffer[audio_block_samples/2]; + src = (int32_t *)&i2s2_rx_buffer[0]; + end = (int32_t *)&i2s2_rx_buffer[audio_block_samples / 2]; } left_f32 = AudioInputI2S2_F32::block_left_f32; right_f32 = AudioInputI2S2_F32::block_right_f32; - if (left_f32 != NULL && right_f32 != NULL) { + if (left_f32 != NULL && right_f32 != NULL) + { offset = AudioInputI2S2_F32::block_offset; - //if (offset <= (uint32_t)(AUDIO_BLOCK_SAMPLES/2)) { //original Teensy Audio Library - if (offset <= ((uint32_t) audio_block_samples/2)) { + if (offset <= ((uint32_t)audio_block_samples / 2)) + { dest_left_f32 = &(left_f32->data[offset]); dest_right_f32 = &(right_f32->data[offset]); - //AudioInputI2S2_F32::block_offset = offset + AUDIO_BLOCK_SAMPLES/2; //original Teensy Audio Library - AudioInputI2S2_F32::block_offset = offset + audio_block_samples/2; - do { - //Serial.println(*src); - //n = *src++; - //*dest_left++ = (int16_t)n; - //*dest_right++ = (int16_t)(n >> 16); - *dest_left_f32++ = (float32_t) *src++; - *dest_right_f32++ = (float32_t) *src++; + AudioInputI2S2_F32::block_offset = offset + audio_block_samples / 2; + do + { + *dest_left_f32++ = (float32_t)*src++; + *dest_right_f32++ = (float32_t)*src++; } while (src < end); } } } - -#define I16_TO_F32_NORM_FACTOR (3.051850947599719e-05) //which is 1/32767 -void AudioInputI2S2_F32::scale_i16_to_f32( float32_t *p_i16, float32_t *p_f32, int len) { - for (int i=0; idata, out_f32->data, audio_block_samples); - scale_i16_to_f32(out_f32->data, out_f32->data, audio_block_samples); - - //prepare to transmit by setting the update_counter (which helps tell if data is skipped or out-of-order) +// -------------------------------------------------------------------------------- +void AudioInputI2S2_F32::update_1chan(int chan, audio_block_f32_t *&out_f32) +{ + if (!out_f32) + return; + // scale the float values so that the maximum possible audio values span -1.0 to + 1.0 + arm_scale_f32(out_f32->data, I32_TO_F32_NORM_FACTOR, out_f32->data, audio_block_samples); + // prepare to transmit by setting the update_counter (which helps tell if data is skipped or out-of-order) out_f32->id = update_counter; - - //transmit the f32 data! - AudioStream_F32::transmit(out_f32,chan); - - //release the memory blocks - AudioStream_F32::release(out_f32); + AudioStream_F32::transmit(out_f32, chan); // transmit the f32 data! + AudioStream_F32::release(out_f32); // release the memory blocks } - +// -------------------------------------------------------------------------------- void AudioInputI2S2_F32::update(void) { static bool flag_beenSuccessfullOnce = false; - audio_block_f32_t *new_left=NULL, *new_right=NULL, *out_left=NULL, *out_right=NULL; - + audio_block_f32_t *new_left = NULL, *new_right = NULL, *out_left = NULL, *out_right = NULL; + new_left = AudioStream_F32::allocate_f32(); new_right = AudioStream_F32::allocate_f32(); - if ((!new_left) || (!new_right)) { - //ran out of memory. Clear and return! - if (new_left) AudioStream_F32::release(new_left); - if (new_right) AudioStream_F32::release(new_right); - new_left = NULL; new_right = NULL; + if ((!new_left) || (!new_right)) + { + // ran out of memory. Clear and return! + if (new_left) AudioStream_F32::release(new_left); + if (new_right) AudioStream_F32::release(new_right); + new_left = NULL; + new_right = NULL; flag_out_of_memory = 1; - if (flag_beenSuccessfullOnce) Serial.println("Input_I2S_F32: update(): WARNING!!! Out of Memory."); - } else { - flag_beenSuccessfullOnce = true; + if (flag_beenSuccessfullOnce) + Serial.println("Input_I2S_F32: update(): WARNING!!! Out of Memory."); } - + else {flag_beenSuccessfullOnce = true; } + __disable_irq(); - if (block_offset >= audio_block_samples) { + if (block_offset >= audio_block_samples) + { // the DMA filled 2 blocks, so grab them and get the // 2 new blocks to the DMA, as quickly as possible out_left = block_left_f32; @@ -219,28 +178,31 @@ void AudioInputI2S2_F32::update(void) block_right_f32 = new_right; block_offset = 0; __enable_irq(); - - //update_counter++; //I chose to update it in the ISR instead. - update_1chan(0,out_left); //uses audio_block_samples and update_counter - update_1chan(1,out_right); //uses audio_block_samples and update_counter - - - } else if (new_left != NULL) { + update_1chan(0, out_left); // uses audio_block_samples and update_counter + update_1chan(1, out_right); // uses audio_block_samples and update_counter + } + else if (new_left != NULL) + { // the DMA didn't fill blocks, but we allocated blocks - if (block_left_f32 == NULL) { + if (block_left_f32 == NULL) + { // the DMA doesn't have any blocks to fill, so // give it the ones we just allocated block_left_f32 = new_left; block_right_f32 = new_right; block_offset = 0; __enable_irq(); - } else { + } + else + { // the DMA already has blocks, doesn't need these __enable_irq(); AudioStream_F32::release(new_left); AudioStream_F32::release(new_right); } - } else { + } + else + { // The DMA didn't fill blocks, and we could not allocate // memory... the system is likely starving for memory! // Sadly, there's nothing we can do. @@ -248,13 +210,11 @@ void AudioInputI2S2_F32::update(void) } } -/******************************************************************/ - +// -------------------------------------------------------------------------------- void AudioInputI2S2slave_F32::begin(void) { dma.begin(true); // Allocate the DMA channel first AudioOutputI2S2slave_F32::config_i2s(); - } diff --git a/src/input_i2s2_F32.h b/src/input_i2s2_F32.h index 5fa89cb..ec3b0cb 100644 --- a/src/input_i2s2_F32.h +++ b/src/input_i2s2_F32.h @@ -39,9 +39,10 @@ #include #include #include "AudioStream_F32.h" -#include "AudioStream.h" //Do we really need this?? (Chip, 2020-10-31) #include "DMAChannel.h" + + class AudioInputI2S2_F32 : public AudioStream_F32 { //GUI: inputs:0, outputs:2 //this line used for automatic generation of GUI nodes @@ -54,21 +55,15 @@ public: } virtual void update(void); - static void scale_i16_to_f32( float32_t *p_i16, float32_t *p_f32, int len) ; - static void scale_i24_to_f32( float32_t *p_i24, float32_t *p_f32, int len) ; - static void scale_i32_to_f32( float32_t *p_i32, float32_t *p_f32, int len); + void begin(void); - void begin(bool); - void sub_begin_i32(void); - //void sub_begin_i16(void); int get_isOutOfMemory(void) { return flag_out_of_memory; } void clear_isOutOfMemory(void) { flag_out_of_memory = 0; } - //friend class AudioOutputI2S_F32; + bool get_update_responsibility() { return update_responsibility;} protected: AudioInputI2S2_F32(int dummy): AudioStream_F32(0, NULL) {} // to be used only inside AudioInputI2Sslave !! static bool update_responsibility; static DMAChannel dma; - static void isr_32(void); static void isr(void); virtual void update_1chan(int, audio_block_f32_t *&); private: @@ -86,7 +81,6 @@ class AudioInputI2S2slave_F32 : public AudioInputI2S2_F32 public: AudioInputI2S2slave_F32(void) : AudioInputI2S2_F32(0) { begin(); } void begin(void); - friend void dma_ch1_isr(void); }; #endif // _INPUT_I2S_F32_H_ diff --git a/src/output_i2s2_F32.cpp b/src/output_i2s2_F32.cpp index 3cc8d31..380c4ff 100644 --- a/src/output_i2s2_F32.cpp +++ b/src/output_i2s2_F32.cpp @@ -35,13 +35,8 @@ // Ported to I2S2, 12.2023 by Piotr Zapart www.hexefx.com - for teensy4.x only! #include "output_i2s2_F32.h" -#include -#include //to get access to Audio/utlity/imxrt_hw.h...do we really need this??? WEA 2020-10-31 +#include "basic_DSPutils.h" -float AudioOutputI2S2_F32::setI2SFreq_T3(const float freq_Hz) -{ - return 0.0f; -} audio_block_f32_t *AudioOutputI2S2_F32::block_left_1st = NULL; audio_block_f32_t *AudioOutputI2S2_F32::block_right_1st = NULL; @@ -51,7 +46,7 @@ uint16_t AudioOutputI2S2_F32::block_left_offset = 0; uint16_t AudioOutputI2S2_F32::block_right_offset = 0; bool AudioOutputI2S2_F32::update_responsibility = false; DMAChannel AudioOutputI2S2_F32::dma(false); -DMAMEM __attribute__((aligned(32))) static uint32_t i2s2_tx_buffer[AUDIO_BLOCK_SAMPLES]; +DMAMEM __attribute__((aligned(32))) static uint64_t i2s2_tx_buffer[AUDIO_BLOCK_SAMPLES]; float AudioOutputI2S2_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; int AudioOutputI2S2_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; @@ -60,80 +55,66 @@ int AudioOutputI2S2_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; #include //from Teensy Audio library. For set_audioClock() #endif -// #for 16-bit transfers #define I2S2_BUFFER_TO_USE_BYTES (AudioOutputI2S2_F32::audio_block_samples * sizeof(i2s2_tx_buffer[0])) - -// #for 32-bit transfers -// #define I2S_BUFFER_TO_USE_BYTES (AudioOutputI2S2_F32::audio_block_samples*2*sizeof(i2s_tx_buffer[0])) - +// -------------------------------------------------------------------------------- void AudioOutputI2S2_F32::begin(void) { - bool transferUsing32bit = false; + bool transferUsing32bit = true; begin(transferUsing32bit); } - +// -------------------------------------------------------------------------------- void AudioOutputI2S2_F32::begin(bool transferUsing32bit) { - dma.begin(true); // Allocate the DMA channel first - block_left_1st = NULL; block_right_1st = NULL; - AudioOutputI2S2_F32::config_i2s(transferUsing32bit, sample_rate_Hz); #if defined(__IMXRT1062__) CORE_PIN2_CONFIG = 2; // EMC_04, 2=SAI2_TX_DATA, page 428 dma.TCD->SADDR = i2s2_tx_buffer; - dma.TCD->SOFF = 2; - dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); - dma.TCD->NBYTES_MLNO = 2; - // dma.TCD->SLAST = -sizeof(i2s_tx_buffer);//orig from Teensy Audio Library 2020-10-31 + dma.TCD->SOFF = 4; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLNO = 4; dma.TCD->SLAST = -I2S2_BUFFER_TO_USE_BYTES; dma.TCD->DOFF = 0; - // dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; //orig from Teensy Audio Library 2020-10-31 - dma.TCD->CITER_ELINKNO = I2S2_BUFFER_TO_USE_BYTES / 2; + dma.TCD->CITER_ELINKNO = I2S2_BUFFER_TO_USE_BYTES / 4; dma.TCD->DLASTSGA = 0; - // dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;//orig from Teensy Audio Library 2020-10-31 - dma.TCD->BITER_ELINKNO = I2S2_BUFFER_TO_USE_BYTES / 2; + dma.TCD->BITER_ELINKNO = I2S2_BUFFER_TO_USE_BYTES / 4; dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; - dma.TCD->DADDR = (void *)((uint32_t)&I2S2_TDR0 + 2); + dma.TCD->DADDR = (void *)((uint32_t)&I2S2_TDR0); dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_TX); dma.enable(); // newer location of this line in Teensy Audio library - // I2S2_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; - I2S2_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE | I2S_TCSR_FR; + I2S2_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; + I2S2_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; #endif update_responsibility = update_setup(); dma.attachInterrupt(AudioOutputI2S2_F32::isr); enabled = 1; } - +// -------------------------------------------------------------------------------- void AudioOutputI2S2_F32::isr(void) { -#if defined(KINETISK) || defined(__IMXRT1062__) - int16_t *dest; + int32_t *dest; audio_block_f32_t *blockL, *blockR; uint32_t saddr, offsetL, offsetR; saddr = (uint32_t)(dma.TCD->SADDR); dma.clearInterrupt(); - // if (saddr < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2) { //original 16-bit if (saddr < (uint32_t)i2s2_tx_buffer + I2S2_BUFFER_TO_USE_BYTES / 2) { // are we transmitting the first half or second half of the buffer? // DMA is transmitting the first half of the buffer // so we must fill the second half - // dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original Teensy Audio - dest = (int16_t *)&i2s2_tx_buffer[audio_block_samples / 2]; // this will be diff if we were to do 32-bit samples - if (AudioOutputI2S2_F32::update_responsibility) - AudioStream_F32::update_all(); + dest = (int32_t *)&i2s2_tx_buffer[audio_block_samples / 2]; + if (AudioOutputI2S2_F32::update_responsibility) AudioStream_F32::update_all(); } else { // DMA is transmitting the second half of the buffer // so we must fill the first half - dest = (int16_t *)i2s2_tx_buffer; + dest = (int32_t *)&i2s2_tx_buffer[0]; } blockL = AudioOutputI2S2_F32::block_left_1st; @@ -141,52 +122,45 @@ void AudioOutputI2S2_F32::isr(void) offsetL = AudioOutputI2S2_F32::block_left_offset; offsetR = AudioOutputI2S2_F32::block_right_offset; - int16_t *d = dest; + int32_t *d = dest; if (blockL && blockR) { - // memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR); - // memcpy_tointerleaveLRwLen(dest, blockL->data + offsetL, blockR->data + offsetR, audio_block_samples/2); float32_t *pL = blockL->data + offsetL; float32_t *pR = blockR->data + offsetR; for (int i = 0; i < audio_block_samples / 2; i++) { - *d++ = (int16_t)*pL++; - *d++ = (int16_t)*pR++; // interleave - //*d++ = 0; - //*d++ = 0; + *d++ = (int32_t)*pL++; + *d++ = (int32_t)*pR++; // interleave } offsetL += audio_block_samples / 2; offsetR += audio_block_samples / 2; } else if (blockL) { - // memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR); float32_t *pL = blockL->data + offsetL; - for (int i = 0; i < audio_block_samples / 2 * 2; i += 2) + for (int i = 0; i < audio_block_samples; i += 2) { - *(d + i) = (int16_t)*pL++; - } // interleave + *(d + i) = (int32_t)*pL++; + } offsetL += audio_block_samples / 2; } else if (blockR) { float32_t *pR = blockR->data + offsetR; - for (int i = 0; i < audio_block_samples / 2 * 2; i += 2) + for (int i = 0; i < audio_block_samples; i += 2) { - *(d + i) = (int16_t)*pR++; - } // interleave + *(d + i) = (int32_t)*pR++; + } offsetR += audio_block_samples / 2; } else { - // memset(dest,0,AUDIO_BLOCK_SAMPLES * 2); - memset(dest, 0, audio_block_samples * 2); + memset(dest, 0, audio_block_samples * 4); return; } arm_dcache_flush_delete(dest, sizeof(i2s2_tx_buffer) / 2); - // if (offsetL < AUDIO_BLOCK_SAMPLES) { //orig Teensy Audio if (offsetL < (uint16_t)audio_block_samples) { AudioOutputI2S2_F32::block_left_offset = offsetL; @@ -198,7 +172,6 @@ void AudioOutputI2S2_F32::isr(void) AudioOutputI2S2_F32::block_left_1st = AudioOutputI2S2_F32::block_left_2nd; AudioOutputI2S2_F32::block_left_2nd = NULL; } - // if (offsetR < AUDIO_BLOCK_SAMPLES) { //orig Teensy Audio if (offsetR < (uint16_t)audio_block_samples) { AudioOutputI2S2_F32::block_right_offset = offsetR; @@ -210,59 +183,24 @@ void AudioOutputI2S2_F32::isr(void) AudioOutputI2S2_F32::block_right_1st = AudioOutputI2S2_F32::block_right_2nd; AudioOutputI2S2_F32::block_right_2nd = NULL; } -#endif -} - -#define F32_TO_I16_NORM_FACTOR (32767) // which is 2^15-1 -void AudioOutputI2S2_F32::scale_f32_to_i16(float32_t *p_f32, float32_t *p_i16, int len) -{ - for (int i = 0; i < len; i++) - { - *p_i16++ = max(-F32_TO_I16_NORM_FACTOR, min(F32_TO_I16_NORM_FACTOR, (*p_f32++) * F32_TO_I16_NORM_FACTOR)); - } -} -#define F32_TO_I24_NORM_FACTOR (8388607) // which is 2^23-1 -void AudioOutputI2S2_F32::scale_f32_to_i24(float32_t *p_f32, float32_t *p_i24, int len) -{ - for (int i = 0; i < len; i++) - { - *p_i24++ = max(-F32_TO_I24_NORM_FACTOR, min(F32_TO_I24_NORM_FACTOR, (*p_f32++) * F32_TO_I24_NORM_FACTOR)); - } -} -#define F32_TO_I32_NORM_FACTOR (2147483647) // which is 2^31-1 -// define F32_TO_I32_NORM_FACTOR (8388607) //which is 2^23-1 -void AudioOutputI2S2_F32::scale_f32_to_i32(float32_t *p_f32, float32_t *p_i32, int len) -{ - for (int i = 0; i < len; i++) - { - *p_i32++ = max(-F32_TO_I32_NORM_FACTOR, min(F32_TO_I32_NORM_FACTOR, (*p_f32++) * F32_TO_I32_NORM_FACTOR)); - } - // for (int i=0; idata[30]); - // count=0; - // } + + scale_float_to_int32range(block_f32->data, block_f32_scaled->data, audio_block_samples); // now process the data blocks __disable_irq(); @@ -326,10 +250,8 @@ void AudioOutputI2S2_F32::update(void) block_f32 = receiveReadOnly_f32(1); // input 1 = right channel if (block_f32) { - // scale F32 to Int32 - // block_f32_scaled = AudioStream_F32::allocate_f32(); - // scale_f32_to_i32(block_f32->data, block_f32_scaled->data, audio_block_samples); - scale_f32_to_i16(block_f32->data, block_f32_scaled->data, audio_block_samples); + arm_scale_f32(block_f32->data, (float32_t)F32_TO_I32_NORM_FACTOR, + block_f32_scaled->data,audio_block_samples); __disable_irq(); if (block_right_1st == NULL) @@ -361,8 +283,6 @@ void AudioOutputI2S2_F32::update(void) AudioStream_F32::release(block_f32_scaled); } } - - void AudioOutputI2S2_F32::config_i2s(void) { config_i2s(false, AudioOutputI2S2_F32::sample_rate_Hz); } void AudioOutputI2S2_F32::config_i2s(bool transferUsing32bit) { config_i2s(transferUsing32bit, AudioOutputI2S2_F32::sample_rate_Hz); } void AudioOutputI2S2_F32::config_i2s(float fs_Hz) { config_i2s(false, fs_Hz); } @@ -373,12 +293,9 @@ void AudioOutputI2S2_F32::config_i2s(bool transferUsing32bit, float fs_Hz) CCM_CCGR5 |= CCM_CCGR5_SAI2(CCM_CCGR_ON); // if either transmitter or receiver is enabled, do nothing - if (I2S2_TCSR & I2S_TCSR_TE) - return; - if (I2S2_RCSR & I2S_RCSR_RE) - return; + if (I2S2_TCSR & I2S_TCSR_TE) return; + if (I2S2_RCSR & I2S_RCSR_RE) return; // PLL: - // int fs = AUDIO_SAMPLE_RATE_EXACT; //original from Teensy Audio Library int fs = fs_Hz; // PLL between 27*24 = 648MHz und 54*24=1296MHz @@ -404,7 +321,6 @@ void AudioOutputI2S2_F32::config_i2s(bool transferUsing32bit, float fs_Hz) int tsync = 1; I2S2_TMR = 0; - // I2S1_TCSR = (1<<25); //Reset I2S2_TCR1 = I2S_TCR1_RFW(1); I2S2_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP // sync=0; tx is async; | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1)); @@ -413,7 +329,6 @@ void AudioOutputI2S2_F32::config_i2s(bool transferUsing32bit, float fs_Hz) I2S2_TCR5 = I2S_TCR5_WNW((32 - 1)) | I2S_TCR5_W0W((32 - 1)) | I2S_TCR5_FBT((32 - 1)); I2S2_RMR = 0; - // I2S1_RCSR = (1<<25); //Reset I2S2_RCR1 = I2S_RCR1_RFW(1); I2S2_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP // sync=0; rx is async; | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1)); @@ -442,17 +357,17 @@ void AudioOutputI2S2slave_F32::begin(void) #if defined(__IMXRT1062__) CORE_PIN7_CONFIG = 3; // 1:TX_DATA0 dma.TCD->SADDR = i2s2_tx_buffer; - dma.TCD->SOFF = 2; - dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); - dma.TCD->NBYTES_MLNO = 2; + dma.TCD->SOFF = 4; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLNO = 4; dma.TCD->SLAST = -sizeof(i2s2_tx_buffer); // dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR1 + 2); dma.TCD->DOFF = 0; - dma.TCD->CITER_ELINKNO = sizeof(i2s2_tx_buffer) / 2; + dma.TCD->CITER_ELINKNO = sizeof(i2s2_tx_buffer) / 4; dma.TCD->DLASTSGA = 0; - dma.TCD->BITER_ELINKNO = sizeof(i2s2_tx_buffer) / 2; + dma.TCD->BITER_ELINKNO = sizeof(i2s2_tx_buffer) / 4; // dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_TX); - dma.TCD->DADDR = (void *)((uint32_t)&I2S2_TDR0 + 2); + dma.TCD->DADDR = (void *)((uint32_t)&I2S2_TDR0); dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_TX); dma.enable(); diff --git a/src/output_i2s2_F32.h b/src/output_i2s2_F32.h index 6301f1e..e655f28 100644 --- a/src/output_i2s2_F32.h +++ b/src/output_i2s2_F32.h @@ -58,22 +58,11 @@ public: virtual void update(void); void begin(void); void begin(bool); - void sub_begin_i32(void); - void sub_begin_i16(void); friend class AudioInputI2S2_F32; - //friend class AudioInputI2S_F32; - #if defined(__IMXRT1062__) friend class AudioOutputI2SQuad_F32; friend class AudioInputI2SQuad_F32; - - #endif - - static void scale_f32_to_i16( float32_t *p_f32, float32_t *p_i16, int len) ; - static void scale_f32_to_i24( float32_t *p_f32, float32_t *p_i16, int len) ; - static void scale_f32_to_i32( float32_t *p_f32, float32_t *p_i32, int len) ; - - static float setI2SFreq_T3(const float); // I2S clock for T3,x + bool get_update_responsibility() { return update_responsibility;} protected: AudioOutputI2S2_F32(int dummy): AudioStream_F32(2, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !! static void config_i2s(void); @@ -85,8 +74,6 @@ protected: static audio_block_f32_t *block_right_1st; static bool update_responsibility; static DMAChannel dma; - static void isr_16(void); - static void isr_32(void); static void isr(void); private: static audio_block_f32_t *block_left_2nd; @@ -98,14 +85,13 @@ private: static int audio_block_samples; volatile uint8_t enabled = 1; }; - + class AudioOutputI2S2slave_F32 : public AudioOutputI2S2_F32 { public: AudioOutputI2S2slave_F32(void) : AudioOutputI2S2_F32(0) { begin(); } ; void begin(void); friend class AudioInputI2S2slave_F32; - friend void dma_ch0_isr(void); protected: static void config_i2s(void); }; diff --git a/src/switch_selectorStereo_F32.h b/src/switch_selectorStereo_F32.h new file mode 100644 index 0000000..aeffc17 --- /dev/null +++ b/src/switch_selectorStereo_F32.h @@ -0,0 +1,93 @@ +/** + * @file switch_selectorStereo_F32.h + * @author Piotr Zapart + * @brief Signal selector for routing mono to stereo + * @version 0.1 + * @date 2024-03-21 + * + * @copyright Copyright (c) 2024 www.hexefx.com + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * You should have received a copy of the GNU General Public License along with this program. + * If not, see ." + */ + +#ifndef _SWITCH_SELECTORSTEREO_F32_H_ +#define _SWITCH_SELECTORSTEREO_F32_H_ +#include +#include + +class AudioSwitchSelectorStereo : public AudioStream_F32 +{ +public: + AudioSwitchSelectorStereo(void) : AudioStream_F32(2, inputQueueArray){}; + typedef enum + { + SIGNAL_SELECT_LR, // default stereo operation + SIGNAL_SELECT_L, // left input as mono input + SIGNAL_SELECT_R // right input as mono input + }selector_mode_t; + + selector_mode_t setMode(selector_mode_t m) + { + if (m <= 2) + { + __disable_irq(); + mode = m; + __enable_irq(); + } + return mode; + } + selector_mode_t getMode() {return mode;}; + void update() + { + audio_block_f32_t *blockL, *blockR, *outL, *outR; + blockL = AudioStream_F32::receiveWritable_f32(0); + blockR = AudioStream_F32::receiveWritable_f32(1); + + + if (!blockL || !blockR) + { + if (blockL) AudioStream_F32::release(blockL); + if (blockR) AudioStream_F32::release(blockR); + + + + return; + } + switch(mode) + { + case SIGNAL_SELECT_LR: + outL = blockL; + outR = blockR; + break; + case SIGNAL_SELECT_L: + outL = blockL; + outR = blockL; + break; + case SIGNAL_SELECT_R: + outL = blockR; + outR = blockR; + break; + default: + outL = blockL; + outR = blockR; + break; + } + AudioStream_F32::transmit(outL, 0); + AudioStream_F32::transmit(outR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + } + +private: + audio_block_f32_t *inputQueueArray[2]; + selector_mode_t mode = SIGNAL_SELECT_LR; +}; + +#endif // _SWITCH_SELECTORSTEREO_F32_H_ diff --git a/src/wavetables.c b/src/wavetables.c index a0d545a..60f4cda 100644 --- a/src/wavetables.c +++ b/src/wavetables.c @@ -81,4 +81,3 @@ const float music_intevals[37] = 3.174802f, 3.363586f, 3.563595f, 3.775497f, 4.000000f }; -