add TGX4 components

main
pio 4 months ago
parent 81e727f1b5
commit 71328bbf05
  1. 17
      README.md
  2. 2
      library.properties
  3. 3
      src/basic_shelvFilter.h
  4. 204
      src/control_AK4452_F32.cpp
  5. 56
      src/control_AK4452_F32.h
  6. 114
      src/control_AK5552_F32.cpp
  7. 58
      src/control_AK5552_F32.h
  8. 22
      src/control_ES8388_F32.cpp
  9. 23
      src/control_ES8388_F32.h
  10. 3
      src/control_WM8731_F32.cpp
  11. 16
      src/effect_delaystereo_F32.cpp
  12. 10
      src/effect_gainStereo_F32.h
  13. 10
      src/effect_guitarBooster_F32.h
  14. 7
      src/effect_noiseGateStereo_F32.h
  15. 2
      src/effect_platereverb_F32.cpp
  16. 49
      src/effect_platereverb_F32.h
  17. 254
      src/effect_wahMono_F32.cpp
  18. 167
      src/effect_wahMono_F32.h
  19. 153
      src/filter_DCblockerStereo_F32.h
  20. 4
      src/hexefx_audio_F32.h
  21. 4
      src/input_i2s2_F32.cpp
  22. 3
      src/input_i2s2_F32.h
  23. 4
      src/input_i2s_ext_F32.cpp
  24. 5
      src/input_i2s_ext_F32.h
  25. 4
      src/output_i2s2_F32.cpp
  26. 3
      src/output_i2s2_F32.h
  27. 4
      src/output_i2s_ext_F32.cpp
  28. 3
      src/output_i2s_ext_F32.h

@ -27,6 +27,12 @@ Versatile stereo ping-pong delay with modulation.
**AudioEffectNoiseGateStereo_F32** **AudioEffectNoiseGateStereo_F32**
Stereo noise gate with external SideChain input. Stereo noise gate with external SideChain input.
**AudioEffectGuitarBooster_F32**
Overdrive emulation using oversampled wave shaper, switchable octave up.
**AudioEffectWahMono_F32**
WAH pedal emulation including 8 models and versatile range handling.
**AudioFilterToneStackStereo_F32** **AudioFilterToneStackStereo_F32**
Stereo guitar tone stack (EQ) emulator. Stereo guitar tone stack (EQ) emulator.
@ -43,12 +49,20 @@ Simple 3 band (Treble, Mid, Bass) equalizer.
**AudioEffectGainStereo_F32** **AudioEffectGainStereo_F32**
Stereo gain control (volume etc.) Stereo gain control (volume etc.)
**AudioSwitchSelectorStereo**
Stereo/mono signal selector.
## I/O ## I/O
**AudioInputI2S2_F32** **AudioInputI2S2_F32**
**AudioOutputI2S2_F32** **AudioOutputI2S2_F32**
Input and output for the I2S2 interface, Teensy 4.1 only. Input and output for the I2S2 interface, Teensy 4.1 only.
**AudioInputI2S_ext_F32**
**AudioOutputI2S_ext_F32**
Custom input and output for the I2S interface, including a few extra options (ie. channel swap)
## Basic ## Basic
Single header basic building blocks for various DSP components: Single header basic building blocks for various DSP components:
- allpass filter - allpass filter
@ -60,8 +74,9 @@ Single header basic building blocks for various DSP components:
## Example projects ## Example projects
https://github.com/hexeguitar/hexefx_audiolib_F32_examples https://github.com/hexeguitar/hexefx_audiolib_F32_examples
https://github.com/hexeguitar/tgx4
--- ---
Copyright 01.2024 by Piotr Zapart Copyright 07.2024 by Piotr Zapart
www.hexefx.com www.hexefx.com

@ -1,5 +1,5 @@
name=hexefx_audiolib_F32 name=hexefx_audiolib_F32
version=1.0.0 version=1.1.0
author=Piotr Zapart author=Piotr Zapart
maintainer=Piotr Zapart <www.hexefx.com> maintainer=Piotr Zapart <www.hexefx.com>
sentence=Audio effect extension for the OpenAudio_ArduinoLibrary sentence=Audio effect extension for the OpenAudio_ArduinoLibrary

@ -29,6 +29,7 @@ public:
} }
inline float process(float input) inline float process(float input)
{ {
if (bp) return input;
float tmp1, tmp2; float tmp1, tmp2;
// smoothly update params // smoothly update params
if (hidamp < (*hidampPtr)) if (hidamp < (*hidampPtr))
@ -64,6 +65,7 @@ public:
lpreg = 0.0f; lpreg = 0.0f;
hpreg = 0.0f; hpreg = 0.0f;
} }
void bypass_set(bool state) { bp = state; reset();}
private: private:
float lpreg; float lpreg;
float hpreg; float hpreg;
@ -74,6 +76,7 @@ private:
float hp_f; float hp_f;
float lp_f; float lp_f;
static constexpr float upd_step = 0.02f; static constexpr float upd_step = 0.02f;
bool bp = false;
}; };

