initial commit

pull/2/head
pio 10 months ago
parent 6b724a9526
commit 01e018ceac
  1. 41
      src/HxFx_memcpy.h
  2. 76
      src/basic_allpass.h
  3. 11
      src/basic_components.h
  4. 88
      src/basic_delay.h
  5. 71
      src/basic_lfo.h
  6. 121
      src/basic_pitch.h
  7. 97
      src/basic_shelvFilter.h
  8. 118
      src/effect_infphaser_F32.cpp
  9. 180
      src/effect_infphaser_F32.h
  10. 131
      src/effect_monoToStereo_F32.cpp
  11. 83
      src/effect_monoToStereo_F32.h
  12. 166
      src/effect_phaserStereo_F32.cpp
  13. 207
      src/effect_phaserStereo_F32.h
  14. 284
      src/effect_platereverb_F32.cpp
  15. 360
      src/effect_platereverb_F32.h
  16. 83
      src/filter_tdf2.h
  17. 158
      src/filter_tonestackStereo_F32.cpp
  18. 187
      src/filter_tonestackStereo_F32.h
  19. 15
      src/hexefx_audio_F32.h
  20. 260
      src/input_i2s2_F32.cpp
  21. 92
      src/input_i2s2_F32.h
  22. 506
      src/output_i2s2_F32.cpp
  23. 113
      src/output_i2s2_F32.h
  24. 84
      src/wavetables.c

@ -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

@ -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 <int N>
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_

@ -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_

@ -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 <int N>
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_

@ -0,0 +1,71 @@
#ifndef _BASIC_LFO_H_
#define _BASIC_LFO_H_
#include <Arduino.h>
#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_

@ -0,0 +1,121 @@
#ifndef _BASIC_PITCH_H_
#define _BASIC_PITCH_H_
#include <Arduino.h>
#include "Audio.h"
#define BASIC_PITCH_BUF_BITS (12)
#define BASIC_PITCH_BUF_SIZE (1<<BASIC_PITCH_BUF_BITS)
#define BASIC_PITCH_BUF_SIZE_HALF (1<<(BASIC_PITCH_BUF_BITS-1))
#define BASIC_PITCH_BUF_MASK (BASIC_PITCH_BUF_SIZE-1)
#define BASIC_PITCH_BUF_FRAC_MASK ((1<<(32-BASIC_PITCH_BUF_BITS))-1)
#define BASIC_PITCH_XFADE_BITS (10)
#define BASIC_PITCH_XFADE_LEN (1<<BASIC_PITCH_XFADE_BITS)
#define BASIC_PITCH_XFADE_LEN_HALF (BASIC_PITCH_XFADE_LEN>>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_

@ -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 <Arduino.h>
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_

@ -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
}

@ -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 <Arduino.h>
#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

@ -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];
}

@ -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 <Arduino.h>
#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

@ -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 <Arduino.h>
#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<<LFO_INTERP_INT_SHIFT)-1)
#define LFO_LUT_SIZE_MASK ((1<<LFO_LUT_BITS)-1)
// parabollic/hypertriangular waveform used for the internal LFO
extern "C" {
extern const uint16_t AudioWaveformHyperTri[];
}
// ---------------------------- /INTERNAL LFO ------------------------------------
AudioEffectPhaserStereo_F32::AudioEffectPhaserStereo_F32() : AudioStream_F32(2, inputQueueArray_f32)
{
memset(allpass_x, 0, PHASER_STEREO_STAGES * sizeof(float32_t) * 2);
memset(allpass_y, 0, PHASER_STEREO_STAGES * sizeof(float32_t) * 2);
bps = false;
lfo_phase_acc = 0;
lfo_add = 0;
lfo_lrphase = 0.0f;
lfo_lroffset = 0;
feedb = 0.0f;
mix_ratio = 0.5f; // start with classic phaser sound
stg = PHASER_STEREO_STAGES;
}
AudioEffectPhaserStereo_F32::~AudioEffectPhaserStereo_F32()
{
}
void AudioEffectPhaserStereo_F32::update()
{
#if defined(__ARM_ARCH_7EM__)
audio_block_f32_t *blockL, *blockR;
const audio_block_f32_t *blockMod; // inputs
bool internalLFO = false; // use internal LFO of no modulation input
uint16_t i = 0;
float32_t modSigL, modSigR;
uint32_t phaseAcc = lfo_phase_acc;
uint32_t phaseAdd = lfo_add;
float32_t _lfo_scaler = lfo_scaler;
float32_t _lfo_bias = lfo_bias;
uint32_t y0, y1, fract;
uint64_t y;
float32_t inSigL, drySigL, inSigR, drySigR;
float32_t fdb = feedb;
blockL = AudioStream_F32::receiveWritable_f32(0); // audio data
blockR = AudioStream_F32::receiveWritable_f32(1); // audio data
blockMod = AudioStream_F32::receiveReadOnly_f32(2); // bipolar/int16_t control input
if (!blockL || !blockR)
{
if (blockMod) AudioStream_F32::release((audio_block_f32_t *)blockMod);
return;
}
if (!blockMod) internalLFO = true; // no modulation input provided -> 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
}

@ -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 <Arduino.h>
#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

@ -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 <Arduino.h>
#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);
}

