diff --git a/src/HxFx_memcpy.h b/src/HxFx_memcpy.h new file mode 100644 index 0000000..8668c88 --- /dev/null +++ b/src/HxFx_memcpy.h @@ -0,0 +1,41 @@ +#ifndef _HX_MEMCPY_H +#define _HX_MEMCPY_H + +/** + * @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 // _HX_MEMCPY_H diff --git a/src/basic_allpass.h b/src/basic_allpass.h new file mode 100644 index 0000000..805a0f8 --- /dev/null +++ b/src/basic_allpass.h @@ -0,0 +1,76 @@ +/** + * @file basic_allpass.h + * @author Piotr Zapart + * @brief basic allpass filter + * @version 1.0 + * @date 2024-01-09 + * + * @copyright Copyright (c) 2024 + * + */ +#ifndef _FILTER_ALLPASS_H_ +#define _FILTER_ALLPASS_H_ + +#include "Arduino.h" +template +class AudioFilterAllpass +{ +public: + ~AudioFilterAllpass() + { + free(bf); + } + /** + * @brief Allocate the filter buffer in RAM + * set the pointer to the allpass coeff + * + * @param coeffPtr pointer to the allpas coeff variable + */ + bool init(float* coeffPtr) + { + bf = (float *)malloc(N*sizeof(float)); // allocate buffer + if (!bf) return false; + kPtr = coeffPtr; + reset(); + return true; + } + /** + * @brief zero the allpass buffer + * + */ + void reset() + { + memset(bf, 0, N*sizeof(float)); + idx = 0; + } + /** + * @brief process new sample + * + * @param in input sample + * @return float output sample + */ + float process(float in) + { + float out = bf[idx] + (*kPtr) * in; + bf[idx] = in - (*kPtr) * out; + if (++idx >= N) idx = 0; + return out; + } + /** + * @brief Set new coeff pointer + * + * @param coeffPtr + */ + void coeff(float* coeffPtr) + { + kPtr = coeffPtr; + } +private: + float *kPtr; + float *bf; + uint32_t idx; + const uint32_t len = N*sizeof(float); +}; + + +#endif // _FILTER_ALLPASS_H_ diff --git a/src/basic_components.h b/src/basic_components.h new file mode 100644 index 0000000..7392eac --- /dev/null +++ b/src/basic_components.h @@ -0,0 +1,11 @@ +#ifndef _BASIC_COMPONENTS_H_ +#define _BASIC_COMPONENTS_H_ + +#include "basic_allpass.h" +#include "basic_delay.h" +#include "basic_lfo.h" +#include "basic_shelvFilter.h" +#include "basic_pitch.h" + + +#endif // _BASIC_COMPONENTS_H_ diff --git a/src/basic_delay.h b/src/basic_delay.h new file mode 100644 index 0000000..10242d3 --- /dev/null +++ b/src/basic_delay.h @@ -0,0 +1,88 @@ +/** + * @file basic_delay.h + * @author Piotr Zapart www.hexefx.com + * @brief basic delay line + * @version 1.0 + * @date 2024-01-09 + * + * @copyright Copyright (c) 2024 + * + */ +#ifndef _BASIC_DELAY_H_ +#define _BASIC_DELAY_H_ + +#include "Arduino.h" + + +/** + * @brief Basic delay line with buffer placed in PSRAM + * + * @tparam N delay length in samples (float) + */ +template +class AudioBasicDelay +{ +public: + ~AudioBasicDelay() + { + free(bf); + } + bool init() + { + bf = (float *)malloc(N*sizeof(float)); // allocate buffer + if (!bf) return false; + idx = 0; + reset(); + return true; + } + void reset() + { + memset(bf, 0, N*sizeof(float)); + } + /** + * @brief get the tap from the delay buffer + * + * @param offset delay time + * @return float + */ + float getTap(uint32_t offset, float frac=0.0f) + { + int32_t read_idx, read_idx_next; + read_idx = idx - offset; + if (read_idx < 0) read_idx += N; + if (frac == 0.0f) return bf[read_idx]; + read_idx_next = read_idx - 1; + if (read_idx_next < 0) read_idx_next += N; + return (bf[read_idx]*(1.0f-frac) + bf[read_idx_next]*frac); + //return bf[read_idx]; + } + /** + * @brief read last sample and write a new one + * + * @param newSample new sample written to the start address + * @return float lase sample read from the end of the buffer + */ + float process(float newSample) + { + float out = bf[idx]; + bf[idx] = newSample; + + return out; + } + void write_toOffset(float newSample, uint32_t offset) + { + int32_t write_idx; + write_idx = idx - offset; + if (write_idx < 0) write_idx += N; + bf[write_idx] = newSample; + } + void updateIndex() + { + if (++idx >= N) idx = 0; + } +private: + float *bf; + int32_t idx; +}; + +#endif // _BASIC_DELAY_H_ diff --git a/src/basic_lfo.h b/src/basic_lfo.h new file mode 100644 index 0000000..1e0db8f --- /dev/null +++ b/src/basic_lfo.h @@ -0,0 +1,71 @@ +#ifndef _BASIC_LFO_H_ +#define _BASIC_LFO_H_ + +#include +#include "AudioStream_F32.h" + +#define BASIC_LFO_PHASE_0 (0) +#define BASIC_LFO_PHASE_90 (64) +#define BASIC_LFO_PHASE_180 (128) + +extern "C" { +extern const int16_t AudioWaveformSine[257]; +} + +/* + * @brief Basic sin LFO with float oputput + * + */ +class AudioBasicLfo +{ +public: + AudioBasicLfo(float rateHz, uint32_t ampl) + { + acc = 0; + divider = (0x7FFF + (ampl>>1)) / ampl; + adder = (uint32_t)(rateHz * rate_mult); + + } + void update() + { + acc += adder; // update the phase acc + } + /** + * @brief LFO output split in two parts + * + * @param phase8bit 0-360deg scaled to 8bit value - phase shift (ie 64=90deg) + * @param intOffset pointer top integer value used as address offset + * @param fractOffset pointer to fractional part used for interpolation + */ + void get(uint8_t phase8bit, uint32_t *intOffset, float *fractOffset) + { + uint32_t idx; + uint32_t y0, y1; + uint64_t y; + float intOff; + idx = ((acc >> 24) + phase8bit) & 0xFF; + y0 = AudioWaveformSine[idx] + 32767; + y1 = AudioWaveformSine[idx+1] + 32767; + idx = acc & 0x00FFFFFF; // lower 24 bit = fractional part + y = (int64_t)y0 * (0x00FFFFFF - idx); + y += (int64_t)y1 * idx; + y0 = (int32_t) (y >> 24); // 16bit output + *fractOffset = modff((float)y0 / (float)divider, &intOff); + *intOffset = (uint32_t)intOff; + } + void setRate(float rateHz) + { + adder = (uint32_t)(rateHz * rate_mult); + } + void setDepth(uint32_t ampl) + { + divider = (0x7FFF + (ampl>>1)) / ampl; + } +private: + uint32_t acc; + uint32_t adder; + int32_t divider = 1; + const uint32_t rate_mult = 4294967295.0f / AUDIO_SAMPLE_RATE_EXACT; +}; + +#endif // _BASIC_LFO_H_ diff --git a/src/basic_pitch.h b/src/basic_pitch.h new file mode 100644 index 0000000..3cf806a --- /dev/null +++ b/src/basic_pitch.h @@ -0,0 +1,121 @@ +#ifndef _BASIC_PITCH_H_ +#define _BASIC_PITCH_H_ + +#include +#include "Audio.h" + +#define BASIC_PITCH_BUF_BITS (12) +#define BASIC_PITCH_BUF_SIZE (1<>1) +#define BASIC_PITCH_XFADE_MASK (BASIC_PITCH_XFADE_LEN-1) + +extern "C" { +extern const float AudioWaveformFader_f32[]; // crossfade waveform +extern const float music_intevals[]; // semitone intervals -1oct to +2oct +} + +class AudioBasicPitch +{ +public: + bool init() + { + outFilter.init(hp_f, (float *)&hp_gain, lp_f, &lp_gain); + bf = (float *)malloc(BASIC_PITCH_BUF_SIZE*sizeof(float)); // allocate buffer + if (!bf) return false; + reset(); + return true; + } + + void setPitch(float ratio) + { + readAdder = (float)pitchDelta0 * ratio; + } + void setPitchSemintone(int8_t s) + { + s = constrain(s, -12, +24); // limit to the predefined range + setPitch(music_intevals[s + 12]); + } + void setTone(float t) + { + //lp_f = constrain(t, 0.01f, 1.0f); + lp_gain = constrain(t, 0.0f, 1.0f); + } + + float process(float newSample) + { + uint32_t idx1, idx2; + uint32_t delta, delta_acc; + float k_frac, delta_frac, s_n, s_half, xf0, xf1; + + bf[writeAddr] = newSample; // write new sample + readAddr = readAddr + readAdder; // update read pointer, readAdder controls the pitch + // bypass mode is at mix = 0 or if no pitch change + if (mix == 0.0f || readAdder == pitchDelta0) + { + writeAddr = (writeAddr + 1) & BASIC_PITCH_BUF_MASK; + return newSample; + } + // sample end + idx1 = (readAddr >> (32-BASIC_PITCH_BUF_BITS)) & BASIC_PITCH_BUF_MASK; // index of the last sample + k_frac = (float)(readAddr & BASIC_PITCH_BUF_FRAC_MASK) / (float)BASIC_PITCH_BUF_FRAC_MASK; // fractional part + s_n = bf[idx1] * (1.0f-k_frac); + s_n += bf[(idx1 + 1) & BASIC_PITCH_BUF_MASK] * k_frac; // interpolated sample + // sample half + idx2 = ((readAddr + 0x80000000) >> (32-BASIC_PITCH_BUF_BITS)) & BASIC_PITCH_BUF_MASK; + k_frac = (float)((readAddr+0x80000000) & BASIC_PITCH_BUF_FRAC_MASK) / (float)BASIC_PITCH_BUF_FRAC_MASK; + s_half = bf[idx2] * (1.0f - k_frac); + s_half += bf[(idx2 + 1) & BASIC_PITCH_BUF_MASK] * k_frac; + + delta_acc = readAddr - (writeAddr<<(32-BASIC_PITCH_BUF_BITS)); // distance between the write and read pointer + + delta = (delta_acc >> (32-9)) & 0x1FF; // 9 bit value = 2x fade table length (fade in + fade out) + delta_frac = (float)(delta_acc & ((1<<23)-1)) / (float)((1<<23)-1); // fractional part for the xfade curve + idx2 = delta&0xFF; + xf0 = AudioWaveformFader_f32[idx2]; + xf1 = AudioWaveformFader_f32[idx2+1]; + k_frac = xf0 * (1.0f-delta_frac) + xf1 * delta_frac; // interpolated smooth crossfade coeff. + + if (delta > 0xFF) k_frac = 1.0f-k_frac; // invert the curve for the fade out part + + s_n = s_n * k_frac + s_half * (1.0f - k_frac); // crossfade the last and mid sample + + writeAddr = (writeAddr + 1) & BASIC_PITCH_BUF_MASK; // update the write pointer + s_n = outFilter.process(s_n); // apply output lowpass + return (s_n * mix + newSample * (1.0f-mix)); // do dry/wet mix + } + void setMix(float mixRatio) + { + mix = constrain(mixRatio, 0.0f, 1.0f); + } + void reset() + { + memset(bf, 0, BASIC_PITCH_BUF_SIZE*sizeof(float)); + + readAddr = 0; + writeAddr = 0; + readAdder = pitchDelta0; + mix = 1.0f; + } +private: + float *bf; + float mix; + uint32_t readAddr; + uint32_t readAdder; + uint16_t writeAddr; + static const uint32_t pitchDelta0 = BASIC_PITCH_BUF_FRAC_MASK+1; + + AudioFilterShelvingLPHP outFilter; + static constexpr float hp_f = 0.003f; + const float hp_gain = 0.0f; + static constexpr float lp_f = 0.26f; + float lp_gain = 1.0f; +}; + + +#endif // _BASIC_PITCH_H_ diff --git a/src/basic_shelvFilter.h b/src/basic_shelvFilter.h new file mode 100644 index 0000000..d6fb90d --- /dev/null +++ b/src/basic_shelvFilter.h @@ -0,0 +1,97 @@ +/** + * @file basic_shelvFilter.h + * @author Piotr Zapart www.hexefx.com + * @brief basic hp/lp filter class + * @version 1.0 + * @date 2024-01-09 + * + * @copyright Copyright (c) 2024 + * + */ +#ifndef _BASIC_SHELVFILTER_H_ +#define _BASIC_SHELVFILTER_H_ + +#include + +class AudioFilterShelvingLPHP +{ +public: + void init(float hp_freq, float *hp_damp, float lp_freq, float *lp_damp) + { + hidampPtr = lp_damp; + hidamp = *hidampPtr; + hp_f = hp_freq; + lodampPtr = hp_damp; + lodamp = *lodampPtr; + lp_f = lp_freq; + lpreg = 0.0f; + hpreg = 0.0f; + } + float process(float input) + { + float tmp1, tmp2; + // smoothly update params + if (hidamp < (*hidampPtr)) + { + hidamp += upd_step; + if (hidamp >(*hidampPtr)) hidamp = *hidampPtr; + } + if (hidamp > (*hidampPtr)) + { + hidamp -= upd_step; + if (hidamp < (*hidampPtr)) hidamp = *hidampPtr; + } + if (lodamp < (*lodampPtr)) + { + lodamp += upd_step; + if (lodamp >(*lodampPtr)) lodamp = *lodampPtr; + } + if (lodamp > (*lodampPtr)) + { + lodamp -= upd_step; + if (lodamp < (*lodampPtr)) lodamp = *lodampPtr; + } + + tmp1 = input - lpreg; + lpreg += tmp1 * lp_f; + tmp2 = input - lpreg; + tmp1 = lpreg - hpreg; + hpreg += tmp1 * hp_f; + return (lpreg + hidamp*tmp2 + lodamp * hpreg); + } +private: + float lpreg; + float hpreg; + float *lodampPtr; + float *hidampPtr; + float hidamp; + float lodamp; + float hp_f; + float lp_f; + static constexpr float upd_step = 0.02f; +}; + + +class AudioFilterLP +{ +public: + void init(float *lp_freq) + { + lp_fPtr = lp_freq; + lpreg = 0.0f; + } + float process(float input) + { + float tmp; + tmp = input - lpreg; + lpreg += (*lp_fPtr) *tmp; + return lpreg; + } +private: + float lpreg; + float *lp_fPtr; +}; + + + +#endif // _BASIC_SHELVFILTER_H_ diff --git a/src/effect_infphaser_F32.cpp b/src/effect_infphaser_F32.cpp new file mode 100644 index 0000000..ac77e75 --- /dev/null +++ b/src/effect_infphaser_F32.cpp @@ -0,0 +1,118 @@ +/* Mono Shephard/Barberpole Phaser/Vibrato effect for Teensy Audio library + * + * Author: Piotr Zapart + * www.hexefx.com + * + * Copyright (c) 2021 by Piotr Zapart + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include "effect_infphaser_F32.h" + +// ---------------------------- INFINITE PHASER MODULATION ----------------------- +#define INF_PHASER_STEP (0x100000000u / INFINITE_PHASER_PATHS) + +AudioEffectInfinitePhaser_F32::AudioEffectInfinitePhaser_F32() : AudioStream_F32(1, inputQueueArray_f32) +{ + memset(allpass_x, 0, INFINITE_PHASER_STAGES * INFINITE_PHASER_PATHS * sizeof(float32_t)); + memset(allpass_y, 0, INFINITE_PHASER_STAGES * INFINITE_PHASER_PATHS * sizeof(float32_t)); + bps = false; + lfo_top = 1.0f; + lfo_btm = 0.0f; + lfo_phase_acc = 0; + lfo_add = 0; + feedb = 0.5f; // effect is hard noticable with low feedback settings, hence the range is limited to 0.5-0.999 + mix_ratio = 0.5f; // start with classic phaser sound + stg = INFINITE_PHASER_STAGES; +} +AudioEffectInfinitePhaser_F32::~AudioEffectInfinitePhaser_F32() +{ +} + +void AudioEffectInfinitePhaser_F32::update() +{ + +#if defined(__ARM_ARCH_7EM__) + audio_block_f32_t *blockIn; + uint16_t i = 0; + float32_t modSig; + uint32_t phaseAcc = lfo_phase_acc; + int32_t phaseAdd = lfo_add; + float32_t top = lfo_top; + float32_t btm = lfo_btm; + uint32_t phase_acc_local; + uint32_t y0, y1; + float32_t inSig, drySig, wetSig; + float32_t fdb = feedb; + float32_t ampl; + + blockIn = AudioStream_F32::receiveWritable_f32(0); // audio data + + if (!blockIn) + { + return; + } + if (bps) + { + AudioStream_F32::transmit(blockIn); + AudioStream_F32::release(blockIn); + return; + } + + for (i=0; i < blockIn->length; i++) + { + wetSig = 0.0f; + drySig = blockIn->data[i] * (1.0f - fdb*0.25f); // attenuate the input if using feedback + + y1 = INFINITE_PHASER_PATHS; + while (y1) + { + y1--; + phase_acc_local = phaseAcc + y1*INF_PHASER_STEP; + modSig = 1.0f - ((float32_t)phase_acc_local / 4294967295.0f); + ampl = modSig * 2.0f; + + if (ampl > 1.0f) ampl = -2.0f * modSig + 2.0f; + modSig = modSig*modSig * abs(top - btm) + min(top, btm); + + inSig = drySig + last_sample[y1] * fdb; + y0 = stg; + while (y0) // process allpass filters in pairs + { + y0--; + allpass_y[y1][y0] = modSig * (allpass_y[y1][y0] + inSig) - allpass_x[y1][y0]; + allpass_x[y1][y0] = inSig; + y0--; + allpass_y[y1][y0] = modSig * (allpass_y[y1][y0] + allpass_y[y1][y0+1]) - allpass_x[y1][y0]; + allpass_x[y1][y0] = allpass_y[y1][y0+1]; + inSig = allpass_y[y1][y0]; + } + last_sample[y1] = inSig; + wetSig += ((drySig * (1.0f - mix_ratio) + inSig * mix_ratio)* ampl)/2.0f; + } + blockIn->data[i] = wetSig; + + phaseAcc += phaseAdd; + } + lfo_phase_acc = phaseAcc; + AudioStream_F32::transmit(blockIn); + AudioStream_F32::release(blockIn); +#endif +} + diff --git a/src/effect_infphaser_F32.h b/src/effect_infphaser_F32.h new file mode 100644 index 0000000..30fccb8 --- /dev/null +++ b/src/effect_infphaser_F32.h @@ -0,0 +1,180 @@ +/* Mono Shephard/Barberpole Phaser/Vibrato effect for Teensy Audio library + * + * Author: Piotr Zapart + * www.hexefx.com + * + * Copyright (c) 2021 by Piotr Zapart + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _EFFECT_INFPHASER_F32_H +#define _EFFECT_INFPHASER_F32_H + +#include +#include "Audio.h" +#include "AudioStream_F32.h" +#include "arm_math.h" + +// ################ SHEPARD/BARBERPOLE INFINITE PHASER ################ +#define INFINITE_PHASER_STAGES 6 +#define INFINITE_PHASER_PATHS 6 // 6 parallel paths + +#define INFINITE_PHASER_MAX_LFO_HZ (0.25f) // maximum LFO rate range + +class AudioEffectInfinitePhaser_F32 : public AudioStream_F32 +{ +public: + AudioEffectInfinitePhaser_F32(); + ~AudioEffectInfinitePhaser_F32(); + virtual void update(); +/** + * @brief Scale and offset the modulation signal. + * LFO will oscillate between these two max and min values. + * + * @param top top level of the LFO + * @param bottom bottom level of the LFO + */ + void depth(float32_t top, float32_t bottom) + { + float32_t a, b; + a = constrain(top, 0.0f, 1.0f); + b = constrain(bottom, 0.0f, 1.0f); + __disable_irq(); + lfo_top = a; + lfo_btm = b; + __enable_irq(); + } + void depth_top(float32_t top) + { + float32_t a = constrain(top, 0.0f, 1.0f); + __disable_irq(); + lfo_top = a; + __enable_irq(); + } + void depth_btm(float32_t btm) + { + float32_t a = constrain(btm, 0.0f, 1.0f); + __disable_irq(); + lfo_btm = a; + __enable_irq(); + } + /** + * @brief Controls the internal LFO. + * Use this function to update all lfo parameteres at once + * + * @param rate scaled lfo frequency -1.0f to 1.0f, use 0.0f for manual phaser control + * @param top lfo top level, range 0.0f to 1.0f + * @param btm lfo bottm level, range 0.0f to 1.0f + */ + void lfo(float32_t rate, float32_t top, float32_t btm) + { + int32_t add; + if (rate < 0.0f) rate = rate*rate*(-1.0f); + else rate = rate*rate; + top = constrain(top, 0.0f, 1.0f); + btm = constrain(btm, 0.0f, 1.0f); + add = rate * (4294967296.0 / AUDIO_SAMPLE_RATE_EXACT); + __disable_irq(); + lfo_top = top; + lfo_btm = btm; + lfo_add = add; + __enable_irq(); + } + /** + * @brief Set the rate of the internal LFO + * + * @param rate lfo frequency, use 0.0f for manual phaser control + * Range -1.0f to 1.0f for reverse and forward modulation + */ + void lfo_rate(float32_t rate) + { + if (rate < 0.0f) rate = rate*rate*(-1.0f); + else rate = rate*rate; + int32_t add; + rate = map(rate, -1.0f, 1.0f, -INFINITE_PHASER_MAX_LFO_HZ, INFINITE_PHASER_MAX_LFO_HZ); + add = rate * (4294967296.0f / AUDIO_SAMPLE_RATE_EXACT); + __disable_irq(); + lfo_add = add; + __enable_irq(); + } + /** + * @brief Controls the feedback parameter + * + * @param fdb feedback value in range 0.0f to 1.0f + */ + void feedback(float32_t fdb) + { + fdb = map(fdb, 0.0f, 1.0f, 0.5f, 0.999f); + __disable_irq(); + feedb = fdb; + __enable_irq(); + } + /** + * @brief Dry / Wet mixer ratio. Classic Phaser sound uses 0.5f for 50% dry and 50%Wet + * 1.0f will produce 100% wet signal craeting a vibrato effect + * + * @param ratio mixing ratio, range 0.0f (full dry) to 1.0f (full wet) + */ + void mix(float32_t ratio) + { + ratio = constrain(ratio, 0.0f, 1.0f); + __disable_irq(); + mix_ratio = ratio; + __enable_irq(); + } + /** + * @brief Sets the number of stages used in the phaser + * Allowed values are: 2, 4, 6 + * + * @param st number of stages, even value <= 6 + */ + void stages(uint8_t st) + { + if (st && st == ((st >> 1) << 1) && st <= INFINITE_PHASER_STAGES) // only 2, 4, 6, 8, 12 allowed + { + __disable_irq(); + stg = st; + __enable_irq(); + } + } + /** + * @brief Use to bypass the effect (true) + * + * @param state true = bypass on, false = phaser on + */ + void set_bypass(bool state) {bps = state;} + bool get_bypass(void) {return bps;} + bool tgl_bypass(void) {bps ^= 1; return bps;} +private: + uint8_t stg; // number of stages + bool bps; // bypass + audio_block_f32_t *inputQueueArray_f32[1]; + float32_t allpass_x[INFINITE_PHASER_PATHS][INFINITE_PHASER_STAGES]; // allpass inputs + float32_t allpass_y[INFINITE_PHASER_PATHS][INFINITE_PHASER_STAGES]; // allpass outputs + float32_t mix_ratio; // 0 = dry. 1.0 = wet + float32_t feedb; // feedback + float32_t last_sample[INFINITE_PHASER_PATHS]; + uint32_t lfo_phase_acc; // interfnal lfo + int32_t lfo_add; + float32_t lfo_top; + float32_t lfo_btm; +}; + +#endif // _EFFECT_INFPHASER_H diff --git a/src/effect_monoToStereo_F32.cpp b/src/effect_monoToStereo_F32.cpp new file mode 100644 index 0000000..52e404b --- /dev/null +++ b/src/effect_monoToStereo_F32.cpp @@ -0,0 +1,131 @@ +/* mono to stereo expander for Teensy 4 + * 32bit float version for OpenAudio_ArduinoLibrary: + * https://github.com/chipaudette/OpenAudio_ArduinoLibrary + * + * Author: Piotr Zapart + * www.hexefx.com + * + * Copyright (c) 2021 by Piotr Zapart + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "effect_monoToStereo_F32.h" + +const float32_t allpass_k_table[ALLP_NETWORK_LEN] = +{ + -0.9823311567306519f, -0.9838343858718872f, -0.9838343858718872f, + -0.9843953251838684f, -0.9843953251838684f, -0.9850407838821411f, + -0.9850407838821411f, -0.9856590032577515f, -0.9856590032577515f, + -0.9868955612182617f, -0.9868955612182617f, -0.9894036054611206f, + -0.9894036054611206f, -0.9902338981628418f, -0.9902338981628418f, + -0.9911531209945679f, -0.9911531209945679f, -0.9911531209945679f, + -0.9911531209945679f, -0.9928167462348938f, -0.9928167462348938f +}; + +AudioEffectMonoToStereo_F32::AudioEffectMonoToStereo_F32() : AudioStream_F32(1, inputQueueArray_f32) +{ + pancos = 1.0f; + pansin= 0.0f; + width = 0.0f; + bypass = false; +} +AudioEffectMonoToStereo_F32::~AudioEffectMonoToStereo_F32() +{ +} + +void AudioEffectMonoToStereo_F32::update() +{ +#if defined(__ARM_ARCH_7EM__) + + audio_block_f32_t *blockIn; + uint16_t i; + float32_t _width = width; + float32_t _pancos = pancos; + float32_t _pansin = pansin; + float32_t allp1Out, allp2Out, stereoL, stereoR; + + blockIn = AudioStream_F32::receiveReadOnly_f32(0); + if (!blockIn) return; + + audio_block_f32_t *blockOutL = AudioStream_F32::allocate_f32(); + audio_block_f32_t *blockOutR = AudioStream_F32::allocate_f32(); + if (!blockOutL || !blockOutR) + { + + if (blockOutL) + AudioStream_F32::release(blockOutL); + if (blockOutR) + AudioStream_F32::release(blockOutR); + return; + } + if (bypass) + { + AudioStream_F32::transmit(blockIn, 0); // transmit input on both + AudioStream_F32::transmit(blockIn, 1); // out channels + AudioStream_F32::release(blockIn); + AudioStream_F32::release(blockOutL); + AudioStream_F32::release(blockOutR); + return; + } + for (i = 0; i < blockIn->length; i++) + { + allp1Out = do_allp_netw(blockIn->data[i], allpass_netw_1x, allpass_netw_1y); + allp2Out = do_allp_netw(allp1Out, allpass_netw_2x, allpass_netw_2y); + stereoL = blockIn->data[i] * _width + allp1Out; + stereoR = allp1Out - (allp2Out * _width); + blockOutL->data[i] = (stereoL * _pancos) + (stereoR * _pansin); + blockOutR->data[i] = (stereoR * _pancos) - (stereoL * _pansin); + } + AudioStream_F32::transmit(blockOutL, 0); + AudioStream_F32::transmit(blockOutR, 1); + AudioStream_F32::release(blockOutL); + AudioStream_F32::release(blockOutR); + AudioStream_F32::release(blockIn); + +#endif +} + + +// y[n] = c*x[n] + x[n-1] - c*y[n-1] +// y[n] = c*(x[n] - y[n-1]) + x[n-1] +// c = (tan(pi*fc/fs)-1) / (tan(pi*fc/fs)+1) +float32_t AudioEffectMonoToStereo_F32::do_allp_netw(float32_t inSig, float32_t *x, float32_t *y) +{ + uint32_t stg = ALLP_NETWORK_LEN; + while (stg) + { + stg--; + y[stg] = allpass_k_table[stg] * (inSig - y[stg]) + x[stg]; + x[stg] = inSig; + inSig = y[stg]; + stg--; + y[stg] = allpass_k_table[stg] * (inSig - y[stg]) + x[stg]; + x[stg] = inSig; + inSig = y[stg]; + stg--; + y[stg] = allpass_k_table[stg] * (inSig - y[stg]) + x[stg]; + x[stg] = inSig; + inSig = y[stg]; + } + return y[0]; +} \ No newline at end of file diff --git a/src/effect_monoToStereo_F32.h b/src/effect_monoToStereo_F32.h new file mode 100644 index 0000000..91337bd --- /dev/null +++ b/src/effect_monoToStereo_F32.h @@ -0,0 +1,83 @@ +/* mono to stereo expander for Teensy 4 + * 32bit float version for OpenAudio_ArduinoLibrary: + * https://github.com/chipaudette/OpenAudio_ArduinoLibrary + * + * Author: Piotr Zapart + * www.hexefx.com + * + * Copyright (c) 2021 by Piotr Zapart + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef _EFFECT_MONOTOSTEREO_F32_H +#define _EFFECT_MONOTOSTEREO_F32_H + +#include +#include "AudioStream_F32.h" +#include "arm_math.h" + +#define ALLP_NETWORK_LEN 21 + +class AudioEffectMonoToStereo_F32 : public AudioStream_F32 +{ +public: + AudioEffectMonoToStereo_F32(); + ~AudioEffectMonoToStereo_F32(); + virtual void update(); + void setSpread(float32_t val) + { + val = constrain(val, 0.0f, 1.0f); + __disable_irq(); + width = val; + __enable_irq(); + } + void setPan(float32_t val) + { + float32_t a, b; + val = constrain(val, -1.0f, 1.0f); + a = map(val, -1.0f, 1.0f, -0.707f, 0.707f); + b = 1.0f - abs(val*0.293f); + __disable_irq(); + pansin = a; + pancos = b; + __enable_irq(); + } + void setBypass(bool state) {bypass = state;} + void tglBypass(void) {bypass ^= 1;} + bool getBypass(void) { return bypass;} +private: + bool bypass; + float32_t width; + float32_t pancos, pansin; + float32_t do_allp_netw(float32_t inSig, float32_t *x, float32_t *y); + float32_t allpass_netw_1x[ALLP_NETWORK_LEN]; + float32_t allpass_netw_1y[ALLP_NETWORK_LEN]; + float32_t allpass_netw_2x[ALLP_NETWORK_LEN]; + float32_t allpass_netw_2y[ALLP_NETWORK_LEN]; + + audio_block_f32_t *inputQueueArray_f32[1]; +}; + + + +#endif // _EFFECT_MONOTOSTEREO_F32_H diff --git a/src/effect_phaserStereo_F32.cpp b/src/effect_phaserStereo_F32.cpp new file mode 100644 index 0000000..468c3c1 --- /dev/null +++ b/src/effect_phaserStereo_F32.cpp @@ -0,0 +1,166 @@ +/* Mono Phaser/Vibrato effect for Teensy Audio library + * + * Author: Piotr Zapart + * www.hexefx.com + * + * Copyright (c) 2021 by Piotr Zapart + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "effect_phaserStereo_F32.h" + +// ---------------------------- INTERNAL LFO ------------------------------------- +#define LFO_LUT_BITS 8 +#define LFO_MAX_F (AUDIO_SAMPLE_RATE_EXACT / 2.0f) +#define LFO_INTERP_INT_SHIFT (32-LFO_LUT_BITS) +#define LFO_INTERP_FRACT_MASK ((1< use internal LFO + if (bps) + { + AudioStream_F32::transmit((audio_block_f32_t *)blockL,0); + AudioStream_F32::transmit((audio_block_f32_t *)blockR,1); + AudioStream_F32::release((audio_block_f32_t *)blockL); + AudioStream_F32::release((audio_block_f32_t *)blockR); + if (blockMod) AudioStream_F32::release((audio_block_f32_t *)blockMod); + return; + } + for (i=0; i < blockL->length; i++) + { + if(internalLFO) + { + uint32_t LUTaddr = phaseAcc >> LFO_INTERP_INT_SHIFT; //8 bit address + fract = phaseAcc & LFO_INTERP_FRACT_MASK; // fractional part mask + y0 = AudioWaveformHyperTri[LUTaddr]; + y1 = AudioWaveformHyperTri[LUTaddr+1]; + y = ((int64_t) y0 * (LFO_INTERP_FRACT_MASK - fract)); + y += ((int64_t) y1 * (fract)); + modSigL = (float32_t)(y>>LFO_INTERP_INT_SHIFT) / 65535.0f; + if (lfo_lroffset) + { + LUTaddr = (LUTaddr + lfo_lroffset) & LFO_LUT_SIZE_MASK; + y0 = AudioWaveformHyperTri[LUTaddr]; + y1 = AudioWaveformHyperTri[LUTaddr+1]; + y = ((int64_t) y0 * (LFO_INTERP_FRACT_MASK - fract)); + y += ((int64_t) y1 * (fract)); + modSigR = (float32_t)(y>>LFO_INTERP_INT_SHIFT) / 65535.0f; + } + else modSigR = modSigL; + + phaseAcc += phaseAdd; + } + else // external modulation signal does not use modulation offset between LR + { + modSigL = ((float32_t)blockMod->data[i] + 32768.0f) / 65535.0f; // mod signal is 0.0 to 1.0 + modSigR = modSigL; + } + // apply scale/offset to the modulation wave + modSigL = modSigL * _lfo_scaler + _lfo_bias; + modSigR = modSigR * _lfo_scaler + _lfo_bias; + + drySigL = blockL->data[i] * (1.0f - abs(fdb)*0.25f); // attenuate the input if using feedback + inSigL = drySigL + last_sampleL * fdb; + drySigR = blockR->data[i] * (1.0f - abs(fdb)*0.25f); + inSigR = drySigR + last_sampleR * fdb; + + y0 = stg; + while (y0) // process allpass filters in pairs + { + y0--; + allpass_y[0][y0] = modSigL * (allpass_y[0][y0] + inSigL) - allpass_x[0][y0]; // left channel + allpass_x[0][y0] = inSigL; + allpass_y[1][y0] = modSigR * (allpass_y[1][y0] + inSigR) - allpass_x[1][y0]; // right channel + allpass_x[1][y0] = inSigR; + y0--; + allpass_y[0][y0] = modSigL * (allpass_y[0][y0] + allpass_y[0][y0+1]) - allpass_x[0][y0]; + allpass_x[0][y0] = allpass_y[0][y0+1]; + inSigL = allpass_y[0][y0]; + allpass_y[1][y0] = modSigR * (allpass_y[1][y0] + allpass_y[1][y0+1]) - allpass_x[1][y0]; + allpass_x[1][y0] = allpass_y[1][y0+1]; + inSigR = allpass_y[1][y0]; + } + last_sampleL = inSigL; + last_sampleR = inSigR; + blockL->data[i] = drySigL * (1.0f - mix_ratio) + last_sampleL * mix_ratio; // dry/wet mixer + blockR->data[i] = drySigR * (1.0f - mix_ratio) + last_sampleR * mix_ratio; // dry/wet mixer + + } + lfo_phase_acc = phaseAcc; + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); + if (blockMod) AudioStream_F32::release((audio_block_f32_t *)blockMod); +#endif + + +} diff --git a/src/effect_phaserStereo_F32.h b/src/effect_phaserStereo_F32.h new file mode 100644 index 0000000..14b292c --- /dev/null +++ b/src/effect_phaserStereo_F32.h @@ -0,0 +1,207 @@ +/* Stereo Phaser/Vibrato effect for Teensy Audio library + * + * Author: Piotr Zapart + * www.hexefx.com + * + * Copyright (c) 2021 by Piotr Zapart + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _EFFECT_PHASERSTEREO_F32_H +#define _EFFECT_PHASERSTEREO_F32_H + +#include +#include "Audio.h" +#include "AudioStream.h" +#include "AudioStream_F32.h" +#include "arm_math.h" + +#define PHASER_STEREO_STAGES 12 + +class AudioEffectPhaserStereo_F32 : public AudioStream_F32 +{ + public: + AudioEffectPhaserStereo_F32(); + ~AudioEffectPhaserStereo_F32(); + virtual void update(); + + /** + * @brief Scale and offset the modulation signal. It can be the internal LFO + * or the incomig routed modulation AudioSignal. + * LFO will oscillate between these two max and min values. + * + * @param top top level of the LFO + * @param bottom bottom level of the LFO + */ + void depth(float32_t top, float32_t bottom) + { + float32_t a, b; + lfo_top = constrain(top, 0.0f, 1.0f); + lfo_btm = constrain(bottom, 0.0f, 1.0f); + + a = abs(lfo_top - lfo_btm); // scaler + b = min(lfo_top, lfo_btm); // bias + __disable_irq(); + lfo_bias = b; + lfo_scaler = a; + __enable_irq(); + } + /** + * @brief classic way of setting the depth: LFO centered around 0.5 + * + * @param dpth modulation wave amplitude + */ + void depth(float32_t value) + { + value = constrain(value, 0.0f, 1.0f); + value *= 0.5f; + lfo_top = 0.5f + value; + lfo_btm = 0.5f - value; + __disable_irq(); + lfo_bias = 0.5f; + lfo_scaler = value; + __enable_irq(); + } + void top(float32_t value) + { + lfo_top = constrain(value, 0.0f, 1.0f); + depth(lfo_top, lfo_btm); + } + void btm(float32_t value) + { + lfo_btm = constrain(value, 0.0f, 1.0f); + depth(lfo_top, lfo_btm); + } + /** + * @brief Controls the internal LFO, or if a control signal is used, scales it + * Use this function to update all lfo parameteres at once + * + * @param f_Hz lfo frequency, use 0.0f for manual phaser control + * @param phase phase shift between the LFOs L and R waveforms, 0.0-1.0 range + * @param top lfo top level + * @param btm lfo bottm level + */ + void lfo(float32_t f_Hz, float32_t phase, float32_t top, float32_t btm) + { + float32_t a, b, c; + uint32_t add; + uint8_t bs; + + a = constrain(top, 0.0f, 1.0f); + b = constrain(btm, 0.0f, 1.0f); + c = abs(a - b); // scaler + a = min(a, b); // bias + f_Hz = constrain(f_Hz, 0.0f, AUDIO_SAMPLE_RATE_EXACT/2); + phase = constrain(phase, 0.0f, 1.0f); + add = f_Hz * (4294967296.0f / AUDIO_SAMPLE_RATE_EXACT); + bs = (uint8_t)(phase * 128.0f); + __disable_irq(); + lfo_scaler = c; + lfo_bias = a; + lfo_add = add; + lfo_lroffset = bs; + __enable_irq(); + } + void stereo(float32_t phase) + { + uint8_t bs; + phase = constrain(phase, 0.0f, 1.0f); + bs = (uint8_t)(phase * 128.0f); + __disable_irq(); + lfo_lroffset = bs; + __enable_irq(); + } + + /** + * @brief Set the rate of the internal LFO + * + * @param f_Hz lfo frequency, use 0.0f for manual phaser control + */ + void lfo_rate(float32_t f_Hz) + { + float32_t c; + uint32_t add; + c = constrain(f_Hz, 0.0f, AUDIO_SAMPLE_RATE_EXACT/2); + add = c * (4294967296.0 / AUDIO_SAMPLE_RATE_EXACT); + __disable_irq(); + lfo_add = add; + __enable_irq(); + } + /** + * @brief Controls the feedback parameter + * + * @param fdb ffedback value in range 0.0f to 1.0f + */ + void feedback(float32_t fdb) + { + feedb = constrain(fdb, -1.0f, 1.0f); + } + /** + * @brief Dry / Wet mixer ratio. Classic Phaser sound uses 0.5f for 50% dry and 50%Wet + * 1.0f will produce 100% wet signal craeting a vibrato effect + * + * @param ratio mixing ratio, range 0.0f (full dry) to 1.0f (full wet) + */ + void mix(float32_t ratio) + { + mix_ratio = constrain(ratio, 0.0f, 1.0f); + } + /** + * @brief Sets the number of stages used in the phaser + * Allowed values are: 2, 4, 6, 8, 10, 12 + * + * @param st number of stages, even value <= 12 + */ + void stages(uint8_t st) + { + if (st && st == ((st >> 1) << 1) && st <= PHASER_STEREO_STAGES) // only 2, 4, 6, 8, 12 allowed + { + stg = st; + } + } + /** + * @brief Use to bypass the effect (true) + * + * @param state true = bypass on, false = phaser on + */ + void bypass_set(bool state) {bps = state;} + bool bypass_tgl(void) {bps ^= 1; return bps;} + +private: + uint8_t stg; // number of stages + bool bps; // bypass + audio_block_f32_t *inputQueueArray_f32[3]; + float32_t allpass_x[2][PHASER_STEREO_STAGES]; // allpass inputs + float32_t allpass_y[2][PHASER_STEREO_STAGES]; // allpass outputs + float32_t mix_ratio; // 0 = dry. 1.0 = wet + float32_t feedb; // feedback + float32_t last_sampleL; + float32_t last_sampleR; + uint32_t lfo_phase_acc; // interfnal lfo + uint32_t lfo_add; + float32_t lfo_lrphase; + uint32_t lfo_lroffset; + float32_t lfo_scaler; + float32_t lfo_bias; + float32_t lfo_top; + float32_t lfo_btm; +}; + +#endif // _EFFECT_PHASER_H diff --git a/src/effect_platereverb_F32.cpp b/src/effect_platereverb_F32.cpp new file mode 100644 index 0000000..4b6febb --- /dev/null +++ b/src/effect_platereverb_F32.cpp @@ -0,0 +1,284 @@ +/* Stereo plate reverb for Teensy 4 + * + * Author: Piotr Zapart + * www.hexefx.com + * + * Copyright (c) 2020 by Piotr Zapart + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include +#include "effect_platereverb_F32.h" + +#define INP_ALLP_COEFF (0.65f) +#define LOOP_ALLOP_COEFF (0.65f) + +#define TREBLE_LOSS_FREQ (0.3f) +#define TREBLE_LOSS_FREQ_MAX (0.08f) +#define BASS_LOSS_FREQ (0.06f) + +#define RV_MASTER_LOWPASS_F (0.6f) // master lowpass scaled frequency coeff. + + +AudioEffectPlateReverb_F32::AudioEffectPlateReverb_F32() : AudioStream_F32(2, inputQueueArray_f32) { begin();} + +bool AudioEffectPlateReverb_F32::begin() +{ + input_attn = 0.5f; + wet_gain = 1.0f; // default mode: wet signal only + dry_gain = 0.0f; + in_allp_k = INP_ALLP_COEFF; + loop_allp_k = LOOP_ALLOP_COEFF; + rv_time_scaler = 1.0f; + rv_time_k = 0.2f; + + if(!in_allp_1L.init(&in_allp_k)) return false; + if(!in_allp_2L.init(&in_allp_k)) return false; + if(!in_allp_3L.init(&in_allp_k)) return false; + if(!in_allp_4L.init(&in_allp_k)) return false; + + if(!in_allp_1R.init(&in_allp_k)) return false; + if(!in_allp_2R.init(&in_allp_k)) return false; + if(!in_allp_3R.init(&in_allp_k)) return false; + if(!in_allp_4R.init(&in_allp_k)) return false; + + in_allp_out_L = 0.0f; + in_allp_out_R = 0.0f; + + if(!lp_allp_1.init(&loop_allp_k)) return false; + if(!lp_allp_2.init(&loop_allp_k)) return false; + if(!lp_allp_3.init(&loop_allp_k)) return false; + if(!lp_allp_4.init(&loop_allp_k)) return false; + + lp_allp_out = 0.0f; + + if(!lp_dly1.init()) return false; + if(!lp_dly2.init()) return false; + if(!lp_dly3.init()) return false; + if(!lp_dly4.init()) return false; + + lp_hidamp_k = 1.0f; + lp_lodamp_k = 0.0f; + flt1.init(BASS_LOSS_FREQ, &lp_lodamp_k, TREBLE_LOSS_FREQ, &lp_hidamp_k); + flt2.init(BASS_LOSS_FREQ, &lp_lodamp_k, TREBLE_LOSS_FREQ, &lp_hidamp_k); + flt3.init(BASS_LOSS_FREQ, &lp_lodamp_k, TREBLE_LOSS_FREQ, &lp_hidamp_k); + flt4.init(BASS_LOSS_FREQ, &lp_lodamp_k, TREBLE_LOSS_FREQ, &lp_hidamp_k); + + master_lp_k = 1.0f; + master_hp_k = 0.0f; + flt_masterL.init(0.08f, &master_hp_k, 0.1f, &master_lp_k); + flt_masterR.init(0.08f, &master_hp_k, 0.1f, &master_lp_k); + + if(!pitchL.init()) return false; + if(!pitchR.init()) return false; + pitchL.setPitch(1.0f); //natural pitch + pitchR.setPitch(1.0f); //natural pitch + pitchL.setTone(0.36f); + pitchR.setTone(0.36f); + pitchL.setMix(0.0f); + pitchR.setMix(0.0f); + + shimmerRatio = 0.0f; + if(!pitchShimL.init()) return false; + if(!pitchShimR.init()) return false; + pitchShimL.setPitch(2.0f); + pitchShimR.setPitch(2.0f); + pitchShimL.setTone(0.26f); + pitchShimR.setTone(0.26f); + pitchShimL.setMix(0.0f); + pitchShimR.setMix(0.0f); + + flags.bypass = 1; + flags.freeze = 0; + initialised = true; + return true; +} + +void AudioEffectPlateReverb_F32::update() +{ + if (!initialised) return; + audio_block_f32_t *blockL, *blockR; + int16_t i; + float acc; + float rv_time; + uint32_t offset; + float lfo_fr; + // handle bypass, 1st call will clean the buffers to avoid continuing the previous reverb tail + if (flags.bypass) + { + if (!flags.cleanup_done) + { + in_allp_1L.reset(); + in_allp_2L.reset(); + in_allp_3L.reset(); + in_allp_4L.reset(); + in_allp_1R.reset(); + in_allp_2R.reset(); + in_allp_3R.reset(); + in_allp_4R.reset(); + lp_allp_1.reset(); + lp_allp_2.reset(); + lp_allp_3.reset(); + lp_allp_4.reset(); + lp_dly1.reset(); + lp_dly2.reset(); + lp_dly3.reset(); + lp_dly4.reset(); + flags.cleanup_done = 1; + } + + if (dry_gain > 0.0f) // if dry/wet mixer is used + { + 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); + } + blockL = AudioStream_F32::allocate_f32(); + if (!blockL) return; + arm_fill_f32(0.0f, blockL->data, blockL->length); + 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); + + if (!blockL || !blockR) + { + if (blockL) AudioStream_F32::release(blockL); + if (blockR) AudioStream_F32::release(blockR); + return; + } + rv_time = rv_time_k; + + for (i=0; i < blockL->length; i++) + { + // do the LFOs + lfo1.update(); + lfo2.update(); + + acc = blockL->data[i] * input_attn; + + // chained input allpasses, channel L + acc = in_allp_1L.process(acc); + acc = in_allp_2L.process(acc); + acc = in_allp_3L.process(acc); + in_allp_out_L = in_allp_4L.process(acc); + in_allp_out_L = pitchL.process(in_allp_out_L); + + // chained input allpasses, channel R + acc = blockR->data[i] * input_attn; + + acc = in_allp_1R.process(acc); + acc = in_allp_2R.process(acc); + acc = in_allp_3R.process(acc); + in_allp_out_R = in_allp_4R.process(acc); + + acc = pitchShimR.process(lp_allp_out + in_allp_out_R); // shimmer + + acc = lp_dly1.process(acc); + acc = flt1.process(acc) * rv_time * rv_time_scaler; + + acc = lp_allp_2.process(acc + in_allp_out_L); + acc = lp_dly2.process(acc); + acc = flt2.process(acc) * rv_time * rv_time_scaler; + + acc = pitchShimL.process(acc + in_allp_out_R); // shimmer + + acc = lp_allp_3.process(acc); + acc = lp_dly3.process(acc); + acc = flt3.process(acc) * rv_time * rv_time_scaler; + + acc = lp_allp_4.process(acc + in_allp_out_L); + acc = lp_dly4.process(acc); + + lp_allp_out = flt4.process(acc) * rv_time * rv_time_scaler; + + acc = lp_dly1.getTap(lp_dly1_offset_L) * 0.8f; + acc += lp_dly2.getTap(lp_dly2_offset_L) * 0.7f; + acc += lp_dly3.getTap(lp_dly3_offset_L) * 0.6f; + acc += lp_dly4.getTap(lp_dly4_offset_L) * 0.5f; + + // Master lowpass filter + acc = flt_masterL.process(acc); + + blockL->data[i] = acc * wet_gain + blockL->data[i] * dry_gain; + // ChannelR + acc = lp_dly1.getTap(lp_dly1_offset_R) * 0.8f; + acc += lp_dly2.getTap(lp_dly2_offset_R) * 0.7f; + acc += lp_dly3.getTap(lp_dly3_offset_R) * 0.6f; + acc += lp_dly4.getTap(lp_dly4_offset_R) * 0.5f; + // Master lowpass filter + acc = flt_masterR.process(acc); + blockR->data[i] = acc * wet_gain + blockR->data[i] * dry_gain; + + // modulate the delay lines + // delay 1 + lfo1.get(BASIC_LFO_PHASE_0, &offset, &lfo_fr); // lfo1 sin output + acc = lp_dly1.getTap(offset, lfo_fr); + lp_dly1.write_toOffset(acc, LFO_AMPL*2); + lp_dly1.updateIndex(); + + // delay 2 + lfo1.get(BASIC_LFO_PHASE_90, &offset, &lfo_fr); // lfo1 cos output + acc = lp_dly2.getTap(offset, lfo_fr); + lp_dly2.write_toOffset(acc, LFO_AMPL*2); + lp_dly2.updateIndex(); + + // delay 3 + lfo2.get(BASIC_LFO_PHASE_0, &offset, &lfo_fr); // lfo2 sin output + acc = lp_dly3.getTap(offset, lfo_fr); + lp_dly3.write_toOffset(acc, LFO_AMPL*2); + lp_dly3.updateIndex(); + + // delay 4 + lfo2.get(BASIC_LFO_PHASE_90, &offset, &lfo_fr); // lfo2 cos output + acc = lp_dly4.getTap(offset, lfo_fr); + lp_dly4.write_toOffset(acc, LFO_AMPL*2); + lp_dly4.updateIndex(); + } + if (LFO_AMPL != LFO_AMPLset) + { + lfo1.setDepth(LFO_AMPL); + lfo2.setDepth(LFO_AMPL); + LFO_AMPL = LFO_AMPLset; + } + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); +} diff --git a/src/effect_platereverb_F32.h b/src/effect_platereverb_F32.h new file mode 100644 index 0000000..871e368 --- /dev/null +++ b/src/effect_platereverb_F32.h @@ -0,0 +1,360 @@ +/* Stereo plate reverb for ESP32 + * + * Author: Piotr Zapart + * www.hexefx.com + * + * Copyright (c) 2021 by Piotr Zapart + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/*** + * 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_ +#define _EFFECT_PLATEREVERB_F32_H_ + +#include +#include "Audio.h" +#include "AudioStream.h" +#include "AudioStream_F32.h" +#include "arm_math.h" +#include "basic_components.h" + + +class AudioEffectPlateReverb_F32 : public AudioStream_F32 +{ +public: + AudioEffectPlateReverb_F32(); + ~AudioEffectPlateReverb_F32(){}; + virtual void update(); + + bool begin(void); + + 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); + __disable_irq(); + rv_time_k = n; + input_attn = 0.5f; + __enable_irq(); + } + + float size_get(void) {return rv_time_k;} + + void hidamp(float n) + { + n = 1.0f - constrain(n, 0.0f, 1.0f); + __disable_irq(); + lp_hidamp_k = n; + __enable_irq(); + } + + void lodamp(float n) + { + n = -constrain(n, 0.0f, 1.0f); + float32_t tscal = 1.0f + n*0.12f; //n is negativbe here + __disable_irq(); + lp_lodamp_k = n; + rv_time_scaler = tscal; // limit the max reverb time, otherwise it will clip + __enable_irq(); + } + + void lowpass(float n) + { + n = 1.0f - constrain(n, 0.0f, 1.0f); + __disable_irq(); + master_lp_k = n; + __enable_irq(); + } + void hipass(float n) + { + n = -constrain(n, 0.0f, 1.0f); + __disable_irq(); + master_hp_k = n; + __enable_irq(); + } + void diffusion(float n) + { + n = constrain(n, 0.0f, 1.0f); + n = map(n, 0.0f, 1.0f, 0.005f, 0.65f); + __disable_irq(); + in_allp_k = n; + loop_allp_k = n; + __enable_irq(); + } + + void freeze(bool state) + { + flags.freeze = state; + if (state) + { + rv_time_k_tmp = rv_time_k; // store the settings + lp_lodamp_k_tmp = lp_lodamp_k; + lp_hidamp_k_tmp = lp_hidamp_k; + __disable_irq(); + rv_time_k = freeze_rvtime_k; + input_attn = freeze_ingain; + rv_time_scaler = 1.0f; + lp_lodamp_k = freeze_lodamp_k; + lp_hidamp_k = freeze_hidamp_k; + pitchShimL.setMix(0.0f); // shimmer off + pitchShimR.setMix(0.0f); + __enable_irq(); + } + 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; + rv_time_scaler = sc; + lp_hidamp_k = lp_hidamp_k_tmp; + lp_lodamp_k = lp_lodamp_k_tmp; + shimmer(shimmerRatio); + __enable_irq(); + } + } + /** + * @brief Allows to bleed some signal in while in freeze mode + * has to be relatively low value to avoid oscillation + * + * @param b - amount if input signal injected to the freeze reverb + * range 0.0 to 1.0 + */ + void freezeBleedIn(float b) + { + 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 + } + + void mix(float wet, float dry=0.0f) + { + wet_level(wet); + dry_level(dry); + } + + void wet_level(float wet) + { + wet_gain = constrain(wet, 0.0f, 6.0f); + } + + void dry_level(float dry) + { + dry_gain = constrain(dry, 0.0f, 1.0f); + } + + bool freeze_tgl() {flags.freeze ^= 1; freeze(flags.freeze); return flags.freeze;} + + bool freeze_get() {return flags.freeze;} + + bool bypass_get(void) {return flags.bypass;} + void bypass_set(bool state) + { + flags.bypass = state; + if (state) freeze(false); // disable freeze in bypass mode + } + bool bypass_tgl(void) + { + flags.bypass ^= 1; + if (flags.bypass) freeze(false); // disable freeze in bypass mode + return flags.bypass; + } + + /** + * @brief controls the delay line modulation, higher values create chorus effect + * + * @param c chorus depth, range 0.0f to 1.0f + */ + void chorus(float c) + { + c = map(c, 0.0f, 1.0f, 1.0f, 100.0f); + LFO_AMPLset = (uint32_t)c; + } + + /** + * @brief + * + * @param s + */ + void shimmer(float s) + { + if (flags.freeze) return; // do not update the shimmer if in freeze mode + s = constrain(s, 0.0f, 1.0f); + s = 2*s - s*s; + pitchShimL.setMix(s); + pitchShimR.setMix(s); + shimmerRatio = s; + } + void shimmerPitch(float ratio) + { + pitchShimL.setPitch(ratio); + pitchShimR.setPitch(ratio); + } + void shimmerPitchSemitones(int8_t semitones) + { + pitchShimL.setPitchSemintone(semitones); + pitchShimR.setPitchSemintone(semitones); + } + /** + * @brief set the reverb pitch. Range -12 to +24 + * + * @param semitones pitch shift in semitones + */ + void pitchSemitones(int8_t semitones) + { + pitchL.setPitchSemintone(semitones); + pitchR.setPitchSemintone(semitones); + } + void pitchMix(float s) + { + s = constrain(s, 0.0f, 1.0f); + pitchL.setMix(s); + pitchR.setMix(s); + pitchRatio = s; + } + +private: + struct flags_t + { + unsigned bypass: 1; + unsigned freeze: 1; + unsigned shimmer: 1; // maybe will be added at some point + unsigned cleanup_done: 1; + }flags; + audio_block_f32_t *inputQueueArray_f32[2]; + + static const uint16_t IN_ALLP1_BUFL_LEN = 224u; + static const uint16_t IN_ALLP2_BUFL_LEN = 420u; + static const uint16_t IN_ALLP3_BUFL_LEN = 856u; + static const uint16_t IN_ALLP4_BUFL_LEN = 1089u; + + static const uint16_t IN_ALLP1_BUFR_LEN = 156u; + static const uint16_t IN_ALLP2_BUFR_LEN = 520u; + static const uint16_t IN_ALLP3_BUFR_LEN = 956u; + static const uint16_t IN_ALLP4_BUFR_LEN = 1289u; + + static const uint16_t LP_ALLP1_BUF_LEN = 2303u; + static const uint16_t LP_ALLP2_BUF_LEN = 2905u; + static const uint16_t LP_ALLP3_BUF_LEN = 3175u; + static const uint16_t LP_ALLP4_BUF_LEN = 2398u; + + static const uint16_t LP_DLY1_BUF_LEN = 3423u; + static const uint16_t LP_DLY2_BUF_LEN = 4589u; + static const uint16_t LP_DLY3_BUF_LEN = 4365u; + static const uint16_t LP_DLY4_BUF_LEN = 3698u; + + const uint16_t lp_dly1_offset_L = 201; + const uint16_t lp_dly2_offset_L = 145; + const uint16_t lp_dly3_offset_L = 1897; + const uint16_t lp_dly4_offset_L = 280; + + const uint16_t lp_dly1_offset_R = 1897; + const uint16_t lp_dly2_offset_R = 1245; + const uint16_t lp_dly3_offset_R = 487; + const uint16_t lp_dly4_offset_R = 780; + + AudioFilterAllpass in_allp_1L; + AudioFilterAllpass in_allp_2L; + AudioFilterAllpass in_allp_3L; + AudioFilterAllpass in_allp_4L; + + AudioFilterAllpass in_allp_1R; + AudioFilterAllpass in_allp_2R; + AudioFilterAllpass in_allp_3R; + AudioFilterAllpass in_allp_4R; + + AudioFilterAllpass lp_allp_1; + AudioFilterAllpass lp_allp_2; + AudioFilterAllpass lp_allp_3; + AudioFilterAllpass lp_allp_4; + + uint16_t LFO_AMPL = 20u; + uint16_t LFO_AMPLset = 20u; + AudioBasicLfo lfo1 = AudioBasicLfo(1.35f, LFO_AMPL); + AudioBasicLfo lfo2 = AudioBasicLfo(1.57f, LFO_AMPL); + + float input_attn; + float wet_gain; + float dry_gain; + + float in_allp_k; // input allpass coeff (default 0.6) + float in_allp_out_L; // L allpass chain output + float in_allp_out_R; // R allpass chain output + + float loop_allp_k; // loop allpass coeff (default 0.6) + float lp_allp_out; + + AudioBasicDelay lp_dly1; + AudioBasicDelay lp_dly2; + AudioBasicDelay lp_dly3; + AudioBasicDelay lp_dly4; + + float lp_hidamp_k, lp_hidamp_k_tmp; // loop high band damping coeff + float lp_lodamp_k, lp_lodamp_k_tmp; // loop low band damping coeff + + AudioFilterShelvingLPHP flt1; + AudioFilterShelvingLPHP flt2; + AudioFilterShelvingLPHP flt3; + AudioFilterShelvingLPHP flt4; + + float master_lp_k, master_hp_k; + AudioFilterShelvingLPHP flt_masterL; + AudioFilterShelvingLPHP flt_masterR; + // Shimmer + float pitchRatio = 0.0f; + AudioBasicPitch pitchL; + AudioBasicPitch pitchR; + + float shimmerRatio = 0.0f; + AudioBasicPitch pitchShimL; + AudioBasicPitch pitchShimR; + + const float rv_time_k_max = 0.97f; + float rv_time_k, rv_time_k_tmp; // reverb time coeff + float rv_time_scaler; // with high lodamp settings lower the max reverb time to avoid clipping + + const float freeze_rvtime_k = 1.0f; + float freeze_ingain = 0.05f; + const float freeze_lodamp_k = 0.0f; + const float freeze_hidamp_k = 1.0f; + + bool initialised = false; +}; + +#endif // _EFFECT_PLATERVBSTEREO_20COPY_H_ diff --git a/src/filter_tdf2.h b/src/filter_tdf2.h new file mode 100644 index 0000000..4e08cde --- /dev/null +++ b/src/filter_tdf2.h @@ -0,0 +1,83 @@ +/* + TDFII.h + + Copyright 2006-7 + David Yeh (implementation) + 2006-14 + Tim Goetze (cosmetics) + + transposed Direct Form II digital filter. + Assumes order of b = order of a. + Assumes a0 = 1. + + Ported for 32bit float version for OpenAudio_ArduinoLibrary: + https://github.com/chipaudette/OpenAudio_ArduinoLibrary + + 12.2023 Piotr Zapart 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA or point your web browser to http://www.gnu.org. +*/ +#ifndef _FILTER_TDF2_H_ +#define _FILTER_TDF2_H_ + +#include "arm_math.h" + +template +class AudioFilterTDF2 +{ +public: + float32_t a[N + 1]; + float32_t b[N + 1]; + float32_t h[N + 1]; + + void reset() + { + for (int i = 0; i <= N; ++i) + h[i] = 0; // zero state + } + + void init() + { + reset(); + clear(); + } + + void clear() + { + for (int i = 0; i <= N; i++) + a[i] = b[i] = 0; + b[0] = 1; + } + + void process(float32_t *src, float32_t *dst, uint32_t blockSize) + { + for (uint16_t i = 0; i + 2006-14 + Tim Goetze (cosmetics) + + Tone Stack emulation for Teensy 4.x + Ported for 32bit float version for OpenAudio_ArduinoLibrary: + https://github.com/chipaudette/OpenAudio_ArduinoLibrary + + 12.2023 Piotr Zapart 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA or point your web browser to http://www.gnu.org. +*/ +#include "filter_tonestackStereo_F32.h" + +/** + * @brief EQ models based on various guitar amplifiers + */ +AudioFilterToneStackStereo_F32::toneStackParams_t AudioFilterToneStackStereo_F32::presets[] = { +/* for convenience, */ +#define k *1e3 +#define M *1e6 +#define nF *1e-9 +#define pF *1e-12 + /* parameter order is R1 - R4, C1 - C3 */ + /* R1=treble R2=Bass R3=Mid, C1-3 related caps, R4 = parallel resistor */ + /* { 250000, 1000000, 25000, 56000, 0.25e-9, 20e-9, 20e-9 }, DY */ + {250 k, 1 M, 25 k, 56 k, 250 pF, 20 nF, 20 nF, "Bassman"}, /* 59 Bassman 5F6-A */ + {250 k, 250 k, 4.8 k, 100 k, 250 pF, 100 nF, 47 nF, "Prince"}, /* 64 Princeton AA1164 */ + {250 k, 1 M, 25 k, 47 k, 600 pF, 20 nF, 20 nF, "Mesa"}, /* Mesa Dual Rect. 'Orange' */ + /* Vox -- R3 is fixed (circuit differs anyway) */ + {1 M, 1 M, 10 k, 100 k, 50 pF, 22 nF, 22 nF, "Vox"}, /* Vox "top boost" */ + + {220 k, 1 M, 22 k, 33 k, 470 pF, 22 nF, 22 nF, "JCM800"}, /* 59/81 JCM-800 Lead 100 2203 */ + {250 k, 250 k, 10 k, 100 k, 120 pF, 100 nF, 47 nF, "Twin"}, /* 69 Twin Reverb AA270 */ + + {500 k, 1 M, 25 k, 47 k, 150 pF, 22 nF, 22 nF, "HK"}, /* Hughes & Kettner Tube 20 */ + {250 k, 250 k, 10 k, 100 k, 150 pF, 82 nF, 47 nF, "Jazz"}, /* Roland Jazz Chorus */ + {250 k, 1 M, 50 k, 33 k, 100 pF, 22 nF, 22 nF, "Pignose"}, /* Pignose G40V */ +#undef k +#undef M +#undef nF +#undef pF +}; + +AudioFilterToneStackStereo_F32 :: AudioFilterToneStackStereo_F32() : AudioStream_F32(2, inputQueueArray_f32) +{ + gain = 1.0f; + setModel(TONESTACK_OFF); +} + +void AudioFilterToneStackStereo_F32::setModel(toneStack_presets_e m) +{ + if (m >= TONE_STACK_MAX_MODELS) return; + if (m == TONESTACK_OFF) + { + bp = true; + filterL.reset(); + filterR.reset(); + return; + } + bp = false; + currentModel = m - 1; + + float32_t R1 = presets[currentModel].R1, \ + R2 = presets[currentModel].R2, \ + R3 = presets[currentModel].R3, \ + R4 = presets[currentModel].R4; + float32_t C1 = presets[currentModel].C1, \ + C2 = presets[currentModel].C2, \ + C3 = presets[currentModel].C3; + + b1t = C1 * R1; + b1m = C3 * R3; + b1l = C1 * R2 + C2 * R2; + b1d = C1 * R3 + C2 * R3; + b2t = C1 * C2 * R1 * R4 + C1 * C3 * R1 * R4; + b2m2 = -(C1 * C3 * R3 * R3 + C2 * C3 * R3 * R3); + b2m = C1 * C3 * R1 * R3 + C1 * C3 * R3 * R3 + C2 * C3 * R3 * R3; + b2l = C1 * C2 * R1 * R2 + C1 * C2 * R2 * R4 + C1 * C3 * R2 * R4; + b2lm = C1 * C3 * R2 * R3 + C2 * C3 * R2 * R3; + b2d = C1 * C2 * R1 * R3 + C1 * C2 * R3 * R4 + C1 * C3 * R3 * R4; + b3lm = C1 * C2 * C3 * R1 * R2 * R3 + C1 * C2 * C3 * R2 * R3 * R4; + b3m2 = -(C1 * C2 * C3 * R1 * R3 * R3 + C1 * C2 * C3 * R3 * R3 * R4); + b3m = C1 * C2 * C3 * R1 * R3 * R3 + C1 * C2 * C3 * R3 * R3 * R4; + b3t = C1 * C2 * C3 * R1 * R3 * R4; + b3tm = -b3t; + b3tl = C1 * C2 * C3 * R1 * R2 * R4; + a0 = 1.0f; + a1d = C1 * R1 + C1 * R3 + C2 * R3 + C2 * R4 + C3 * R4; + a1m = C3 * R3; + a1l = C1 * R2 + C2 * R2; + a2m = C1 * C3 * R1 * R3 - C2 * C3 * R3 * R4 + C1 * C3 * R3 * R3 + C2 * C3 * R3 * R3; + a2lm = C1 * C3 * R2 * R3 + C2 * C3 * R2 * R3; + a2m2 = -(C1 * C3 * R3 * R3 + C2 * C3 * R3 * R3); + a2l = C1 * C2 * R2 * R4 + C1 * C2 * R1 * R2 + C1 * C3 * R2 * R4 + C2 * C3 * R2 * R4; + a2d = C1 * C2 * R1 * R4 + C1 * C3 * R1 * R4 + C1 * C2 * R3 * R4 + C1 * C2 * R1 * R3 + C1 * C3 * R3 * R4 + C2 * C3 * R3 * R4; + a3lm = C1 * C2 * C3 * R1 * R2 * R3 + C1 * C2 * C3 * R2 * R3 * R4; + a3m2 = -(C1 * C2 * C3 * R1 * R3 * R3 + C1 * C2 * C3 * R3 * R3 * R4); + a3m = C1 * C2 * C3 * R3 * R3 * R4 + C1 * C2 * C3 * R1 * R3 * R3 - C1 * C2 * C3 * R1 * R3 * R4; + a3l = C1 * C2 * C3 * R1 * R2 * R4; + a3d = C1 * C2 * C3 * R1 * R3 * R4; + + filterL.reset(); + filterR.reset(); +} + +void AudioFilterToneStackStereo_F32::update() +{ +#if defined(__ARM_ARCH_7EM__) + audio_block_f32_t *blockL, *blockR; + blockL = AudioStream_F32::receiveWritable_f32(0); // audio data + blockR = AudioStream_F32::receiveWritable_f32(1); // audio data + if (!blockL || !blockR) + { + if (blockL) release((audio_block_f32_t *)blockL); + if (blockR) release((audio_block_f32_t *)blockR); + return; + } + if (bp) // bypass mode + { + AudioStream_F32::transmit((audio_block_f32_t *)blockL,0); + AudioStream_F32::transmit((audio_block_f32_t *)blockR,1); + AudioStream_F32::release((audio_block_f32_t *)blockL); + AudioStream_F32::release((audio_block_f32_t *)blockR); + return; + } + filterL.process(blockL->data, blockL->data, blockL->length); + filterR.process(blockR->data, blockR->data, blockR->length); + if (gain != 1.0f) + { + arm_scale_f32(blockL->data, gain, blockL->data, blockL->length); + arm_scale_f32(blockR->data, gain, blockR->data, blockR->length); + } + AudioStream_F32::transmit(blockL, 0); + AudioStream_F32::transmit(blockR, 1); + AudioStream_F32::release(blockL); + AudioStream_F32::release(blockR); +#endif +} diff --git a/src/filter_tonestackStereo_F32.h b/src/filter_tonestackStereo_F32.h new file mode 100644 index 0000000..4ecf050 --- /dev/null +++ b/src/filter_tonestackStereo_F32.h @@ -0,0 +1,187 @@ +/* + ToneStack.h + + Copyright 2006-7 + David Yeh + 2006-14 + Tim Goetze (cosmetics) + + Tone Stack emulation for Teensy 4.x + + Ported for 32bit float version for OpenAudio_ArduinoLibrary: + https://github.com/chipaudette/OpenAudio_ArduinoLibrary + + 12.2023 Piotr Zapart 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA or point your web browser to http://www.gnu.org. +*/ + +#ifndef _FILTER_TONESTACK_STEREO_F32_H_ +#define _FILTER_TONESTACK_STEREO_F32_H_ + +#include +#include +#include "AudioStream_F32.h" +#include "filter_tdf2.h" +#include "arm_math.h" + +#define TONE_STACK_MAX_MODELS (10) + +typedef enum +{ + TONESTACK_OFF, + TONESTACK_BASSMAN, + TONESTACK_PRINCE, + TONESTACK_MESA, + TONESTACK_VOX, + TONESTACK_JCM800, + TONESTACK_TWIN, + TONESTACK_HK, + TONESTACK_JAZZ, + TONESTACK_PIGNOSE +}toneStack_presets_e; + +class AudioFilterToneStackStereo_F32 : public AudioStream_F32 +{ +public: + AudioFilterToneStackStereo_F32(); + ~AudioFilterToneStackStereo_F32(){}; + virtual void update(void); + + typedef struct + { + float32_t R1, R2, R3, R4; + float32_t C1, C2, C3; + const char *name; + } toneStackParams_t; + /** + * @brief preset table + */ + static toneStackParams_t presets[]; + /** + * @brief Set the EQ type from the available pool of models + * Use TONESTACK_OFF to bypass the module + * + * @param m model defined in toneStack_presets_e + */ + void setModel(toneStack_presets_e m); + + /** + * @brief return the name of the model defined in presets[] + * + * @return const char* pointer to the name char array + */ + const char *getName(){ return presets[currentModel].name;} + + /** + * @brief set all 3 parameters at once + * + * @param b bass setting + * @param m middle setting + * @param t treble setting + */ + void setTone(float32_t b, float32_t m, float32_t t) + { + b = constrain(b, 0.0f, 1.0f); bass = b; + m = constrain(m, 0.0f, 1.0f); mid = m; + t = constrain(t, 0.0f, 1.0f); treble = t; + struct + { + float32_t a1, a2, a3; + float32_t b1, b2, b3; + } acoef; // analog coefficients + + // digital coefficients + float32_t dcoef_a[order + 1]; + float32_t dcoef_b[order + 1]; + + m = (m - 1.0f) * 3.5f; + m = pow10f(m); + acoef.a1 = a1d + m * a1m + b * a1l; + acoef.a2 = m * a2m + b * m * a2lm + m * m * a2m2 + b * a2l + a2d; + acoef.a3 = b * m * a3lm + m * m * a3m2 + m * a3m + b * a3l + a3d; + dcoef_a[0] = -1.0f - acoef.a1 * c - acoef.a2 * c * c - acoef.a3 * c * c * c; // sets scale + dcoef_a[1] = -3.0f - acoef.a1 * c + acoef.a2 * c * c + 3.0f * acoef.a3 * c * c * c; + dcoef_a[2] = -3.0f + acoef.a1 * c + acoef.a2 * c * c - 3.0f * acoef.a3 * c * c * c; + dcoef_a[3] = -1.0f + acoef.a1 * c - acoef.a2 * c * c + acoef.a3 * c * c * c; + + acoef.b1 = t * b1t + m * b1m + b * b1l + b1d; + acoef.b2 = t * b2t + m * m * b2m2 + m * b2m + b * b2l + b * m * b2lm + b2d; + acoef.b3 = b * m * b3lm + m * m * b3m2 + m * b3m + t * b3t + t * m * b3tm + t * b * b3tl; + dcoef_b[0] = -acoef.b1 * c - acoef.b2 * c * c - acoef.b3 * c * c * c; + dcoef_b[1] = -acoef.b1 * c + acoef.b2 * c * c + 3.0f * acoef.b3 * c * c * c; + dcoef_b[2] = acoef.b1 * c + acoef.b2 * c * c - 3.0f * acoef.b3 * c * c * c; + dcoef_b[3] = acoef.b1 * c - acoef.b2 * c * c + acoef.b3 * c * c * c; + + __disable_irq(); + for (int i = 1; i <= order; ++i) + { + filterL.a[i] = dcoef_a[i] / dcoef_a[0]; + filterR.a[i] = filterL.a[i]; + } + for (int i = 0; i <= order; ++i) + { + filterL.b[i] = dcoef_b[i] / dcoef_a[0]; + filterR.b[i] = filterL.b[i]; + } + __enable_irq(); + } + /** + * @brief set the bass range EQ + * + * @param b bass setting + */ + void setBass(float32_t b) { setTone(b, mid, treble);} + + /** + * @brief set the mid range EQ + * + * @param m middle setting + */ + void setMid(float32_t m) { setTone(bass, m, treble);} + + /** + * @brief set the treble range EQ + * + * @param t treble setting + */ + void setTreble(float32_t t) {setTone(bass, mid, t);} + + /** + * @brief Master volume setting + * + * @param g gain value + */ + void setGain(float32_t g) { gain = g;} + +private: + static const uint8_t order = 3; + AudioFilterTDF2 filterL; + AudioFilterTDF2 filterR; + audio_block_f32_t *inputQueueArray_f32[2]; + bool bp = false; // bypass + uint8_t currentModel; + float32_t c = 2.0f * AUDIO_SAMPLE_RATE; + float32_t b1t, b1m, b1l, b1d, + b2t, b2m2, b2m, b2l, b2lm, b2d, + b3lm, b3m2, b3m, b3t, b3tm, b3tl, + a0, a1d, a1m, a1l, a2m, a2lm, a2m2, a2l, a2d, + a3lm, a3m2, a3m, a3l, a3d; // intermediate calculations + float32_t bass, mid, treble, gain; +}; + +#endif // _FILTER_TONESTACK_F32_H_ diff --git a/src/hexefx_audio_F32.h b/src/hexefx_audio_F32.h new file mode 100644 index 0000000..7e29b78 --- /dev/null +++ b/src/hexefx_audio_F32.h @@ -0,0 +1,15 @@ +#ifndef _HEXEFX_AUDIO_H +#define _HEXEFX_AUDIO_H + +#include "input_i2s2_F32.h" +#include "output_i2s2_F32.h" + +#include "filter_ir_cabsim_F32.h" +#include "filter_tonestackStereo_F32.h" + +#include "effect_platereverb_F32.h" +#include "effect_monoToStereo_F32.h" +#include "effect_infphaser_F32.h" +#include "effect_phaserStereo_F32.h" + +#endif // _HEXEFX_AUDIO_H diff --git a/src/input_i2s2_F32.cpp b/src/input_i2s2_F32.cpp new file mode 100644 index 0000000..3376509 --- /dev/null +++ b/src/input_i2s2_F32.cpp @@ -0,0 +1,260 @@ +/* + * input_i2s2_f32.cpp + * + * Audio Library for Teensy 3.X + * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * 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 "input_i2s2_F32.h" +#include "output_i2s2_F32.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; +uint16_t AudioInputI2S2_F32::block_offset = 0; +bool AudioInputI2S2_F32::update_responsibility = false; +DMAChannel AudioInputI2S2_F32::dma(false); + +int AudioInputI2S2_F32::flag_out_of_memory = 0; +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])) + +void AudioInputI2S2_F32::begin(void) { + bool transferUsing32bit = false; + begin(transferUsing32bit); +} + +void AudioInputI2S2_F32::begin(bool transferUsing32bit) { + 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; + + // TODO: should we set & clear the I2S_RCSR_SR bit here? + AudioOutputI2S2_F32::config_i2s(transferUsing32bit); + +#if defined(__IMXRT1062__) + 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->SOFF = 0; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); + dma.TCD->NBYTES_MLNO = 2; + 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->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->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; +} + + +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; + 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) { + // 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 + if (AudioInputI2S2_F32::update_responsibility) AudioStream_F32::update_all(); + } 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]; + } + left_f32 = AudioInputI2S2_F32::block_left_f32; + right_f32 = AudioInputI2S2_F32::block_right_f32; + 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)) { + 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++; + } 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) + out_f32->id = update_counter; + + //transmit the f32 data! + AudioStream_F32::transmit(out_f32,chan); + + //release the memory blocks + AudioStream_F32::release(out_f32); +} + +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; + + 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; + flag_out_of_memory = 1; + 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) { + // 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; + block_left_f32 = new_left; + out_right = block_right_f32; + 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) { + // the DMA didn't fill blocks, but we allocated blocks + 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 { + // the DMA already has blocks, doesn't need these + __enable_irq(); + AudioStream_F32::release(new_left); + AudioStream_F32::release(new_right); + } + } 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. + __enable_irq(); + } +} + +/******************************************************************/ + + +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 new file mode 100644 index 0000000..5fa89cb --- /dev/null +++ b/src/input_i2s2_F32.h @@ -0,0 +1,92 @@ +/* + * ***** input_i2s2_f32.h ****** + * + * Audio Library for Teensy 3.X + * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * 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 + +#ifndef _INPUT_I2S2_F32_H_ +#define _INPUT_I2S2_F32_H_ + +#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 +public: + AudioInputI2S2_F32(void) : AudioStream_F32(0, NULL) { begin(); } //uses default AUDIO_SAMPLE_RATE and BLOCK_SIZE_SAMPLES from AudioStream.h + AudioInputI2S2_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) { + sample_rate_Hz = settings.sample_rate_Hz; + audio_block_samples = settings.audio_block_samples; + begin(); + } + + 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; +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: + static audio_block_f32_t *block_left_f32; + static audio_block_f32_t *block_right_f32; + static float sample_rate_Hz; + static int audio_block_samples; + static uint16_t block_offset; + static int flag_out_of_memory; + static unsigned long update_counter; +}; + +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 new file mode 100644 index 0000000..3cc8d31 --- /dev/null +++ b/src/output_i2s2_F32.cpp @@ -0,0 +1,506 @@ +/* + * ***** output_i2s_f32.cpp ***** + * + * Audio Library for Teensy 3.X + * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * 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 +// 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 + +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; +audio_block_f32_t *AudioOutputI2S2_F32::block_left_2nd = NULL; +audio_block_f32_t *AudioOutputI2S2_F32::block_right_2nd = NULL; +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]; + +float AudioOutputI2S2_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; +int AudioOutputI2S2_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; + +#if defined(__IMXRT1062__) +#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; + 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->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->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->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.TCD->DADDR = (void *)((uint32_t)&I2S2_TDR0 + 2); + 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; +#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; + 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(); + } + else + { + // DMA is transmitting the second half of the buffer + // so we must fill the first half + dest = (int16_t *)i2s2_tx_buffer; + } + + blockL = AudioOutputI2S2_F32::block_left_1st; + blockR = AudioOutputI2S2_F32::block_right_1st; + offsetL = AudioOutputI2S2_F32::block_left_offset; + offsetR = AudioOutputI2S2_F32::block_right_offset; + + int16_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; + } + 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) + { + *(d + i) = (int16_t)*pL++; + } // interleave + 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) + { + *(d + i) = (int16_t)*pR++; + } // interleave + offsetR += audio_block_samples / 2; + } + else + { + // memset(dest,0,AUDIO_BLOCK_SAMPLES * 2); + memset(dest, 0, audio_block_samples * 2); + 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; + } + else + { + AudioOutputI2S2_F32::block_left_offset = 0; + AudioStream_F32::release(blockL); + 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; + } + else + { + AudioOutputI2S2_F32::block_right_offset = 0; + AudioStream_F32::release(blockR); + 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; ilength != audio_block_samples) + { + Serial.print("AudioOutputI2S2_F32: *** WARNING ***: audio_block says len = "); + Serial.print(block_f32->length); + Serial.print(", but I2S settings want it to be = "); + Serial.println(audio_block_samples); + } + // Serial.print("AudioOutputI2S2_F32: audio_block_samples = "); + // Serial.println(audio_block_samples); + + // 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); + + // count++; + // if (count > 100) { + // Serial.print("AudioOutputI2S2_F32::update() orig, scaled = "); + // Serial.print(block_f32->data[30]); + // Serial.print(", "); + // Serial.println(block_f32_scaled->data[30]); + // count=0; + // } + + // now process the data blocks + __disable_irq(); + if (block_left_1st == NULL) + { + block_left_1st = block_f32_scaled; + block_left_offset = 0; + __enable_irq(); + } + else if (block_left_2nd == NULL) + { + block_left_2nd = block_f32_scaled; + __enable_irq(); + } + else + { + audio_block_f32_t *tmp = block_left_1st; + block_left_1st = block_left_2nd; + block_left_2nd = block_f32_scaled; + block_left_offset = 0; + __enable_irq(); + AudioStream_F32::release(tmp); + } + AudioStream_F32::transmit(block_f32, 0); + AudioStream_F32::release(block_f32); // echo the incoming audio out the outputs + } + else + { + // this branch should never get called, but if it does, let's release the buffer that was never used + AudioStream_F32::release(block_f32_scaled); + } + + block_f32_scaled = block2_f32_scaled; // this is simply renaming the pre-allocated buffer + 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); + + __disable_irq(); + if (block_right_1st == NULL) + { + block_right_1st = block_f32_scaled; + block_right_offset = 0; + __enable_irq(); + } + else if (block_right_2nd == NULL) + { + block_right_2nd = block_f32_scaled; + __enable_irq(); + } + else + { + audio_block_f32_t *tmp = block_right_1st; + block_right_1st = block_right_2nd; + block_right_2nd = block_f32_scaled; + block_right_offset = 0; + __enable_irq(); + AudioStream_F32::release(tmp); + } + AudioStream_F32::transmit(block_f32, 1); + AudioStream_F32::release(block_f32); // echo the incoming audio out the outputs + } + else + { + // this branch should never get called, but if it does, let's release the buffer that was never used + 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); } + +void AudioOutputI2S2_F32::config_i2s(bool transferUsing32bit, float fs_Hz) +{ +#if defined(__IMXRT1062__) + 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; + // 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 + int n1 = 4; // SAI prescaler 4 => (n1*n2) = multiple of 4 + int n2 = 1 + (24000000 * 27) / (fs * 256 * n1); + + double C = ((double)fs * 256 * n1 * n2) / 24000000; + int c0 = C; + int c2 = 10000; + int c1 = C * c2 - (c0 * c2); + set_audioClock(c0, c1, c2); + + // clear SAI2_CLK register locations + CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI2_CLK_SEL_MASK)) | CCM_CSCMR1_SAI2_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4, + CCM_CS2CDR = (CCM_CS2CDR & ~(CCM_CS2CDR_SAI2_CLK_PRED_MASK | CCM_CS2CDR_SAI2_CLK_PODF_MASK)) | CCM_CS2CDR_SAI2_CLK_PRED(n1 - 1) | CCM_CS2CDR_SAI2_CLK_PODF(n2 - 1); + IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 & ~(IOMUXC_GPR_GPR1_SAI2_MCLK3_SEL_MASK)) | (IOMUXC_GPR_GPR1_SAI2_MCLK_DIR | IOMUXC_GPR_GPR1_SAI2_MCLK3_SEL(0)); // Select MCLK + + CORE_PIN33_CONFIG = 2; // EMC_07, 2=SAI2_MCLK + CORE_PIN4_CONFIG = 2; // EMC_06, 2=SAI2_TX_BCLK + CORE_PIN3_CONFIG = 2; // EMC_05, 2=SAI2_TX_SYNC, page 429 + + int rsync = 0; + 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)); + I2S2_TCR3 = I2S_TCR3_TCE; + I2S2_TCR4 = I2S_TCR4_FRSZ((2 - 1)) | I2S_TCR4_SYWD((32 - 1)) | I2S_TCR4_MF | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP; + 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)); + I2S2_RCR3 = I2S_RCR3_RCE; + I2S2_RCR4 = I2S_RCR4_FRSZ((2 - 1)) | I2S_RCR4_SYWD((32 - 1)) | I2S_RCR4_MF | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; + I2S2_RCR5 = I2S_RCR5_WNW((32 - 1)) | I2S_RCR5_W0W((32 - 1)) | I2S_RCR5_FBT((32 - 1)); + +#endif +} + +/******************************************************************/ + +// From Chip: The I2SSlave functionality has NOT been extended to allow for different block sizes or sample rates (2020-10-31) + +void AudioOutputI2S2slave_F32::begin(void) +{ + + dma.begin(true); // Allocate the DMA channel first + + // pinMode(2, OUTPUT); + block_left_1st = NULL; + block_right_1st = NULL; + + AudioOutputI2S2slave_F32::config_i2s(); + +#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->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->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = sizeof(i2s2_tx_buffer) / 2; + // dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_TX); + dma.TCD->DADDR = (void *)((uint32_t)&I2S2_TDR0 + 2); + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_TX); + dma.enable(); + + 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.enable(); + dma.attachInterrupt(AudioOutputI2S2_F32::isr); +} + +void AudioOutputI2S2slave_F32::config_i2s(void) +{ +#if defined(__IMXRT1062__) + + 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; + + // not using MCLK in slave mode - hope that's ok? + // CORE_PIN23_CONFIG = 3; // AD_B1_09 ALT3=SAI2_MCLK + CORE_PIN4_CONFIG = 3; // AD_B1_11 ALT3=SAI2_RX_BCLK + CORE_PIN3_CONFIG = 3; // AD_B1_10 ALT3=SAI2_RX_SYNC + IOMUXC_SAI2_RX_BCLK_SELECT_INPUT = 1; // 1=GPIO_AD_B1_11_ALT3, page 868 + IOMUXC_SAI2_RX_SYNC_SELECT_INPUT = 1; // 1=GPIO_AD_B1_10_ALT3, page 872 + + // configure transmitter + I2S2_TMR = 0; + I2S2_TCR1 = I2S_TCR1_RFW(1); // watermark at half fifo size + I2S2_TCR2 = I2S_TCR2_SYNC(1) | I2S_TCR2_BCP; + I2S2_TCR3 = I2S_TCR3_TCE; + I2S2_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF | I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_RCR4_FSD; + I2S2_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); + + // configure receiver + I2S2_RMR = 0; + I2S2_RCR1 = I2S_RCR1_RFW(1); + I2S2_RCR2 = I2S_RCR2_SYNC(0) | I2S_TCR2_BCP; + I2S2_RCR3 = I2S_RCR3_RCE; + I2S2_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF | I2S_RCR4_FSE | I2S_RCR4_FSP; + I2S2_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); + +#endif +} diff --git a/src/output_i2s2_F32.h b/src/output_i2s2_F32.h new file mode 100644 index 0000000..6301f1e --- /dev/null +++ b/src/output_i2s2_F32.h @@ -0,0 +1,113 @@ +/* + * ***** output_i2s_f32.h ***** + * + * Audio Library for Teensy 3.X + * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com + * + * Development of this audio library was funded by PJRC.COM, LLC by sales of + * Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop + * open source software by purchasing Teensy or other PJRC products. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice, development funding notice, and this permission + * notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * 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 + +#ifndef _OUTPUT_I2S2_F32_H_ +#define _OUTPUT_I2S2_F32_H_ + + +#include +#include +#include "AudioStream_F32.h" +#include "DMAChannel.h" + +class AudioOutputI2S2_F32 : public AudioStream_F32 +{ +//GUI: inputs:2, outputs:0 //this line used for automatic generation of GUI node +public: + //uses default AUDIO_SAMPLE_RATE and BLOCK_SIZE_SAMPLES from AudioStream.h: + AudioOutputI2S2_F32(void) : AudioStream_F32(2, inputQueueArray) { begin();} + // Allow variable sample rate and block size: + AudioOutputI2S2_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray) + { + sample_rate_Hz = settings.sample_rate_Hz; + audio_block_samples = settings.audio_block_samples; + begin(); + } + 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 +protected: + AudioOutputI2S2_F32(int dummy): AudioStream_F32(2, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !! + static void config_i2s(void); + static void config_i2s(bool); + static void config_i2s(float); + static void config_i2s(bool, float); + + static audio_block_f32_t *block_left_1st; + 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; + static audio_block_f32_t *block_right_2nd; + static uint16_t block_left_offset; + static uint16_t block_right_offset; + audio_block_f32_t *inputQueueArray[2]; + static float sample_rate_Hz; + 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); +}; + +#endif // _OUTPUT_I2S_F32_H_ diff --git a/src/wavetables.c b/src/wavetables.c new file mode 100644 index 0000000..a0d545a --- /dev/null +++ b/src/wavetables.c @@ -0,0 +1,84 @@ +/* + Various waveforms used in the hexefx_audiolib_F32 +*/ +#include +const uint16_t AudioWaveformHyperTri[257] = +{ + 0, 804, 1608, 2412, 3216, 4019, 4821, 5623, 6424, 7223, + 8022, 8820, 9616, 10411, 11204, 11996, 12785, 13573, 14359, 15142, + 15924, 16703, 17479, 18253, 19024, 19792, 20557, 21319, 22078, 22834, + 23586, 24334, 25079, 25820, 26557, 27291, 28020, 28745, 29465, 30181, + 30893, 31600, 32302, 32999, 33692, 34379, 35061, 35738, 36409, 37075, + 37736, 38390, 39039, 39682, 40319, 40950, 41575, 42194, 42806, 43411, + 44011, 44603, 45189, 45768, 46340, 46905, 47464, 48014, 48558, 49095, + 49624, 50145, 50659, 51166, 51664, 52155, 52638, 53113, 53580, 54039, + 54490, 54933, 55367, 55794, 56211, 56620, 57021, 57413, 57797, 58171, + 58537, 58895, 59243, 59582, 59913, 60234, 60546, 60850, 61144, 61429, + 61704, 61970, 62227, 62475, 62713, 62942, 63161, 63371, 63571, 63762, + 63943, 64114, 64276, 64428, 64570, 64703, 64826, 64939, 65042, 65136, + 65219, 65293, 65357, 65412, 65456, 65491, 65515, 65530, 65535, 65530, + 65515, 65491, 65456, 65412, 65357, 65293, 65219, 65136, 65042, 64939, + 64826, 64703, 64570, 64428, 64276, 64114, 63943, 63762, 63571, 63371, + 63161, 62942, 62713, 62475, 62227, 61970, 61704, 61429, 61144, 60850, + 60546, 60234, 59913, 59582, 59243, 58895, 58537, 58171, 57797, 57413, + 57021, 56620, 56211, 55794, 55367, 54933, 54490, 54039, 53580, 53113, + 52638, 52155, 51664, 51166, 50659, 50145, 49624, 49095, 48558, 48014, + 47464, 46905, 46340, 45768, 45189, 44603, 44011, 43411, 42806, 42194, + 41575, 40950, 40319, 39682, 39039, 38390, 37736, 37075, 36409, 35738, + 35061, 34379, 33692, 32999, 32302, 31600, 30893, 30181, 29465, 28745, + 28020, 27291, 26557, 25820, 25079, 24334, 23586, 22834, 22078, 21319, + 20557, 19792, 19024, 18253, 17479, 16703, 15924, 15142, 14359, 13573, + 12785, 11996, 11204, 10411, 9616, 8820, 8022, 7223, 6424, 5623, + 4821, 4019, 3216, 2412, 1608, 804, 0 + }; + +const float32_t AudioWaveformFader_f32[257] = +{ + 0.000000f, 0.003075f, 0.006187f, 0.009336f, 0.012522f, 0.015745f, 0.019004f, 0.022300f, + 0.025633f, 0.029002f, 0.032407f, 0.035848f, 0.039324f, 0.042837f, 0.046384f, 0.049967f, + 0.053585f, 0.057238f, 0.060925f, 0.064647f, 0.068403f, 0.072193f, 0.076016f, 0.079873f, + 0.083764f, 0.087687f, 0.091643f, 0.095632f, 0.099653f, 0.103706f, 0.107791f, 0.111907f, + 0.116055f, 0.120233f, 0.124442f, 0.128681f, 0.132951f, 0.137250f, 0.141578f, 0.145936f, + 0.150322f, 0.154737f, 0.159180f, 0.163650f, 0.168148f, 0.172674f, 0.177226f, 0.181804f, + 0.186409f, 0.191039f, 0.195695f, 0.200375f, 0.205080f, 0.209810f, 0.214563f, 0.219340f, + 0.224139f, 0.228962f, 0.233806f, 0.238673f, 0.243561f, 0.248470f, 0.253400f, 0.258350f, + 0.263320f, 0.268309f, 0.273317f, 0.278343f, 0.283388f, 0.288450f, 0.293530f, 0.298626f, + 0.303738f, 0.308867f, 0.314011f, 0.319169f, 0.324342f, 0.329529f, 0.334730f, 0.339944f, + 0.345170f, 0.350408f, 0.355658f, 0.360920f, 0.366191f, 0.371473f, 0.376765f, 0.382066f, + 0.387375f, 0.392693f, 0.398018f, 0.403351f, 0.408690f, 0.414035f, 0.419386f, 0.424742f, + 0.430103f, 0.435468f, 0.440836f, 0.446208f, 0.451582f, 0.456958f, 0.462336f, 0.467714f, + 0.473093f, 0.478472f, 0.483851f, 0.489228f, 0.494604f, 0.499977f, 0.505348f, 0.510715f, + 0.516079f, 0.521438f, 0.526793f, 0.532142f, 0.537485f, 0.542821f, 0.548151f, 0.553473f, + 0.558787f, 0.564093f, 0.569389f, 0.574675f, 0.579952f, 0.585217f, 0.590471f, 0.595713f, + 0.600943f, 0.606160f, 0.611364f, 0.616553f, 0.621728f, 0.626888f, 0.632032f, 0.637160f, + 0.642271f, 0.647366f, 0.652442f, 0.657500f, 0.662539f, 0.667559f, 0.672559f, 0.677539f, + 0.682498f, 0.687435f, 0.692351f, 0.697244f, 0.702114f, 0.706960f, 0.711782f, 0.716580f, + 0.721353f, 0.726100f, 0.730822f, 0.735516f, 0.740184f, 0.744824f, 0.749436f, 0.754020f, + 0.758574f, 0.763099f, 0.767594f, 0.772058f, 0.776492f, 0.780894f, 0.785264f, 0.789602f, + 0.793907f, 0.798178f, 0.802416f, 0.806619f, 0.810788f, 0.814922f, 0.819020f, 0.823082f, + 0.827107f, 0.831096f, 0.835047f, 0.838960f, 0.842835f, 0.846672f, 0.850469f, 0.854227f, + 0.857945f, 0.861623f, 0.865260f, 0.868856f, 0.872411f, 0.875924f, 0.879394f, 0.882822f, + 0.886206f, 0.889548f, 0.892845f, 0.896099f, 0.899308f, 0.902472f, 0.905591f, 0.908664f, + 0.911692f, 0.914673f, 0.917608f, 0.920496f, 0.923336f, 0.926129f, 0.928875f, 0.931572f, + 0.934221f, 0.936821f, 0.939373f, 0.941875f, 0.944327f, 0.946730f, 0.949082f, 0.951384f, + 0.953636f, 0.955836f, 0.957986f, 0.960084f, 0.962131f, 0.964126f, 0.966069f, 0.967959f, + 0.969797f, 0.971583f, 0.973315f, 0.974995f, 0.976621f, 0.978194f, 0.979714f, 0.981179f, + 0.982591f, 0.983948f, 0.985252f, 0.986501f, 0.987695f, 0.988835f, 0.989920f, 0.990950f, + 0.991925f, 0.992845f, 0.993709f, 0.994519f, 0.995272f, 0.995971f, 0.996614f, 0.997201f, + 0.997732f, 0.998208f, 0.998628f, 0.998992f, 0.999300f, 0.999552f, 0.999748f, 1.000000f, + 1.000000f +}; + +/************************************************************************/ +/* Pitch intervals in range from -1oct to +2 oct, step 1semitone */ +/************************************************************************/ +const float music_intevals[37] = +{ + 0.500000f, 0.529732f, 0.561231f, 0.594604f, 0.629961f, 0.667420f, 0.707107f, 0.749154f, + 0.793701f, 0.840896f, 0.890899f, 0.943874f, 1.000000f, 1.059463f, 1.122462f, 1.189207f, + 1.259921f, 1.334840f, 1.414214f, 1.498307f, 1.587401f, 1.681793f, 1.781797f, 1.887749f, + 2.000000f, 2.118926f, 2.244924f, 2.378414f, 2.519842f, 2.669680f, 2.828427f, 2.996614f, + 3.174802f, 3.363586f, 3.563595f, 3.775497f, 4.000000f +}; + +