@ -0,0 +1,204 @@
/**
* @file control_AK4452_F32.cpp
* @author Piotr Zapart
* @brief driver for the AK4452 DAC
* @version 0.1
* @date 2024-06-14
*
* @copyright Copyright (c) 2024 www.hexefx.com
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>."
*/
#include "control_AK4452_F32.h"
#define AK4452_REG_CTRL1 (0x00)
#define AK4452_REG_CTRL1_DFLT (0x0C)
#define AK4452_BIT_RSTN (1<<0)
#define AK4452_DIF_MASK (0x0E)
#define AK4452_DIF_SHIFT (0x01)
#define AK4452_DIF(x) (((x)<<AK4452_DIF_SHIFT)&AK4452_DIF_MASK)
#define AK4452_DIF_16B_LSB (0x00)
#define AK4452_DIF_20B_LSB (0x01)
#define AK4452_DIF_24B_MSB (0x02)
#define AK4452_DIF_24B_I2S (0x03)
#define AK4452_DIF_24B_LSB (0x04)
#define AK4452_DIF_32B_LSB (0x05)
#define AK4452_DIF_32B_MSB (0x06)
#define AK4452_DIF_32B_I2S (0x07)
#define AK4452_BIT_ACKS (1<<7)
#define AK4452_REG_CTRL2 (0x01)
#define AK4452_REG_CTRL2_DFLT (0x22)
#define AK4452_BIT_SMUTE (1<<0)
#define AK4452_DEM_MASK (0x06)
#define AK4452_DEM_SHIFT (0x01)
#define AK4452_DEM(x) (((x)<<AK4452_DEM_SHIFT)&AK4452_DEM_MASK)
#define AK4452_DEM_44_1K (0x00)
#define AK4452_DEM_OFF (0x01)
#define AK4452_DEM_48K (0x02)
#define AK4452_DEM_32K (0x03)
#define AK4452_DFS10_MASK (0x18)
#define AK4452_DFS10_SHIFT (0x03)
#define AK4452_DFS10(x) (((x)<<AK4452_DFS10_SHIFT)&AK4452_DFS10_MASK)
#define AK4452_DFS_NORMAL (0x00)
#define AK4452_DFS_DOUBLE (0x01)
#define AK4452_DFS_QUAD (0x02)
#define AK4452_DFS_OCT (0x04)
#define AK4452_DFS_HEX (0x05)
#define AK4452_BIT_SD (1<<5)
#define AK4452_REG_CTRL3 (0x02)
#define AK4452_REG_CTRL3_DFLT (0x00)
#define AK4452_BIT_SLOW (1<<0)
#define AK4452_BIT_SELLR1 (1<<1)
#define AK4452_BIT_DZFB (1<<2)
#define AK4452_BIT_MONO1 (1<<3)
#define AK4452_BIT_DCKB (1<<4)
#define AK4452_BIT_DCKS (1<<5)
#define AK4452_BIT_DP (1<<7)
#define AK4452_REG_L1CH_ATT (0x03)
#define AK4452_REG_L1CH_ATT_DFLT (0xFF)
#define AK4452_REG_R1CH_ATT (0x04)
#define AK4452_REG_R1CH_ATT_DFLT (0xFF)
#define AK4452_REG_CTRL4 (0x05)
#define AK4452_REG_CTRL4_DFLT (0x00)
#define AK4452_BIT_SSLOW (1<<0)
#define AK4452_DFS2_MASK (1<<1)
#define AK4452_DFS2_SHIFT (0x01)
#define AK4452_DFS2(x) (((x)<<AK4452_DFS2_SHIFT)&AK4452_DFS2_MASK) // bits10 in CTRL2
#define AK4452_BIT_INVL1 (1<<6)
#define AK4452_BIT_INVR1 (1<<7)
#define AK4452_REG_DSD1 (0x06)
#define AK4452_REG_DSD1_DFLT (0x00)
#define AK4452_BIT_DSDSEL0 (1<<0)
#define AK4452_BIT_DSDD (1<<1)
#define AK4452_BIT_DMRE (1<<3)
#define AK4452_BIT_DMC (1<<4)
#define AK4452_BIT_DMR1 (1<<5)
#define AK4452_BIT_DML1 (1<<6)
#define AK4452_BIT_DDM (1<<7)
#define AK4452_REG_CTRL5 (0x07)
#define AK4452_REG_CTRL5_DFLT (0x03)
#define AK4452_BIT_SYNCE (1<<0)
#define AK4452_REG_SND_CTRL (0x08)
#define AK4452_REG_SND_CTRL_DFLT (0x00)
#define AK4452_SC_MASK (0x03)
#define AK4452_SC_SHIFT (0x00)
#define AK4452_SC(x) (((x)<<AK4452_SC_SHIFT)&AK4452_SC_MASK)
#define AK4452_SC_NORMAL (0x00)
#define AK4452_SC_MAX (0x01)
#define AK4452_SC_MIN (0x02)
#define AK4452_BIT_DZF_L1 (1<<6)
#define AK4452_BIT_DZF_R1 (1<<7)
#define AK4452_REG_DSD2 (0x09)
#define AK4452_REG_DSD2_DFLT (0x00)
#define AK4452_BIT_DSDSEL1 (1<<0)
#define AK4452_BIT_DSDF (1<<1)
#define AK4452_REG_CTRL6 (0x0A)
#define AK4452_REG_CTRL6_DFLT (0x0D)
#define AK4452_BIT_PW1 (1<<2)
#define AK4452_SDS21_MASK (0x30)
#define AK4452_SDS21_SHIFT (0x04)
#define AK4452_SDS21(x) (((x)<<AK4452_SDS21_SHIFT)&AK4452_SDS21_MASK)
#define AK4452_SDS_TDM128_L1R1 (0x00)
#define AK4452_SDS_TDM128_L2R2 (0x01)
#define AK4452_SDS_TDM256_L1R1 (0x00)
#define AK4452_SDS_TDM256_L2R2 (0x01)
#define AK4452_SDS_TDM256_L3R3 (0x02)
#define AK4452_SDS_TDM256_L4R4 (0x03)
#define AK4452_SDS_TDM512_L1R1 (0x00)
#define AK4452_SDS_TDM512_L2R2 (0x01)
#define AK4452_SDS_TDM512_L3R3 (0x02)
#define AK4452_SDS_TDM512_L4R4 (0x03)
#define AK4452_SDS_TDM512_L5R5 (0x04)
#define AK4452_SDS_TDM512_L6R6 (0x05)
#define AK4452_SDS_TDM512_L7R7 (0x06)
#define AK4452_SDS_TDM512_L8R8 (0x07)
#define AK4452_TDM_MASK (0xC0)
#define AK4452_TDM_SHIFT (0x06)
#define AK4452_TDM(x) (((x)<<AK4452_TDM_SHIFT)&AK4452_TDM_MASK)
#define AK4452_TDM_NORMAL (0x00)
#define AK4452_TDM_128 (0x01)
#define AK4452_TDM_246 (0x02)
#define AK4452_TDM_512 (0x03)
#define AK4452_REG_CTRL7 (0x0B)
#define AK4452_REG_CTRL7_DFLT (0x0C)
#define AK4452_BIT_DCHAIN (1<<2)
#define AK4452_SDS0_MASK (1<<4)
#define AK4452_SDS0_SHIFT (0x04)
#define AK4452_SDS0(x) (((x)<<AK4452_SDS0_SHIFT)&AK4452_SDS0_MASK)
#define AK4452_ATS_MASK (0xC0)
#define AK4452_ATS_SHIFT (0x06)
#define AK4452_ATS(x) (((x)<<AK4452_ATS_SHIFT)&AK4452_ATS_MASK)
#define AK4452_ATS_4080 (0x00)
#define AK4452_ATS_2040 (0x01)
#define AK4452_ATS_510 (0x02)
#define AK4452_ATS_255 (0x03)
#define AK4452_REG_CTRL8 (0x0C)
#define AK4452_REG_CTRL8_DFLT (0x00)
#define AK4452_FIR(x) ((x)&0x07)
bool AudioControlAK4452_F32::configured = false;
bool AudioControlAK4452_F32::enable(TwoWire *i2cBus, uint8_t addr)
{
ctrlBus = i2cBus;
i2cAddr = addr;
ctrlBus->begin();
ctrlBus->setClock(400000);
if (!writeReg(AK4452_REG_CTRL1, 0x00)) // put the registers in reset mode
{
return false; // codec not found
}
// DIF[2:0] = 0b111 - 32bit I2S, Normal mode
// DFS[2:0] = 0b000 (default) Normal speed mode
// DSDSEL[1:0] = 0b10 256fs
writeReg(AK4452_REG_DSD2, AK4452_BIT_DSDSEL1);
writeReg(AK4452_REG_CTRL1, AK4452_DIF(AK4452_DIF_32B_I2S) | AK4452_BIT_RSTN);
configured = true;
return true;
}
bool AudioControlAK4452_F32::writeReg(uint8_t addr, uint8_t val)
{
ctrlBus->beginTransmission(i2cAddr);
ctrlBus->write(addr);
ctrlBus->write(val);
return ctrlBus->endTransmission() == 0;
}
bool AudioControlAK4452_F32::readReg(uint8_t addr, uint8_t *valPtr)
{
ctrlBus->beginTransmission(i2cAddr);
ctrlBus->write(addr);
if (ctrlBus->endTransmission(false) != 0)
return false;
if (ctrlBus->requestFrom((int)i2cAddr, 1) < 1) return false;
*valPtr = ctrlBus->read();
return true;
}
uint8_t AudioControlAK4452_F32::modifyReg(uint8_t reg, uint8_t val, uint8_t iMask)
{
uint8_t val1;
val1 = (readReg(reg, &val1) & (~iMask)) | val;
if (!writeReg(reg, val1))
return 0;
return val1;
}

@ -0,0 +1,56 @@
/**
* @file control_AK4452_F32.h
* @author Piotr Zapart
* @brief driver for the AK4452 DAC
* @version 0.1
* @date 2024-06-14
*
* @copyright Copyright (c) 2024 www.hexefx.com
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>."
*/
#ifndef _CONTROL_AK4452_H_
#define _CONTROL_AK4452_H_
#include <Arduino.h>
#include <Wire.h>
#include "AudioControl.h"
#define AK4452_ADDR00 (0x10)
#define AK4452_ADDR01 (0x11)
#define AK4452_ADDR10 (0x12)
#define AK4452_ADDR11 (0x13)
class AudioControlAK4452_F32 : public AudioControl
{
public:
AudioControlAK4452_F32(){};
~AudioControlAK4452_F32(){};
bool enable()
{
return enable(&Wire, AK4452_ADDR10);
}
bool enable(TwoWire *i2cBus, uint8_t addr);
// not used but required by AudioControl
bool disable() {return true;}
bool volume(float volume) {return true;};
bool inputLevel(float volume) {return true;}
bool inputSelect(int n) {return true;}
private:
static bool configured;
TwoWire *ctrlBus;
uint8_t i2cAddr;
bool writeReg(uint8_t addr, uint8_t val);
bool readReg(uint8_t addr, uint8_t* valPtr);
uint8_t modifyReg(uint8_t reg, uint8_t val, uint8_t iMask);
};
#endif // _CONTROL_AK4452_H_