@ -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 <Arduino.h>
#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_ALLP1_BUFL_LEN> in_allp_1L;
AudioFilterAllpass<IN_ALLP2_BUFL_LEN> in_allp_2L;
AudioFilterAllpass<IN_ALLP3_BUFL_LEN> in_allp_3L;
AudioFilterAllpass<IN_ALLP4_BUFL_LEN> in_allp_4L;
AudioFilterAllpass<IN_ALLP1_BUFR_LEN> in_allp_1R;
AudioFilterAllpass<IN_ALLP2_BUFR_LEN> in_allp_2R;
AudioFilterAllpass<IN_ALLP3_BUFR_LEN> in_allp_3R;
AudioFilterAllpass<IN_ALLP4_BUFR_LEN> in_allp_4R;
AudioFilterAllpass<LP_ALLP1_BUF_LEN> lp_allp_1;
AudioFilterAllpass<LP_ALLP2_BUF_LEN> lp_allp_2;
AudioFilterAllpass<LP_ALLP3_BUF_LEN> lp_allp_3;
AudioFilterAllpass<LP_ALLP4_BUF_LEN> 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_BUF_LEN> lp_dly1;
AudioBasicDelay<LP_DLY2_BUF_LEN> lp_dly2;
AudioBasicDelay<LP_DLY3_BUF_LEN> lp_dly3;
AudioBasicDelay<LP_DLY4_BUF_LEN> 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_

@ -0,0 +1,83 @@
/*
TDFII.h
Copyright 2006-7
David Yeh <dtyeh@ccrma.stanford.edu> (implementation)
2006-14
Tim Goetze <tim@quitte.de> (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 <int N>
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<blockSize; i++)
{
float32_t in = *src++;
float32_t y = h[0] + b[0] * in;
for (uint16_t j = 1; j < N; ++j)
h[j - 1] = h[j] + b[j] * in- a[j] * y;
h[N - 1] = b[N] * in - a[N] * y;
*dst++ = y;
}
}
};
#endif // _FILTER_TDF2_H_

@ -0,0 +1,158 @@
/*
ToneStack.h
Copyright 2006-7
David Yeh <dtyeh@ccrma.stanford.edu>
2006-14
Tim Goetze <tim@quitte.de> (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
}

@ -0,0 +1,187 @@
/*
ToneStack.h
Copyright 2006-7
David Yeh <dtyeh@ccrma.stanford.edu>
2006-14
Tim Goetze <tim@quitte.de> (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 <Arduino.h>
#include <Audio.h>
#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<order> filterL;
AudioFilterTDF2<order> 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_

@ -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

@ -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 <Arduino.h> //do we really need this? (Chip: 2020-10-31)
#include "input_i2s2_F32.h"
#include "output_i2s2_F32.h"
#include <arm_math.h>
//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; i<len; i++) { *p_f32++ = ((*p_i16++) * I16_TO_F32_NORM_FACTOR); }
}
#define I24_TO_F32_NORM_FACTOR (1.192093037616377e-07) //which is 1/(2^23 - 1)
void AudioInputI2S2_F32::scale_i24_to_f32( float32_t *p_i24, float32_t *p_f32, int len) {
for (int i=0; i<len; i++) { *p_f32++ = ((*p_i24++) * I24_TO_F32_NORM_FACTOR); }
}
#define I32_TO_F32_NORM_FACTOR (4.656612875245797e-10) //which is 1/(2^31 - 1)
void AudioInputI2S2_F32::scale_i32_to_f32( float32_t *p_i32, float32_t *p_f32, int len) {
for (int i=0; i<len; i++) { *p_f32++ = ((*p_i32++) * I32_TO_F32_NORM_FACTOR); }
}
void AudioInputI2S2_F32::update_1chan(int chan, audio_block_f32_t *&out_f32) {
if (!out_f32) return;
//scale the float values so that the maximum possible audio values span -1.0 to + 1.0
//scale_i32_to_f32(out_f32->data, 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();
}

@ -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 <Arduino.h>
#include <arm_math.h>
#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_

@ -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 <arm_math.h>
#include <Audio.h> //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 <utility/imxrt_hw.h> //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; i<len; i++) { *p_i32++ = (*p_f32++) * F32_TO_I32_NORM_FACTOR + 512.f*8388607.f; }
}
// update has to be carefully coded so that, if audio_blocks are not available, the code exits
// gracefully and won't hang. That'll cause the whole system to hang, which would be very bad.
// static int count = 0;
void AudioOutputI2S2_F32::update(void)
{
// null audio device: discard all incoming data
// if (!active) return;
// audio_block_t *block = receiveReadOnly();
// if (block) release(block);
audio_block_f32_t *block_f32;
audio_block_f32_t *block_f32_scaled = AudioStream_F32::allocate_f32();
audio_block_f32_t *block2_f32_scaled = AudioStream_F32::allocate_f32();
if ((!block_f32_scaled) || (!block2_f32_scaled))
{
// couldn't get some working memory. Return.
if (block_f32_scaled)
AudioStream_F32::release(block_f32_scaled);
if (block2_f32_scaled)
AudioStream_F32::release(block2_f32_scaled);
return;
}
// now that we have our working memory, proceed with getting the audio data and processing
block_f32 = receiveReadOnly_f32(0); // input 0 = left channel
if (block_f32)
{
if (block_f32->length != 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
}

@ -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 <Arduino.h>
#include <arm_math.h>
#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_

@ -0,0 +1,84 @@
/*
Various waveforms used in the hexefx_audiolib_F32
*/
#include <arm_math.h>
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
};
Loading…
Cancel
Save