@ -0,0 +1,114 @@
/**
* @file control_AK5552_F32.cpp
* @author Piotr Zapart
* @brief driver for the AK5552 ADC
* @version 0.1
* @date 2024-06-14
*
* @copyright Copyright (c) 2024 www.hexefx.com
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>."
*/
#include "control_AK5552_F32.h"
#define AK5552_REG_PWR_MAN1 (0x00)
#define AK5552_REG_PWR_MAN1_DFLT (0xFF)
#define AK5552_BIT_PW1 (1<<0)
#define AK5552_BIT_PW2 (1<<1)
#define AK5552_REG_PWR_MAN2 (0x01)
#define AK5552_REG_PWR_MAN2_DFLT (0x01)
#define AK5552_BIT_RSTN (1<<0)
#define AK5552_BIT_MONO1 (1<<1)
#define AK5552_BIT_MONO2 (1<<2)
#define AK5552_REG_PWR_CTRL1 (0x02)
#define AK5552_REG_PWR_CTRL1_DFLT (0x01)
#define AK5552_BIT_HPFE (1<<0)
#define AK5552_BIT_DIF0 (1<<1)
#define AK5552_BIT_DIF1 (1<<2)
#define AK5552_BIT_CKS0 (1<<3)
#define AK5552_BIT_CKS1 (1<<4)
#define AK5552_BIT_CKS2 (1<<5)
#define AK5552_BIT_CKS3 (1<<6)
#define AK5552_REG_PWR_CTRL2 (0x03)
#define AK5552_REG_PWR_CTRL2_DFLT (0x00)
#define AK5552_BIT_TDM0 (1<<5)
#define AK5552_BIT_TDM1 (1<<6)
#define AK5552_REG_PWR_CTRL3 (0x04)
#define AK5552_REG_PWR_CTRL3_DFLT (0x00)
#define AK5552_BIT_SLOW (1<<0)
#define AK5552_BIT_SD (1<<1)
#define AK5552_REG_PWR_DSD (0x04)
#define AK5552_REG_PWR_DSD_DFLT (0x00)
#define AK5552_DSDSEL_64FS (0x00)
#define AK5552_DSDSEL_128FS (0x01)
#define AK5552_DSDSEL_256FS (0x10)
#define AK5552_DSDSEL(x) ((x)&0x03)
#define AK5552_BIT_DCKB (1<<2)
#define AK5552_BIT_PMOD (1<<3)
#define AK5552_BIT_DCKS (1<<5)
bool AudioControlAK5552_F32::configured = false;
bool AudioControlAK5552_F32::enable(TwoWire *i2cBus, uint8_t addr)
{
ctrlBus = i2cBus;
i2cAddr = addr;
ctrlBus->begin();
ctrlBus->setClock(400000);
if (!writeReg(AK5552_REG_PWR_MAN2, 0x00)) // put the registers in reset mode
{
return false; // codec not found
}
// Normal Speed 256fs, table 5, page 34
// CKS3=0, CKS2=0, CKS1=1, CKS0=0
// 32bit I2S, Slave mode, table 8, page 62
// TDM1=0, TDM0=0, MSN=0, DIF1=1, DIF0=1
writeReg( AK5552_REG_PWR_CTRL1, AK5552_BIT_CKS1 |
AK5552_BIT_DIF1 |
AK5552_BIT_DIF0);
// enable short delay
writeReg(AK5552_REG_PWR_CTRL3, AK5552_BIT_SD);
writeReg(AK5552_REG_PWR_MAN2, AK5552_BIT_RSTN); // register in normal operation
configured = true;
return true;
}
bool AudioControlAK5552_F32::writeReg(uint8_t addr, uint8_t val)
{
ctrlBus->beginTransmission(i2cAddr);
ctrlBus->write(addr);
ctrlBus->write(val);
return ctrlBus->endTransmission() == 0;
}
bool AudioControlAK5552_F32::readReg(uint8_t addr, uint8_t *valPtr)
{
ctrlBus->beginTransmission(i2cAddr);
ctrlBus->write(addr);
if (ctrlBus->endTransmission(false) != 0)
return false;
if (ctrlBus->requestFrom((int)i2cAddr, 1) < 1) return false;
*valPtr = ctrlBus->read();
return true;
}
uint8_t AudioControlAK5552_F32::modifyReg(uint8_t reg, uint8_t val, uint8_t iMask)
{
uint8_t val1;
val1 = (readReg(reg, &val1) & (~iMask)) | val;
if (!writeReg(reg, val1))
return 0;
return val1;
}

@ -0,0 +1,58 @@
/**
* @file control_AK5552_F32.h
* @author Piotr Zapart
* @brief driver for the AK5552 ADC
* @version 0.1
* @date 2024-06-14
*
* @copyright Copyright (c) 2024 www.hexefx.com
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>."
*/
#ifndef _CONTROL_AK5552_F32_H_
#define _CONTROL_AK5552_F32_H_
#include <Arduino.h>
#include <Wire.h>
#include "AudioControl.h"
#define AK5552_ADDR00 (0x10)
#define AK5552_ADDR01 (0x11)
#define AK5552_ADDR10 (0x12)
#define AK5552_ADDR11 (0x13)
class AudioControlAK5552_F32 : public AudioControl
{
public:
AudioControlAK5552_F32(){};
~AudioControlAK5552_F32(){};
bool enable()
{
return enable(&Wire, AK5552_ADDR00);
}
bool enable(TwoWire *i2cBus, uint8_t addr);
// not used but required by AudioControl
bool disable() {return true;}
bool volume(float volume) {return true;};
bool inputLevel(float volume) {return true;}
bool inputSelect(int n) {return true;}
private:
static bool configured;
TwoWire *ctrlBus;
uint8_t i2cAddr;
bool writeReg(uint8_t addr, uint8_t val);
bool readReg(uint8_t addr, uint8_t* valPtr);
uint8_t modifyReg(uint8_t reg, uint8_t val, uint8_t iMask);
};
#endif // _CONTROL_AK5552_H_

@ -1,7 +1,5 @@
#include "control_ES8388_F32.h" #include "control_ES8388_F32.h"
#define ES8388_REG_CHIP_CTRL1 (0x00) // Default 0000 0110 #define ES8388_REG_CHIP_CTRL1 (0x00) // Default 0000 0110
#define ES8388_REG_CHIP_CTRL1_DFLT (0x06) #define ES8388_REG_CHIP_CTRL1_DFLT (0x06)
#define ES8388_BIT_SCPRESET (1<<7) // 1=reset registers to default #define ES8388_BIT_SCPRESET (1<<7) // 1=reset registers to default
@ -476,19 +474,6 @@ bool AudioControlES8388_F32::enable(TwoWire *i2cBus, uint8_t addr, config_t cfg)
optimizeConversion(0); optimizeConversion(0);
writeReg(ES8388_REG_CHIP_PWR_MAN, 0x00); // Power up DEM and STM writeReg(ES8388_REG_CHIP_PWR_MAN, 0x00); // Power up DEM and STM
// ALC config - disabled for now, will be tested someday..
// writeReg(ES8388_REG_ADC_CTRL10, ES8388_ALCSEL(ES8388_ALCSEL_LR) | // ALC OFF
// ES8388_MAXGAIN(ES8388_MAXGAIN_M0_5DB) | // max gain -0.5dB
// ES8388_MINGAIN(ES8388_MINGAIN_M12DB)); // min gain -12dB
// writeReg(ES8388_REG_ADC_CTRL11, ES8388_ALCLVL(0x0A)); // target gain -1.5dB, hold time=0
// writeReg(ES8388_REG_ADC_CTRL12, ES8388_ALCATK(0x02) | // ALC limiter attack time 90.8us
// ES8388_ALCDCY(0x01)); // ALC limiter decay time 182us
// writeReg(ES8388_REG_ADC_CTRL13, ES8388_BIT_ALCMODE | ES8388_WINSIZE(0x06)); // Limiter mode, no ZC, 96*16 samples peak window
// writeReg(ES8388_REG_ADC_CTRL14, 0x00); // disable noise gate
//writeReg(ES8388_REG_ADC_CTRL14, ES8388_NGTH(0x1F) | ES8388_NGG(ES8388_NGG_ADCMUTE)| ES8388_BIT_NGAT_EN);
// ADC PGA gain
//writeReg(ES8388_REG_ADC_CTRL1, 0x00);
configured = true; configured = true;
return true; return true;
} }
@ -555,7 +540,7 @@ void AudioControlES8388_F32::set_noiseGate(float thres)
writeReg(ES8388_REG_ADC_CTRL14, ES8388_NGTH(thres_val) | ES8388_NGG(ES8388_NGG_ADCMUTE)| ES8388_BIT_NGAT_EN); writeReg(ES8388_REG_ADC_CTRL14, ES8388_NGTH(thres_val) | ES8388_NGG(ES8388_NGG_ADCMUTE)| ES8388_BIT_NGAT_EN);
} }
// bypassed the analog input to the output, disconnect the digital i / o
bool AudioControlES8388_F32::analogBypass(bool bypass) bool AudioControlES8388_F32::analogBypass(bool bypass)
{ {
bool res = true; bool res = true;
@ -572,16 +557,15 @@ bool AudioControlES8388_F32::analogBypass(bool bypass)
return res; return res;
} }
// bypassed the analog input to the output, disconnect the digital input, preserve the digital output connection
bool AudioControlES8388_F32::analogSoftBypass(bool bypass) bool AudioControlES8388_F32::analogSoftBypass(bool bypass)
{ {
bool res = true; bool res = true;
if (bypass) if (bypass)
{ {
res &= writeReg(ES8388_REG_DAC_CTRL17, ES8388_BIT_LI2LO | // Lin in on res &= writeReg(ES8388_REG_DAC_CTRL17, ES8388_BIT_LI2LO | // Lin on
ES8388_BIT_LD2LO | // L Dac on ES8388_BIT_LD2LO | // L Dac on
ES8388_LI2LOVOL(ES8388_VOL_0DB)); // Lin gain 0dB ES8388_LI2LOVOL(ES8388_VOL_0DB)); // Lin gain 0dB
res &= writeReg(ES8388_REG_DAC_CTRL20, ES8388_BIT_RI2RO | // Rin in on res &= writeReg(ES8388_REG_DAC_CTRL20, ES8388_BIT_RI2RO | // Rin on
ES8388_BIT_RD2RO | // R Dac on ES8388_BIT_RD2RO | // R Dac on
ES8388_RI2ROVOL(ES8388_VOL_0DB)); // Rin gain 0dB ES8388_RI2ROVOL(ES8388_VOL_0DB)); // Rin gain 0dB
} }

@ -1,3 +1,20 @@
/**
* @file control_ES8388_F32.h
* @author your name (you@domain.com)
* @brief driver for the ES8388 codec chip
* @version 0.1
* @date 2024-06-14
*
* @copyright Copyright (c) 2024 www.hexefx.com
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>."
*/
#ifndef _CONTROL_ES8388_F32_H_ #ifndef _CONTROL_ES8388_F32_H_
#define _CONTROL_ES8388_F32_H_ #define _CONTROL_ES8388_F32_H_
@ -8,7 +25,7 @@
#define ES8388_I2C_ADDR_L (0x10) // CS/ADD pin low #define ES8388_I2C_ADDR_L (0x10) // CS/ADD pin low
#define ES8388_I2C_ADDR_H (0x11) // CS/ADD pin high #define ES8388_I2C_ADDR_H (0x11) // CS/ADD pin high
class AudioControlES8388_F32 //: public AudioControl class AudioControlES8388_F32 : public AudioControl
{ {
public: public:
AudioControlES8388_F32(void){}; AudioControlES8388_F32(void){};
@ -27,8 +44,8 @@ public:
bool enable(TwoWire *i2cBus, uint8_t addr, config_t cfg); bool enable(TwoWire *i2cBus, uint8_t addr, config_t cfg);
bool disable(void) { return false; } bool disable(void) { return false; }
bool volume(float n); bool volume(float n);
bool inputLevel(float n); // range: 0.0f to 1.0f bool inputLevel(float n) {return true;} // range: 0.0f to 1.0f
bool inputSelect(int n) {return true;}
void set_noiseGate(float thres); void set_noiseGate(float thres);

@ -236,13 +236,12 @@ bool AudioControlWM8731_F32::enable(bit_depth_t bits, TwoWire *i2cBus, uint8_t a
void AudioControlWM8731_F32::dac_mute(bool m) void AudioControlWM8731_F32::dac_mute(bool m)
{ {
modify(WM8731_REG_DIGITAL, m ? WM8731_BITS_DACMU(1) : WM8731_BITS_DACMU(0), WM8731_BITS_DACMU_MASK); modify(WM8731_REG_DIGITAL, m ? WM8731_BITS_DACMU(1) : WM8731_BITS_DACMU(0), WM8731_BITS_DACMU_MASK);
//write(WM8731_REG_DIGITAL, ); // DAC soft mute
DACmute = m; DACmute = m;
} }
// ---------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------
void AudioControlWM8731_F32::hp_filter(bool state) void AudioControlWM8731_F32::hp_filter(bool state)
{ {
modify(WM8731_REG_DIGITAL, WM8731_BITS_ADCHPD(state), WM8731_BITS_ADCHPD_MASK); modify(WM8731_REG_DIGITAL, WM8731_BITS_ADCHPD(state^1), WM8731_BITS_ADCHPD_MASK);
//write(WM8731_REG_DIGITAL, WM8731_BITS_DACMU(DACmute) | WM8731_BITS_ADCHPD(state)); //write(WM8731_REG_DIGITAL, WM8731_BITS_DACMU(DACmute) | WM8731_BITS_ADCHPD(state));
} }
// ---------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------

@ -232,9 +232,13 @@ void AudioEffectDelayStereo_F32::freeze(bool state)
{ {
feedb_tmp = feedb; // store the settings feedb_tmp = feedb; // store the settings
inputGain_tmp = inputGainSet; inputGain_tmp = inputGainSet;
bassCut_k_tmp = bassCut_k; // bassCut_k_tmp = bassCut_k;
trebleCut_k_tmp = trebleCut_k; // trebleCut_k_tmp = trebleCut_k;
__disable_irq(); __disable_irq();
flt0R.bypass_set(true);
flt1R.bypass_set(true);
flt0L.bypass_set(true);
flt1L.bypass_set(true);
feedb = 1.0f; // infinite echo feedb = 1.0f; // infinite echo
inputGainSet = freeze_ingain; inputGainSet = freeze_ingain;
__enable_irq(); __enable_irq();
@ -244,8 +248,12 @@ void AudioEffectDelayStereo_F32::freeze(bool state)
__disable_irq(); __disable_irq();
feedb = feedb_tmp; feedb = feedb_tmp;
inputGainSet = inputGain_tmp; inputGainSet = inputGain_tmp;
bassCut_k = bassCut_k_tmp; flt0R.bypass_set(false);
trebleCut_k = trebleCut_k_tmp; flt1R.bypass_set(false);
flt0L.bypass_set(false);
flt1L.bypass_set(false);
// bassCut_k = bassCut_k_tmp;
// trebleCut_k = trebleCut_k_tmp;
__enable_irq(); __enable_irq();
} }
} }

@ -76,12 +76,9 @@ public:
void setPan(float32_t p) void setPan(float32_t p)
{ {
float32_t tmp, gL, gR; float32_t gL, gR;
pan = constrain(p, -1.0f, 1.0f); pan = constrain(p, 0.0f, 1.0f);
tmp = (pan + 1.0f) * 0.5f; // map to 0..1 mix_pwr(pan, &panR, &panL);
mix_pwr(tmp, &panR, &panL);
gL = panL * gain; gL = panL * gain;
gR = panR * gain; gR = panR * gain;
@ -89,7 +86,6 @@ public:
gainLset = gL; gainLset = gL;
gainRset = gR; gainRset = gR;
__enable_irq(); __enable_irq();
} }
float32_t getPan() { return pan;} float32_t getPan() { return pan;}
void phase_inv(bool inv) void phase_inv(bool inv)

@ -1,6 +1,3 @@
#ifndef _EFFECT_GUITARBOOSTER_F32_H_
#define _EFFECT_GUITARBOOSTER_F32_H_
/** /**
* @file effect_guitarBooster_F32.h * @file effect_guitarBooster_F32.h
* @author Piotr Zapart * @author Piotr Zapart
@ -20,6 +17,8 @@
* You should have received a copy of the GNU General Public License along with this program. * You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>." * If not, see <https://www.gnu.org/licenses/>."
*/ */
#ifndef _EFFECT_GUITARBOOSTER_F32_H_
#define _EFFECT_GUITARBOOSTER_F32_H_
#include <AudioStream_F32.h> #include <AudioStream_F32.h>
#include "basic_DSPutils.h" #include "basic_DSPutils.h"
@ -78,7 +77,8 @@ public:
{ {
value = fabs(value); value = fabs(value);
value = constrain(value, 0.0f, 1.0f); value = constrain(value, 0.0f, 1.0f);
value = 1.0f + value * upsample_k * gainRange; // start with 0.5 - L+R are summed giving x2 gain
value = 0.5f + value * upsample_k * gainRange;
__disable_irq() __disable_irq()
gainSet = value; gainSet = value;
__enable_irq(); __enable_irq();
@ -168,7 +168,7 @@ private:
float32_t dryGain = 0.0f; float32_t dryGain = 0.0f;
float32_t wetGain = 1.0f; float32_t wetGain = 1.0f;
float32_t DCbias = 0.175f; float32_t DCbias = 0.175f;
float32_t gainRange = 4.0f; float32_t gainRange = 4.5f;
float32_t gainSet = 1.0f; // gain is in range 0.0 to 1.0, scaled to 0.0 to gainRange float32_t gainSet = 1.0f; // gain is in range 0.0 to 1.0, scaled to 0.0 to gainRange
float32_t gain = 0.0f; float32_t gain = 0.0f;
float32_t gain_hp = 1.0f; float32_t gain_hp = 1.0f;

@ -86,7 +86,7 @@ public:
} }
//sum L + R //sum L + R
arm_add_f32(p_sideChain_inL, p_sideChain_inR, blockSideCh->data, blockSideCh->length); arm_add_f32(p_sideChain_inL, p_sideChain_inR, blockSideCh->data, blockSideCh->length);
arm_scale_f32(blockSideCh->data, 0.5f, blockSideCh->data, blockSideCh->length); // divide by 2 arm_scale_f32(blockSideCh->data, sideChain_gain * 0.5f, blockSideCh->data, blockSideCh->length); // divide by 2
calcGain(blockSideCh, blockGain); calcGain(blockSideCh, blockGain);
calcSmoothedGain(blockGain); calcSmoothedGain(blockGain);
@ -153,6 +153,10 @@ public:
time = map_sat(time, 0.0f, 1.0f, NOISEGATE_HOLDT_MIN, NOISEGATE_HOLDT_MAX); time = map_sat(time, 0.0f, 1.0f, NOISEGATE_HOLDT_MIN, NOISEGATE_HOLDT_MAX);
setHoldTime(time); setHoldTime(time);
} }
void setSideChainGain(float g)
{
sideChain_gain = g;
}
bool infoIsOpen() bool infoIsOpen()
{ {
@ -176,6 +180,7 @@ private:
float32_t fs = AUDIO_SAMPLE_RATE_EXACT; float32_t fs = AUDIO_SAMPLE_RATE_EXACT;
float32_t* p_sideChain_inL = NULL; float32_t* p_sideChain_inL = NULL;
float32_t* p_sideChain_inR = NULL; float32_t* p_sideChain_inR = NULL;
float32_t sideChain_gain = 1.0f;;
float32_t linearThreshold; float32_t linearThreshold;
float32_t prev_gain_dB = 0; float32_t prev_gain_dB = 0;
float32_t openingTimeConst, closingTimeConst; float32_t openingTimeConst, closingTimeConst;

@ -55,6 +55,8 @@ bool AudioEffectPlateReverb_F32::begin()
loop_allp_k = LOOP_ALLOP_COEFF; loop_allp_k = LOOP_ALLOP_COEFF;
rv_time_scaler = 1.0f; rv_time_scaler = 1.0f;
rv_time_k = 0.2f; rv_time_k = 0.2f;
pitch_semit = 0;
pitchShim_semit = 0;
if(!in_allp_1L.init(&in_allp_k)) return false; if(!in_allp_1L.init(&in_allp_k)) return false;
if(!in_allp_2L.init(&in_allp_k)) return false; if(!in_allp_2L.init(&in_allp_k)) return false;

@ -292,7 +292,9 @@ public:
void chorus(float c) void chorus(float c)
{ {
c = map(c, 0.0f, 1.0f, 1.0f, 100.0f); c = map(c, 0.0f, 1.0f, 1.0f, 100.0f);
__disable_irq();
LFO_AMPLset = (uint32_t)c; LFO_AMPLset = (uint32_t)c;
__enable_irq();
} }
/** /**
@ -305,9 +307,11 @@ public:
if (flags.freeze) return; // do not update the shimmer if in freeze mode if (flags.freeze) return; // do not update the shimmer if in freeze mode
s = constrain(s, 0.0f, 1.0f); s = constrain(s, 0.0f, 1.0f);
s = 2*s - s*s; s = 2*s - s*s;
__disable_irq();
pitchShimL.setMix(s); pitchShimL.setMix(s);
pitchShimR.setMix(s); pitchShimR.setMix(s);
shimmerRatio = s; shimmerRatio = s;
__enable_irq();
} }
/** /**
* @brief Sets the pitch of the shimmer effect * @brief Sets the pitch of the shimmer effect
@ -316,8 +320,10 @@ public:
*/ */
void shimmerPitch(float ratio) void shimmerPitch(float ratio)
{ {
__disable_irq();
pitchShimL.setPitch(ratio); pitchShimL.setPitch(ratio);
pitchShimR.setPitch(ratio); pitchShimR.setPitch(ratio);
__enable_irq();
} }
/** /**
* @brief Sets the shimmer effect pitch in semitones * @brief Sets the shimmer effect pitch in semitones
@ -326,9 +332,28 @@ public:
*/ */
void shimmerPitchSemitones(int8_t semitones) void shimmerPitchSemitones(int8_t semitones)
{ {
__disable_irq();
pitchShimL.setPitchSemintone(semitones); pitchShimL.setPitchSemintone(semitones);
pitchShimR.setPitchSemintone(semitones); pitchShimR.setPitchSemintone(semitones);
__enable_irq();
} }
/**
* @brief shimemr pitch set using built in semitzone table
*
*
* @param value float 0.0f to 1.0f
*/
void shimmerPitchNormalized(float32_t value)
{
value = constrain(value, 0.0f, 1.0f);
float32_t idx = map(value, 0.0f, 1.0f, 0.0f, (float32_t)sizeof(semitoneTable)+0.499f);
pitchShim_semit = semitoneTable[(uint8_t)idx];
__disable_irq();
pitchShimL.setPitchSemintone(pitchShim_semit);
pitchShimR.setPitchSemintone(pitchShim_semit);
__enable_irq();
}
int8_t shimmerPitch_get() {return pitchShim_semit;}
/** /**
* @brief set the reverb pitch. Range -12 to +24 * @brief set the reverb pitch. Range -12 to +24
* *
@ -336,9 +361,28 @@ public:
*/ */
void pitchSemitones(int8_t semitones) void pitchSemitones(int8_t semitones)
{ {
__disable_irq();
pitchL.setPitchSemintone(semitones); pitchL.setPitchSemintone(semitones);
pitchR.setPitchSemintone(semitones); pitchR.setPitchSemintone(semitones);
__enable_irq();
} }
/**
* @brief sets the reverb pitch using the built in table
* input range is float 0.0 to 1.0
*
* @param value
*/
void pitchNormalized(float32_t value)
{
value = constrain(value, 0.0f, 1.0f);
float32_t idx = map(value, 0.0f, 1.0f, 0.0f, (float32_t)sizeof(semitoneTable)+0.499f);
pitch_semit = semitoneTable[(uint8_t)idx];
__disable_irq();
pitchL.setPitchSemintone(pitch_semit);
pitchR.setPitchSemintone(pitch_semit);
__enable_irq();
}
int8_t pitch_get() {return pitch_semit;}
/** /**
* @brief Reverb pitch shifter dry/wet mixer * @brief Reverb pitch shifter dry/wet mixer
* *
@ -347,9 +391,11 @@ public:
void pitchMix(float s) void pitchMix(float s)
{ {
s = constrain(s, 0.0f, 1.0f); s = constrain(s, 0.0f, 1.0f);
__disable_irq();
pitchL.setMix(s); pitchL.setMix(s);
pitchR.setMix(s); pitchR.setMix(s);
pitchRatio = s; pitchRatio = s;
__enable_irq();
} }
private: private:
@ -451,6 +497,9 @@ private:
AudioBasicPitch pitchShimL; AudioBasicPitch pitchShimL;
AudioBasicPitch pitchShimR; AudioBasicPitch pitchShimR;
const int8_t semitoneTable[9] = {-12, -7, -5, -3, 0, 3, 5, 7, 12};
int8_t pitch_semit;
int8_t pitchShim_semit;
const float rv_time_k_max = 0.97f; const float rv_time_k_max = 0.97f;
float rv_time_k, rv_time_k_tmp; // reverb time coeff 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 float rv_time_scaler; // with high lodamp settings lower the max reverb time to avoid clipping

@ -0,0 +1,254 @@
/**
* @file effect_wahMono_F32.cpp
* @author Piotr Zapart
* @brief Mono WAH effect
* @version 0.1
* @date 2024-07-09
*
* @copyright Copyright (c) 2024 www.hexefx.com
*
* Implementation is based on the work of Transmogrifox
* https://cackleberrypines.net/transmogrifox/src/bela/inductor_wah_C_src/
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>."
*/
#include "effect_wahMono_F32.h"
#define k *1e3
#define nF *1e-9
const AudioEffectWahMono_F32::wah_componentValues_t AudioEffectWahMono_F32::compValues[WAH_MODEL_LAST] =
{// Lp Cf Ci Rpot Ri Rs Rp, Rc Rbias Re beta name
{0.50f, 10.0f nF, 10.0f nF, 100.0f k, 68.0f k, 1.5f k, 33.0f k, 22.0f k, 470.0f k, 390.0f, 250.0f}, // G1
{0.50f, 10.0f nF, 10.0f nF, 100.0f k, 68.0f k, 1.5f k, 33.0f k, 22.0f k, 470.0f k, 510.0f, 650.0f}, // G2
{0.66f, 10.0f nF, 10.0f nF, 100.0f k, 68.0f k, 1.5f k, 33.0f k, 22.0f k, 470.0f k, 390.0f, 250.0f}, // G3
{0.50f, 10.0f nF, 10.0f nF, 100.0f k, 68.0f k, 1.5f k, 100.0f k,22.0f k, 470.0f k, 470.0f, 250.0f}, // G4
{0.50f, 15.0f nF, 8.0f nF, 100.0f k, 220.0f k, 0.1f k, 47.0f k, 22.0f k, 470.0f k, 510.0f, 250.0f}, // VOCAL
{0.50f, 10.0f nF, 47.0f nF, 100.0f k, 68.0f k, 1.5f k,150.0f k, 22.0f k, 470.0f k, 150.0f, 250.0f}, // EXTREME
{0.66f, 10.0f nF, 10.0f nF, 100.0f k, 68.0f k, 1.5f k,33.0f k, 22.0f k, 470.0f k, 470.0f, 250.0f}, // CUSTOM
{0.50f, 100.0f nF, 10.0f nF, 100.0f k, 100.0f k, 1.5f k,100.0f k, 22.0f k, 470.0f k, 470.0f, 200.0f} // BASS
};
#undef k
#undef nF
void AudioEffectWahMono_F32::update()
{
audio_block_f32_t *blockL, *blockR, *blockMod;
float32_t a0, a1, a2, ax, drySig;
uint16_t i;
if (bp) // handle bypass
{
blockL = AudioStream_F32::receiveReadOnly_f32(0);
blockR = AudioStream_F32::receiveReadOnly_f32(1);
if (!blockL || !blockR)
{
if (blockL)
AudioStream_F32::release(blockL);
if (blockR)
AudioStream_F32::release(blockR);
return;
}
AudioStream_F32::transmit(blockL, 0);
AudioStream_F32::transmit(blockR, 1);
AudioStream_F32::release(blockL);
AudioStream_F32::release(blockR);
return;
}
blockL = AudioStream_F32::receiveWritable_f32(0);
blockR = AudioStream_F32::receiveWritable_f32(1);
if (!blockL || !blockR)
{
if (blockL)
AudioStream_F32::release(blockL);
if (blockR)
AudioStream_F32::release(blockR);
return;
}
blockMod = AudioStream_F32::receiveReadOnly_f32(2);
arm_add_f32(blockL->data, blockR->data, blockL->data, blockL->length); // add two channels
arm_scale_f32(blockL->data, input_gain, blockL->data, blockL->length);
for (i=0; i<blockL->length; i++)
{
//variable gp is the pot gain, nominal range 0.0 to 1.0
//although this can be abused for extended range.
//A value less than zero would make the filter go unstable
//and put a lot of NaNs in your audio output
if (blockMod)
{
// TODO: scale the range to 0.0-1.0
gp = blockMod->data[i];
}
if(gp < 0.0f) gp = 0.0f;
float32_t gp_scaled = map_sat(gp, 0.0f, 1.0f, gp_top, gp_btm);
//The magic numbers below approximate frequency warping characteristic
float32_t gw = 4.6f-18.4f/(4.0f + gp_scaled);
//Update Biquad coefficients
a0 = a0b + gw*a0c;
a1 = -(a1b + gw*a1c);
a2 = -(a2b + gw*a2c);
ax = 1.0f/a0;
drySig = blockL->data[i];
//run it through the 1-pole HPF and gain first
float32_t hpf = ghpf * (drySig - xh1) - a1p*yh1;
xh1 = drySig;
yh1 = hpf;
//Apply modulated biquad
float32_t y0 = b0*hpf + b1*x1 + b2*x2 + a1*y1 + a2*y2;
y0 *= ax;
float32_t out = clip(y0);
y0 = 0.95f*y0 + 0.05f*out; //Let a little harmonic distortion feed back into the filter loop
x2 = x1;
x1 = hpf;
y2 = y1;
y1 = y0;
out = out * wet_gain + drySig * dry_gain;
blockL->data[i] = out;
blockR->data[i] = out;
}
AudioStream_F32::transmit(blockL, 0); // send blockL on both output channels
AudioStream_F32::transmit(blockL, 1);
AudioStream_F32::release(blockL);
AudioStream_F32::release(blockR);
if (blockMod) AudioStream_F32::release(blockMod);
}
FLASHMEM void AudioEffectWahMono_F32::setModel(wahModel_t model)
{
if (model > WAH_MODEL_LAST) model = WAH_MODEL_G1;
float32_t _b0, _b1, _b2;
float32_t _a0c, _a1c, _a2c;
float32_t _b0h, _b1h, _b2h;
float32_t _b0b, _b2b;
float32_t _a0b, _a1b, _a2b;
float32_t _a1p, _ghpf;
//helper variables
float32_t ro = 0.0f;
float32_t re = 0.0f;
float32_t Req = 0.0f;
float32_t ic = 0.0f;
float32_t RpRi, f0, w0, Q, c, s, alpha;
//Equivalent output resistance seen from BJT collector
ro = compValues[model].Rc * compValues[model].Rpot / (compValues[model].Rc + compValues[model].Rpot) ;
ro = ro * compValues[model].Rbias / (ro + compValues[model].Rbias);
ic = 3.7f/compValues[model].Rc; //Typical bias current
re = 0.025f/ic; //BJT gain stage equivalent internal emitter resistance
// gm = Ic/Vt, re = 1/gm
Req = compValues[model].Re + re;
gf = -ro/Req; //forward gain of transistor stage
re = compValues[model].beta*Req; //Resistance looking into BJT emitter
Rp = compValues[model].Rp*re/(re + compValues[model].Rp);
RpRi = Rp*compValues[model].Ri/(Rp + compValues[model].Ri);
f0 = 1.0f/(2.0f*M_PI*sqrtf(compValues[model].Lp*compValues[model].Cf));
w0 = 2.0f*M_PI*f0/fs;
Q = RpRi*sqrtf(compValues[model].Cf/compValues[model].Lp);
c = cosf(w0);
s = sinf(w0);
alpha = s/(2.0f*Q);
//High Pass Biquad Coefficients
_b0h = (1.0f + c)/2.0f;
_b1h = -(1.0f + c);
_b2h = (1.0f + c)/2.0f;
//Band-pass biquad coefficients
_b0b = Q*alpha;
_b2b = -Q*alpha;
_a0b = 1.0f + alpha;
_a1b = -2.0f*c;
_a2b = 1.0f - alpha;
//1-pole high pass filter coefficients
// H(z) = g * (1 - z^-1)/(1 - a1*z^-1)
// Direct Form 1:
// y[n] = ghpf * ( x[n] - x[n-1] ) - a1p*y[n-1]
_a1p = -expf(-1.0f/(compValues[model].Ri*compValues[model].Ci*fs));
_ghpf = gf*(1.0f - a1p)*0.5f; //BJT forward gain worked in here to
// save extra multiplications in
// updating biquad coefficients
//Distill all down to final biquad coefficients
float32_t Gi = compValues[model].Rs/(compValues[model].Ri + compValues[model].Rs);
float32_t gbpf = 1.0f/(2.0f*M_PI*f0*compValues[model].Ri*compValues[model].Cf); //band-pass component equivalent gain
//Final Biquad numerator coefficients
_b0 = gbpf*b0b + Gi*a0b;
_b1 = Gi*a1b;
_b2 = gbpf*b2b + Gi*a2b;
//Constants to make denominator coefficients computation more efficient
//in real-time
_a0c = -gf*b0h;
_a1c = -gf*b1h;
_a2c = -gf*b2h;
__disable_irq();
b0h = _b0h;
b1h = _b1h;
b2h = _b2h;
b0b = _b0b;
b2b = _b2b;
a0b = _a0b;
a1b = _a1b;
a2b = _a2b;
a1p = _a1p;
ghpf = _ghpf;
b0 = _b0;
b1 = _b1;
b2 = _b2;
a0c = _a0c;
a1c = _a1c;
a2c = _a2c;
y1 = 0.0f; //biquad state variables
y2 = 0.0f;
x1 = 0.0f;
x2 = 0.0f;
//First order high-pass filter state variables
yh1 = 0.0f;
xh1 = 0.0f;
__enable_irq();
}
float32_t AudioEffectWahMono_F32::clip(float32_t x)
{
float32_t thrs = 0.8f;
float32_t nthrs = -0.72f;
float32_t f=1.25f;
//Hard limiting
if(x >= 1.2f) x = 1.2f;
if(x <= -1.12f) x = -1.12f;
//Soft clipping
if(x > thrs)
{
x -= f*sqr(x - thrs);
}
if(x < nthrs){
x += f*sqr(x - nthrs);
}
return x;
}

@ -0,0 +1,167 @@
/**
* @file effect_wahMono_F32.h
* @author Piotr Zapart
* @brief Mono WAH effect
* @version 0.1
* @date 2024-07-09
*
* @copyright Copyright (c) 2024 www.hexefx.com
*
* Implementation is based on the work of Transmogrifox
* https://cackleberrypines.net/transmogrifox/src/bela/inductor_wah_C_src/
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>."
*/
#ifndef _EFFECT_WAHMONO_F32_H_
#define _EFFECT_WAHMONO_F32_H_
#include <Arduino.h>
#include "AudioStream_F32.h"
#include "basic_DSPutils.h"
typedef enum
{
WAH_MODEL_G1 = 0,
WAH_MODEL_G2,
WAH_MODEL_G3,
WAH_MODEL_G4,
WAH_MODEL_VOCAL,
WAH_MODEL_EXTREME,
WAH_MODEL_CUSTOM,
WAH_MODEL_BASS,
WAH_MODEL_LAST
}wahModel_t;
class AudioEffectWahMono_F32 : public AudioStream_F32
{
public:
AudioEffectWahMono_F32(void) : AudioStream_F32(3, inputQueueArray_f32)
{
setModel(WAH_MODEL_G3);
}
// Alternate specification of block size. Sample rate does not apply for analyze_rms
AudioEffectWahMono_F32(const AudioSettings_F32 &settings) : AudioStream_F32(3, inputQueueArray_f32)
{
block_size = settings.audio_block_samples;
fs = settings.sample_rate_Hz;
setModel(WAH_MODEL_G3);
}
virtual void update();
void setModel(wahModel_t model);
void setFreq(float32_t val)
{
val = constrain(val, 0.0f, 1.0f);
val = 1.0f - val;
__disable_irq();
gp = val;
__enable_irq();
}
void setRange(float32_t heel, float32_t toe)
{
gp_top = 1.0f - constrain(heel, 0.0f, 1.0f);
gp_btm = 1.0f - constrain(toe, 0.0f, 1.0f);
}
void setMix(float32_t mix)
{
mix = constrain(mix, 0.0f, 1.0f);
float32_t dry, wet;
mix_pwr(mix, &wet, &dry);
__disable_irq();
dry_gain = dry;
wet_gain = wet;
__enable_irq();
}
bool bypass_get(void) {return bp;}
void bypass_set(bool state) {bp = state;}
bool bypass_tgl(void)
{
bp ^= 1;
return bp;
}
private:
bool bp = true; // bypass flag
audio_block_f32_t *inputQueueArray_f32[3];
uint16_t block_size = AUDIO_BLOCK_SAMPLES;
float32_t fs = AUDIO_SAMPLE_RATE_EXACT;
typedef struct
{
//Circuit parameters
//Using these makes it straight-forward to model other
//variants of the circuit
float32_t Lp; //RLC tank inductor
float32_t Cf; //feedback capacitor
float32_t Ci; //input capacitor
float32_t Rpot; //Pot resistance value
float32_t Ri; //input feed resistor
float32_t Rs; //RLC tank to BJT base resistor (dry mix)
float32_t Rp; //resistor placed parallel with the inductor
//Gain-setting components
float32_t Rc; //BJT gain stage collector resistor
float32_t Rbias; //Typically 470k bias resistor shows up in parallel with output
float32_t Re; //BJT gain stage emitter resistor
float32_t beta; //BJT forward gain
}wah_componentValues_t;
static const wah_componentValues_t compValues[WAH_MODEL_LAST];
float32_t gp = 0.0f; // pot gain
float32_t input_gain = 0.5f;
float32_t dry_gain = 0.0f;
float32_t wet_gain = 1.0f;
float32_t gp_top = 0.0f;
float32_t gp_btm = 1.0f;
float32_t re; //equivalent resistance looking into input BJT base
float32_t Rp; //resistor placed parallel with the inductor
float32_t gf; //forward gain of BJT amplifier
//High-Pass biquad coefficients
float32_t b0h, b1h, b2h;
//Band-Pass biquad coefficients
float32_t b0b, b2b;
float32_t a0b, a1b, a2b;
//Final combined biquad coefficients used by run_filter()
float32_t b0;
float32_t b1;
float32_t b2;
float32_t a0c;
float32_t a1c;
float32_t a2c;
//First order high-pass filter coefficients
//y[n] = ghpf * ( x[n] - x[n-1] ) - a1p*y[n-1]
float32_t a1p;
float32_t ghpf;
//biquad state variables
float32_t y1;
float32_t y2;
float32_t x1;
float32_t x2;
//First order high-pass filter state variables
float32_t yh1;
float32_t xh1;
float32_t clip(float32_t x);
float32_t sqr(float32_t x)
{
return x*x;
}
};
#endif // _EFFECT_WAHMONO_F32_H_

@ -0,0 +1,153 @@
/**
* @file filter_DCblockerStereo_F32.h
* @author Piotr Zapart
* @brief simple IIR based stereo DB blocking filter
* @version 0.1
* @date 2024-06-01
*
* @copyright Copyright (c) 2024
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>."
*/
#ifndef _FILTER_DCBLOCKERSTEREO_F32_H_
#define _FILTER_DCBLOCKERSTEREO_F32_H_
#include <AudioStream_F32.h>
#include "basic_DSPutils.h"
#include <arm_math.h>
// y = x - xm1 + 0.995 * ym1;
// xm1 = x;
// ym1 = y;
class AudioFilterDCblockerStereo_F32 : public AudioStream_F32
{
public:
AudioFilterDCblockerStereo_F32(void) : AudioStream_F32(2, inputQueueArray)
{
fs_Hz = AUDIO_SAMPLE_RATE_EXACT;
blockSize = AUDIO_BLOCK_SAMPLES;
}
AudioFilterDCblockerStereo_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray)
{
fs_Hz = settings.sample_rate_Hz;
blockSize = settings.audio_block_samples;
}
void update()
{
audio_block_f32_t *blockL, *blockR;
uint16_t i;
float32_t tmpf32;
if (bp) // handle bypass
{
blockL = AudioStream_F32::receiveReadOnly_f32(0);
blockR = AudioStream_F32::receiveReadOnly_f32(1);
if (!blockL || !blockR)
{
if (blockL)
AudioStream_F32::release(blockL);
if (blockR)
AudioStream_F32::release(blockR);
return;
}
AudioStream_F32::transmit(blockL, 0);
AudioStream_F32::transmit(blockR, 1);
AudioStream_F32::release(blockL);
AudioStream_F32::release(blockR);
return;
}
blockL = AudioStream_F32::receiveWritable_f32(0);
blockR = AudioStream_F32::receiveWritable_f32(1);
if (!blockL || !blockR)
{
if (blockL)
AudioStream_F32::release(blockL);
if (blockR)
AudioStream_F32::release(blockR);
return;
}
float32_t _xRegL = xRegL;
float32_t _xRegR = xRegR;
float32_t _yRegL = yRegL;
float32_t _yRegR = yRegR;
for (i=0; i<blockSize; i=i+4)
{
tmpf32 = blockL->data[i] - _xRegL + k * _yRegL;
_yRegL = tmpf32;
_xRegL = blockL->data[i];
blockL->data[i] = _yRegL;
tmpf32 = blockL->data[i+1] - _xRegL + k * _yRegL;
_yRegL = tmpf32;
_xRegL = blockL->data[i+1];
blockL->data[i+1] = _yRegL;
tmpf32 = blockL->data[i+2] - _xRegL + k * _yRegL;
_yRegL = tmpf32;
_xRegL = blockL->data[i+2];
blockL->data[i+2] = _yRegL;
tmpf32 = blockL->data[i+3] - _xRegL + k * _yRegL;
_yRegL = tmpf32;
_xRegL = blockL->data[i+3];
blockL->data[i+3] = _yRegL;
tmpf32 = blockR->data[i] - _xRegR + k * _yRegR;
_yRegR = tmpf32;
_xRegR = blockR->data[i];
blockR->data[i] = _yRegR;
tmpf32 = blockR->data[i+1] - _xRegR + k * _yRegR;
_yRegR = tmpf32;
_xRegR = blockR->data[i+1];
blockR->data[i+1] = _yRegR;
tmpf32 = blockR->data[i+2] - _xRegR + k * _yRegR;
_yRegR = tmpf32;
_xRegR = blockR->data[i+2];
blockR->data[i+2] = _yRegR;
tmpf32 = blockR->data[i+3] - _xRegR + k * _yRegR;
_yRegR = tmpf32;
_xRegR = blockR->data[i+3];
blockR->data[i+3] = _yRegR;
}
xRegL = _xRegL;
yRegL = _yRegL;
xRegR = _xRegR;
yRegR = _yRegR;
AudioStream_F32::transmit(blockL, 0); // send blockL on both output channels
AudioStream_F32::transmit(blockL, 1);
AudioStream_F32::release(blockL);
AudioStream_F32::release(blockR);
}
private:
audio_block_f32_t *inputQueueArray[2];
float32_t fs_Hz;
uint16_t blockSize;
float k = 0.995f;
bool bp = false; // bypass flag
float32_t xRegL = 0.0f;
float32_t xRegR = 0.0f;
float32_t yRegL = 0.0f;
float32_t yRegR = 0.0f;
};
#endif // _FILTER_DCBLOCKERSTEREO_F32_H_

@ -4,6 +4,8 @@
#include "control_WM8731_F32.h" #include "control_WM8731_F32.h"
#include "control_SGTL5000_F32.h" #include "control_SGTL5000_F32.h"
#include "control_ES8388_F32.h" #include "control_ES8388_F32.h"
#include "control_AK4452_F32.h"
#include "control_AK5552_F32.h"
#include "input_i2s_ext_F32.h" #include "input_i2s_ext_F32.h"
#include "output_i2s_ext_F32.h" // extended version #include "output_i2s_ext_F32.h" // extended version
@ -17,6 +19,7 @@
#include "filter_equalizer_F32.h" #include "filter_equalizer_F32.h"
#include "filter_3bandeq.h" #include "filter_3bandeq.h"
#include "filter_biquadStereo_F32.h" #include "filter_biquadStereo_F32.h"
#include "filter_DCblockerStereo_F32.h"
#include "effect_gainStereo_F32.h" #include "effect_gainStereo_F32.h"
#include "effect_platereverb_F32.h" #include "effect_platereverb_F32.h"
@ -30,5 +33,6 @@
#include "effect_compressorStereo_F32.h" #include "effect_compressorStereo_F32.h"
#include "effect_guitarBooster_F32.h" #include "effect_guitarBooster_F32.h"
#include "effect_xfaderStereo_F32.h" #include "effect_xfaderStereo_F32.h"
#include "effect_wahMono_F32.h"
#endif // _HEXEFX_AUDIO_H #endif // _HEXEFX_AUDIO_H

@ -178,8 +178,8 @@ void AudioInputI2S2_F32::update(void)
block_right_f32 = new_right; block_right_f32 = new_right;
block_offset = 0; block_offset = 0;
__enable_irq(); __enable_irq();
update_1chan(0, out_left); // uses audio_block_samples and update_counter update_1chan(0^channel_swap, out_left); // uses audio_block_samples and update_counter
update_1chan(1, out_right); // uses audio_block_samples and update_counter update_1chan(1^channel_swap, out_right); // uses audio_block_samples and update_counter
} }
else if (new_left != NULL) else if (new_left != NULL)
{ {

@ -60,6 +60,8 @@ public:
int get_isOutOfMemory(void) { return flag_out_of_memory; } int get_isOutOfMemory(void) { return flag_out_of_memory; }
void clear_isOutOfMemory(void) { flag_out_of_memory = 0; } void clear_isOutOfMemory(void) { flag_out_of_memory = 0; }
bool get_update_responsibility() { return update_responsibility;} bool get_update_responsibility() { return update_responsibility;}
void set_channel_swap(bool sw) { channel_swap = sw ? 1 : 0;}
bool get_channel_swap() {return (bool)channel_swap;}
protected: protected:
AudioInputI2S2_F32(int dummy): AudioStream_F32(0, NULL) {} // to be used only inside AudioInputI2Sslave !! AudioInputI2S2_F32(int dummy): AudioStream_F32(0, NULL) {} // to be used only inside AudioInputI2Sslave !!
static bool update_responsibility; static bool update_responsibility;
@ -74,6 +76,7 @@ private:
static uint16_t block_offset; static uint16_t block_offset;
static int flag_out_of_memory; static int flag_out_of_memory;
static unsigned long update_counter; static unsigned long update_counter;
uint8_t channel_swap = 0;
}; };
class AudioInputI2S2slave_F32 : public AudioInputI2S2_F32 class AudioInputI2S2slave_F32 : public AudioInputI2S2_F32

@ -199,8 +199,8 @@ void AudioInputI2S_ext_F32::update(void)
__enable_irq(); __enable_irq();
// update_counter++; //I chose to update it in the ISR instead. // 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(0^channel_swap, out_left); // uses audio_block_samples and update_counter
update_1chan(1, out_right); // uses audio_block_samples and update_counter update_1chan(1^channel_swap, out_right); // uses audio_block_samples and update_counter
} }
else if (new_left != NULL) else if (new_left != NULL)
{ {

@ -58,7 +58,8 @@ public:
void begin(void); void begin(void);
int get_isOutOfMemory(void) { return flag_out_of_memory; } int get_isOutOfMemory(void) { return flag_out_of_memory; }
void clear_isOutOfMemory(void) { flag_out_of_memory = 0; } void clear_isOutOfMemory(void) { flag_out_of_memory = 0; }
void set_channel_swap(bool sw) { channel_swap = sw ? 1 : 0;}
bool get_channel_swap() {return (bool)channel_swap;}
protected: protected:
AudioInputI2S_ext_F32(int dummy) : AudioStream_F32(0, NULL) {} // to be used only inside AudioInputI2Sslave !! AudioInputI2S_ext_F32(int dummy) : AudioStream_F32(0, NULL) {} // to be used only inside AudioInputI2Sslave !!
static bool update_responsibility; static bool update_responsibility;
@ -74,7 +75,7 @@ private:
static uint16_t block_offset; static uint16_t block_offset;
static int flag_out_of_memory; static int flag_out_of_memory;
static unsigned long update_counter; static unsigned long update_counter;
static bool msbFirstMode; // some codecs like the new AKM series (AK4558) use MSB exclusively uint8_t channel_swap = 0;
}; };
class AudioInputI2Sslave_ext_F32 : public AudioInputI2S_ext_F32 class AudioInputI2Sslave_ext_F32 : public AudioInputI2S_ext_F32

@ -202,7 +202,7 @@ void AudioOutputI2S2_F32::update(void)
return; return;
} }
// now that we have our working memory, proceed with getting the audio data and processing // now that we have our working memory, proceed with getting the audio data and processing
block_f32 = receiveReadOnly_f32(0); // input 0 = left channel block_f32 = receiveReadOnly_f32(0^channel_swap); // input 0 = left channel
if (block_f32) if (block_f32)
{ {
if (block_f32->length != audio_block_samples) if (block_f32->length != audio_block_samples)
@ -247,7 +247,7 @@ void AudioOutputI2S2_F32::update(void)
} }
block_f32_scaled = block2_f32_scaled; // this is simply renaming the pre-allocated buffer block_f32_scaled = block2_f32_scaled; // this is simply renaming the pre-allocated buffer
block_f32 = receiveReadOnly_f32(1); // input 1 = right channel block_f32 = receiveReadOnly_f32(1^channel_swap); // input 1 = right channel
if (block_f32) if (block_f32)
{ {
scale_float_to_int32range(block_f32->data, block_f32_scaled->data, audio_block_samples); scale_float_to_int32range(block_f32->data, block_f32_scaled->data, audio_block_samples);

@ -63,6 +63,8 @@ public:
friend class AudioOutputI2SQuad_F32; friend class AudioOutputI2SQuad_F32;
friend class AudioInputI2SQuad_F32; friend class AudioInputI2SQuad_F32;
bool get_update_responsibility() { return update_responsibility;} bool get_update_responsibility() { return update_responsibility;}
void set_channel_swap(bool sw) { channel_swap = sw ? 1 : 0;}
bool get_channel_swap() {return (bool)channel_swap;}
protected: protected:
AudioOutputI2S2_F32(int dummy): AudioStream_F32(2, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !! AudioOutputI2S2_F32(int dummy): AudioStream_F32(2, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !!
static void config_i2s(void); static void config_i2s(void);
@ -84,6 +86,7 @@ private:
static float sample_rate_Hz; static float sample_rate_Hz;
static int audio_block_samples; static int audio_block_samples;
volatile uint8_t enabled = 1; volatile uint8_t enabled = 1;
uint8_t channel_swap = 0;
}; };
class AudioOutputI2S2slave_F32 : public AudioOutputI2S2_F32 class AudioOutputI2S2slave_F32 : public AudioOutputI2S2_F32

@ -203,7 +203,7 @@ void AudioOutputI2S_ext_F32::update(void)
return; return;
} }
// now that we have our working memory, proceed with getting the audio data and processing // now that we have our working memory, proceed with getting the audio data and processing
block_f32 = receiveReadOnly_f32(0); // input 0 = left channel block_f32 = receiveReadOnly_f32(0^channel_swap); // input 0 = left channel
if (block_f32) if (block_f32)
{ {
if (block_f32->length != audio_block_samples) if (block_f32->length != audio_block_samples)
@ -251,7 +251,7 @@ void AudioOutputI2S_ext_F32::update(void)
block_f32_scaled = block2_f32_scaled; // this is simply renaming the pre-allocated buffer block_f32_scaled = block2_f32_scaled; // this is simply renaming the pre-allocated buffer
block_f32 = receiveReadOnly_f32(1); // input 1 = right channel block_f32 = receiveReadOnly_f32(1^channel_swap); // input 1 = right channel
if (block_f32) if (block_f32)
{ {
// Optional scaling for easy volume control. Leave outputScale==1.0f for default // Optional scaling for easy volume control. Leave outputScale==1.0f for default

@ -60,6 +60,8 @@ public:
void setGain(float _oscale) {outputScale = _oscale; } void setGain(float _oscale) {outputScale = _oscale; }
virtual void update(void); virtual void update(void);
void begin(void); void begin(void);
void set_channel_swap(bool sw) { channel_swap = sw ? 1 : 0;}
bool get_channel_swap() {return (bool)channel_swap;}
friend class AudioInputI2S_ext_F32; friend class AudioInputI2S_ext_F32;
#if defined(__IMXRT1062__) #if defined(__IMXRT1062__)
friend class AudioOutputI2SQuad_F32; friend class AudioOutputI2SQuad_F32;
@ -84,6 +86,7 @@ private:
static int audio_block_samples; static int audio_block_samples;
volatile uint8_t enabled = 1; volatile uint8_t enabled = 1;
float outputScale = 1.0f; // Quick volume control float outputScale = 1.0f; // Quick volume control
uint8_t channel_swap = 0;
}; };
class AudioOutputI2Sslave_ext_F32 : public AudioOutputI2S_ext_F32 class AudioOutputI2Sslave_ext_F32 : public AudioOutputI2S_ext_F32

Loading…
Cancel
Save