diff --git a/src/basic_DSPutils.h b/src/basic_DSPutils.h index 2c3750c..a5593bd 100644 --- a/src/basic_DSPutils.h +++ b/src/basic_DSPutils.h @@ -61,4 +61,17 @@ inline void memcpyDeinterleave_f32(float32_t *src, float32_t *dstA, float32_t *d } } inline void memcpyDeinterleave_f32(float32_t *src, float32_t *dstA, float32_t *dstB, int16_t sz); + + +// added input saturation +template +T map_sat(T x, A in_min, B in_max, C out_min, D out_max, typename std::enable_if::value >::type* = 0) +{ + if (x <= in_min) return out_min; + else if (x >= in_max) return out_max; + // when the input is a float or double, do all math using the input's type + return (x - (T)in_min) * ((T)out_max - (T)out_min) / ((T)in_max - (T)in_min) + (T)out_min; +} + + #endif // _BASIC_DSPUTILS_H_ diff --git a/src/control_ES8388_F32.cpp b/src/control_ES8388_F32.cpp new file mode 100644 index 0000000..b0a4671 --- /dev/null +++ b/src/control_ES8388_F32.cpp @@ -0,0 +1,625 @@ +#include "control_ES8388_F32.h" + + + +#define ES8388_REG_CHIP_CTRL1 (0x00) // Default 0000 0110 +#define ES8388_REG_CHIP_CTRL1_DFLT (0x06) + #define ES8388_BIT_SCPRESET (1<<7) // 1=reset registers to default + #define ES8388_BIT_LRCM (1<<6) + #define ES8388_BIT_DACMCLK (1<<5) + #define ES8388_BIT_SAMEFS (1<<4) + #define ES8388_BIT_SEQEN (1<<3) + #define ES8388_BIT_ENREF (1<<2) + #define ES8388_VMIDSEL_DIS (0x00) + #define ES8388_VMIDSEL_50K (0x01) + #define ES8388_VMIDSEL_500K (0x02) + #define ES8388_VMIDSEL_5K (0x03) +#define ES8388_REG_CHIP_CTRL2 (0x01) // Default 0101 1100 +#define ES8388_REG_CHIP_CTRL2_DFLT (0x5C) + #define ES8388_BIT_LPVCMMOD (1<<5) + #define ES8388_BIT_LPVREFBUF (1<<4) + #define ES8388_BIT_PDNANA (1<<3) + #define ES8388_BIT_PDBIBIASGEN (1<<2) + #define ES8388_BIT_VREFLO (1<<1) + #define ES8388_BIT_PDBVREFBUF (1<<0) +#define ES8388_REG_CHIP_PWR_MAN (0x02) // Default 1100 0011 +#define ES8388_REG_CHIP_PWR_MAN_DFLT (0xC3) + #define ES8388_BIT_ADC_DIGPDN (1<<7) + #define ES8388_BIT_DAC_DIGPDN (1<<6) + #define ES8388_BIT_ADCSTMRST (1<<5) + #define ES8388_BIT_DACSTMRST (1<<4) + #define ES8388_BIT_ADCDLL_PDN (1<<3) + #define ES8388_BIT_DACDLL_PDN (1<<2) + #define ES8388_BIT_ADCVREFPDN (1<<1) + #define ES8388_BIT_DACVREFPDN (1<<0) +#define ES8388_REG_ADC_PWR_MAN (0x03) // Default 1111 1100 +#define ES8388_REG_ADC_PWR_MAN_DFLT (0xFC) + #define ES8388_BIT_PDNAINL (1<<7) + #define ES8388_BIT_PDNAINR (1<<6) + #define ES8388_BIT_PDNADCL (1<<5) + #define ES8388_BIT_PDNADCR (1<<4) + #define ES8388_BIT_PDNMICB (1<<3) + #define ES8388_BIT_PDNADCBIASG (1<<2) + #define ES8388_BIT_FLASHLP (1<<1) + #define ES8388_BIT_INT1LP (1<<0) +#define ES8388_REG_DAC_PWR_MAN (0x04) // Default 1100 0000 +#define ES8388_REG_DAC_PWR_MAN_DFLT (0xC0) + #define ES8388_BIT_PDNDACL (1<<7) + #define ES8388_BIT_PDNDACR (1<<6) + #define ES8388_BIT_LOUT1_EN (1<<5) + #define ES8388_BIT_ROUT1_EN (1<<4) + #define ES8388_BIT_LOUT2_EN (1<<3) + #define ES8388_BIT_ROUT2_EN (1<<2) +#define ES8388_REG_CHIP_LOWPWR1 (0x05) // Default 0000 0000 +#define ES8388_REG_CHIP_LOWPWR1_DFLT (0x00) + #define ES8388_BIT_LPDACL (1<<7) + #define ES8388_BIT_LPDACR (1<<6) + #define ES8388_BIT_LPOUT1 (1<<5) + #define ES8388_BIT_LPOUT2 (1<<3) +#define ES8388_REG_CHIP_LOWPWR2 (0x06) // Default 0000 0000 +#define ES8388_REG_CHIP_LOWPWR2_DFLT (0x00) + #define ES8388_BIT_LPPGA (1<<7) + #define ES8388_BIT_LPMIX (1<<6) + #define ES8388_BIT_LPADCVRP (1<<1) + #define ES8388_BIT_LPDACVRP (1<<0) +#define ES8388_REG_ANALOG_VOLT_MAN (0x07) // Default 0111 1100 +#define ES8388_REG_ANALOG_VOLT_MAN_DFLT (0x7C) + #define ES8388_BIT_VSEL_MASK (0x7C) +#define ES8388_REG_MASTER_MODE_CTRL (0x08) // Default 1000 0000 +#define ES8388_REG_MASTER_MODE_CTRL_DFLT (0x80) + #define ES8388_BIT_MSC (1<<7) + #define ES8388_BIT_MCLKDIV2 (1<<6) + #define ES8388_BIT_BCLKINV (1<<5) + #define ES8388_BCLKDIVAUTO (0x00) + #define ES8388_BCLKDIV1 (0x01) + #define ES8388_BCLKDIV2 (0x02) + #define ES8388_BCLKDIV3 (0x03) + #define ES8388_BCLKDIV4 (0x04) + #define ES8388_BCLKDIV6 (0x05) + #define ES8388_BCLKDIV8 (0x06) + #define ES8388_BCLKDIV9 (0x07) + #define ES8388_BCLKDIV11 (0x08) + #define ES8388_BCLKDIV12 (0x09) + #define ES8388_BCLKDIV16 (0x0A) + #define ES8388_BCLKDIV18 (0x0B) + #define ES8388_BCLKDIV22 (0x0C) + #define ES8388_BCLKDIV24 (0x0D) + #define ES8388_BCLKDIV33 (0x0E) + #define ES8388_BCLKDIV36 (0x0F) + #define ES8388_BCLKDIV44 (0x10) + #define ES8388_BCLKDIV48 (0x11) + #define ES8388_BCLKDIV66 (0x12) + #define ES8388_BCLKDIV72 (0x13) + #define ES8388_BCLKDIV5 (0x14) + #define ES8388_BCLKDIV10 (0x15) + #define ES8388_BCLKDIV15 (0x16) + #define ES8388_BCLKDIV17 (0x17) + #define ES8388_BCLKDIV20 (0x18) + #define ES8388_BCLKDIV25 (0x19) + #define ES8388_BCLKDIV30 (0x1A) + #define ES8388_BCLKDIV32 (0x1B) + #define ES8388_BCLKDIV34 (0x1C) + #define ES8388_BCLKDIV(x) ((x)&0x1F) +#define ES8388_REG_ADC_CTRL1 (0x09) // Default 0000 0000 +#define ES8388_REG_ADC_CTRL1_DFLT (0x00) + #define ES8388_MICAMPL_MASK (0xF0) + #define ES8388_MICAMPL_SHIFT (0x04) + #define ES8388_MICAMPR_MASK (0x0F) + #define ES8388_MICAMPR_SHIFT (0x00) +#define ES8388_REG_ADC_CTRL2 (0x0A) // Default 0000 0000 +#define ES8388_REG_ADC_CTRL2_DFLT (0x00) + #define ES8388_LINSEL_MASK (0xC0) + #define ES8388_LINSEL_SHIFT (0x06) + #define ES8388_INPUT1 (0x00) + #define ES8388_INPUT2 (0x01) + #define ES8388_INPUTDIFF (0x03) + #define ES8388_LINSEL(x) (((x)<begin(); + ctrlBus->setClock(100000); + bool reply = true; + + reply = writeReg(ES8388_REG_MASTER_MODE_CTRL, 0x00); // set to slave mode + reply &= writeReg(ES8388_REG_CHIP_PWR_MAN, 0xF3); // power down + reply &=writeReg(ES8388_REG_DAC_CTRL21, ES8388_BIT_SLRCK); // DACLRC = ADCLRC + reply &=writeReg(ES8388_REG_CHIP_CTRL1, ES8388_VMIDSEL_5K | ES8388_BIT_ENREF); // 50k divider, + reply &=writeReg(ES8388_REG_CHIP_CTRL2, 0x40); // low power modes off, bit6 not defined? based on default value + reply &=writeReg(ES8388_REG_ADC_PWR_MAN, 0x00); // power up ADC, turn off the PDNMICB? + reply &=writeReg(ES8388_REG_DAC_PWR_MAN, ES8388_BIT_LOUT1_EN | ES8388_BIT_ROUT1_EN); // enable LR1 + if (reply == false) + { + DBG_SERIAL.println("Codec i2c error"); + return false; + } + switch (cfg) + { + case ES8388_CFG_LINEIN_DIFF: + writeReg(ES8388_REG_ADC_CTRL2, ES8388_LINSEL(ES8388_INPUTDIFF) | // LIN=LIN1-RIN1 (ADCCTRL3[7] DS = 0) + ES8388_RINSEL(ES8388_INPUTDIFF) | // RIN=LIN2-RIN2 (DSR = 1) + ES8388_BIT_DSSEL | // use different DSR settings for L and R + ES8388_BIT_DSR); // DS setting for channel R + break; + case ES8388_CFG_LINEIN_SINGLE_ENDED: + writeReg(ES8388_REG_ADC_CTRL2, ES8388_LINSEL(ES8388_INPUT1) | // LIN=LIN1-RIN1 (ADCCTRL3[7] DS = 0) + ES8388_RINSEL(ES8388_INPUT1)); // RIN=LIN2-RIN2 (DSR = 1) + break; + default: + writeReg(ES8388_REG_ADC_CTRL2, ES8388_LINSEL(ES8388_INPUT1) | // LIN=LIN1-RIN1 (ADCCTRL3[7] DS = 0) + ES8388_RINSEL(ES8388_INPUT1)); // RIN=LIN2-RIN2 (DSR = 1) + break; + } + writeReg(ES8388_REG_ADC_CTRL6, 0x00); // disable HPF + // 0dB + writeReg(ES8388_REG_ADC_CTRL4, ES8388_ADCWL(ES8388_ADCWL_32BIT)); // 24bit + writeReg(ES8388_REG_ADC_CTRL5, ES8388_ADCFSRATIO(ES8388_FSRATIO_256)); // 256*Fs, single speed + // ADC digital volume + writeReg(ESP8388_LADCVOL, 0x00); // 0dB + writeReg(ESP8388_RADCVOL, 0x00); // 0dB + // DAC setup + writeReg(ES8388_REG_DAC_CTRL1, ES8388_DACWL(ES8388_DACWL_32BIT)); // 24bit + writeReg(ES8388_REG_DAC_CTRL2, ES8388_DACFSRATIO(ES8388_FSRATIO_256)); // 256*Fs single speed + // DAC digital volume + writeReg(ES8388_REG_DAC_CTRL4, 0x00); // 0dB + writeReg(ES8388_REG_DAC_CTRL5, 0x00); // 0dB + // Mixer Setup + writeReg(ES8388_REG_DAC_CTRL16, ES8388_LMIXSEL(ES8388_LMIXSEL_ADCL_P) | + ES8388_RMIXSEL(ES8388_RMIXSEL_ADCR_P)); + writeReg(ES8388_REG_DAC_CTRL17, ES8388_BIT_LD2LO); // LDAC to left mixer enable, gain 0dB + writeReg(ES8388_REG_DAC_CTRL20, ES8388_BIT_RD2RO); // RDAC to right mixer enable, gain 0dB + + // R L OUT volume + dacGain = 0x1E; + writeReg(ES8388_LOUT1VOL, dacGain); // L1 0dB + writeReg(ES8388_ROUT1VOL, dacGain); // R1 0dB + // optimize A/D conversion for 1/4 Vrms range + optimizeConversion(0); + writeReg(ES8388_REG_CHIP_PWR_MAN, 0x00); // Power up DEM and STM + + // ALC config + // 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; + return true; +} + +void AudioControlES8388_F32::optimizeConversion(uint8_t range) +{ + uint8_t ingain[] = {0, 2, 4, 6, 8}; // 0db, 6dB, 12dB, 18dB, 24dB + uint8_t outvol[] = {30, 26, 22, 18, 14}; // 0db, -6dB, -12dB, -18dB, -24dB + if (range < 0) range = 0; + if (range > 4) range = 4; + volume(outvol[range]); + setInGain(ingain[range]); +} + +// get and set the output level (analog gain) +// vol = 0-31 +void AudioControlES8388_F32::volume(uint8_t vol) +{ + if (vol > 30) + vol = 30; + writeReg(ES8388_REG_DAC_CTRL24, vol); // LOUT1VOL + writeReg(ES8388_REG_DAC_CTRL25, vol); // ROUT1VOL +} +bool AudioControlES8388_F32::volume(float n) +{ + n = constrain(n, 0.0f, 1.0f); + uint8_t vol = n * 30.99f; + if (vol > 30) + vol = 30; + writeReg(ES8388_REG_DAC_CTRL24, vol); // LOUT1VOL + writeReg(ES8388_REG_DAC_CTRL25, vol); // ROUT1VOL + return true; +} + + +uint8_t AudioControlES8388_F32::getOutVol() +{ + uint8_t vol; + readReg(ES8388_REG_DAC_CTRL24, &vol); + return vol; +} + +bool AudioControlES8388_F32::setInGain(uint8_t gain) +{ + if (gain > 8) + gain = 8; + uint8_t temp; + temp = gain << 4; + temp = temp | gain; + + return writeReg(ES8388_REG_ADC_CTRL1, temp); +} + +uint8_t AudioControlES8388_F32::getInGain() +{ + uint8_t temp; + readReg(ES8388_REG_ADC_CTRL1, &temp); + temp = (temp & 0xF0) >> 4; + return temp; +} +void AudioControlES8388_F32::set_noiseGate(float thres) +{ + uint8_t thres_val = constrain(thres, 0.0f, 1.0f) * 31.99f; + DBG_SERIAL.printf("Gate: %d\r\n", thres_val); + 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 res = true; + if (bypass) + { + res = writeReg(ES8388_REG_DAC_CTRL17, ES8388_BIT_LI2LO | ES8388_LI2LOVOL(ES8388_VOL_0DB)); + res &= writeReg(ES8388_REG_DAC_CTRL20, ES8388_BIT_RI2RO | ES8388_RI2ROVOL(ES8388_VOL_0DB)); + } + else + { + res = writeReg(ES8388_REG_DAC_CTRL17, ES8388_BIT_LD2LO | ES8388_LI2LOVOL(ES8388_VOL_0DB)); + res &= writeReg(ES8388_REG_DAC_CTRL20, ES8388_BIT_RD2RO | ES8388_RI2ROVOL(ES8388_VOL_0DB)); + } + 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 res = true; + if (bypass) + { + res &= writeReg(ES8388_REG_DAC_CTRL17, ES8388_BIT_LI2LO | // Lin in on + ES8388_BIT_LD2LO | // L Dac on + ES8388_LI2LOVOL(ES8388_VOL_0DB)); // Lin gain 0dB + res &= writeReg(ES8388_REG_DAC_CTRL20, ES8388_BIT_RI2RO | // Rin in on + ES8388_BIT_RD2RO | // R Dac on + ES8388_RI2ROVOL(ES8388_VOL_0DB)); // Rin gain 0dB + } + else + { + res = writeReg(ES8388_REG_DAC_CTRL17, ES8388_BIT_LD2LO | ES8388_LI2LOVOL(ES8388_VOL_0DB)); + res &= writeReg(ES8388_REG_DAC_CTRL20, ES8388_BIT_RD2RO | ES8388_RI2ROVOL(ES8388_VOL_0DB)); + } + return res; +} + +bool AudioControlES8388_F32::writeReg(uint8_t addr, uint8_t val) +{ + ctrlBus->beginTransmission(i2cAddr); + ctrlBus->write(addr); + ctrlBus->write(val); + return ctrlBus->endTransmission() == 0; +} +bool AudioControlES8388_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 AudioControlES8388_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; +} \ No newline at end of file diff --git a/src/control_ES8388_F32.h b/src/control_ES8388_F32.h new file mode 100644 index 0000000..9814aaf --- /dev/null +++ b/src/control_ES8388_F32.h @@ -0,0 +1,55 @@ +#ifndef _CONTROL_ES8388_F32_H_ +#define _CONTROL_ES8388_F32_H_ + +#include +#include +#include "AudioControl.h" + +#define ES8388_I2C_ADDR_L (0x10) // CS/ADD pin low +#define ES8388_I2C_ADDR_H (0x11) // CS/ADD pin high + +class AudioControlES8388_F32 //: public AudioControl +{ +public: + AudioControlES8388_F32(void){}; + ~AudioControlES8388_F32(void){}; + typedef enum + { + ES8388_CFG_LINEIN_SINGLE_ENDED = 0, + ES8388_CFG_LINEIN_DIFF, + }config_t; + + + bool enable() + { + return enable(&Wire, ES8388_I2C_ADDR_L, ES8388_CFG_LINEIN_SINGLE_ENDED); + } + bool enable(TwoWire *i2cBus, uint8_t addr, config_t cfg); + bool disable(void) { return false; } + bool volume(float n); + bool inputLevel(float n); // range: 0.0f to 1.0f + + + void set_noiseGate(float thres); + + void volume(uint8_t vol); + uint8_t getOutVol(); + + bool setInGain(uint8_t gain); + uint8_t getInGain(); + + bool analogBypass(bool bypass); + bool analogSoftBypass(bool bypass); +private: + static bool configured; + TwoWire *ctrlBus; + uint8_t i2cAddr; + uint8_t dacGain; + + 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); + void optimizeConversion(uint8_t range); +}; + +#endif // _CONTROL_ES8388_F32_H_ diff --git a/src/control_SGTL5000_F32.cpp b/src/control_SGTL5000_F32.cpp index 9ebd0eb..72db695 100644 --- a/src/control_SGTL5000_F32.cpp +++ b/src/control_SGTL5000_F32.cpp @@ -1,16 +1,1020 @@ +/* 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. + */ + #include "control_SGTL5000_F32.h" +#include "control_sgtl5000.h" // for biquad config +#include #include -#define CHIP_I2S_CTRL 0x0006 -#define CHIP_ADCDAC_CTRL 0x000E +#define SGTL5000_CHIP_ID 0x0000 +// 15:8 PARTID 0xA0 - 8 bit identifier for SGTL5000 +// 7:0 REVID 0x00 - revision number for SGTL5000. + +#define SGTL5000_CHIP_DIG_POWER 0x0002 +// 6 ADC_POWERUP 1=Enable, 0=disable the ADC block, both digital & analog, +// 5 DAC_POWERUP 1=Enable, 0=disable the DAC block, both analog and digital +// 4 DAP_POWERUP 1=Enable, 0=disable the DAP block +// 1 I2S_OUT_POWERUP 1=Enable, 0=disable the I2S data output +// 0 I2S_IN_POWERUP 1=Enable, 0=disable the I2S data input + +#define SGTL5000_CHIP_CLK_CTRL 0x0004 +// 5:4 RATE_MODE Sets the sample rate mode. MCLK_FREQ is still specified +// relative to the rate in SYS_FS +// 0x0 = SYS_FS specifies the rate +// 0x1 = Rate is 1/2 of the SYS_FS rate +// 0x2 = Rate is 1/4 of the SYS_FS rate +// 0x3 = Rate is 1/6 of the SYS_FS rate +// 3:2 SYS_FS Sets the internal system sample rate (default=2) +// 0x0 = 32 kHz +// 0x1 = 44.1 kHz +// 0x2 = 48 kHz +// 0x3 = 96 kHz +// 1:0 MCLK_FREQ Identifies incoming SYS_MCLK frequency and if the PLL should be used +// 0x0 = 256*Fs +// 0x1 = 384*Fs +// 0x2 = 512*Fs +// 0x3 = Use PLL +// The 0x3 (Use PLL) setting must be used if the SYS_MCLK is not +// a standard multiple of Fs (256, 384, or 512). This setting can +// also be used if SYS_MCLK is a standard multiple of Fs. +// Before this field is set to 0x3 (Use PLL), the PLL must be +// powered up by setting CHIP_ANA_POWER->PLL_POWERUP and +// CHIP_ANA_POWER->VCOAMP_POWERUP. Also, the PLL dividers must +// be calculated based on the external MCLK rate and +// CHIP_PLL_CTRL register must be set (see CHIP_PLL_CTRL register +// description details on how to calculate the divisors). + +#define SGTL5000_CHIP_I2S_CTRL 0x0006 +// 8 SCLKFREQ Sets frequency of I2S_SCLK when in master mode (MS=1). When in slave +// mode (MS=0), this field must be set appropriately to match SCLK input +// rate. +// 0x0 = 64Fs +// 0x1 = 32Fs - Not supported for RJ mode (I2S_MODE = 1) +// 7 MS Configures master or slave of I2S_LRCLK and I2S_SCLK. +// 0x0 = Slave: I2S_LRCLK an I2S_SCLK are inputs +// 0x1 = Master: I2S_LRCLK and I2S_SCLK are outputs +// NOTE: If the PLL is used (CHIP_CLK_CTRL->MCLK_FREQ==0x3), +// the SGTL5000 must be a master of the I2S port (MS==1) +// 6 SCLK_INV Sets the edge that data (input and output) is clocked in on for I2S_SCLK +// 0x0 = data is valid on rising edge of I2S_SCLK +// 0x1 = data is valid on falling edge of I2S_SCLK +// 5:4 DLEN I2S data length (default=1) +// 0x0 = 32 bits (only valid when SCLKFREQ=0), +// not valid for Right Justified Mode +// 0x1 = 24 bits (only valid when SCLKFREQ=0) +// 0x2 = 20 bits +// 0x3 = 16 bits +// 3:2 I2S_MODE Sets the mode for the I2S port +// 0x0 = I2S mode or Left Justified (Use LRALIGN to select) +// 0x1 = Right Justified Mode +// 0x2 = PCM Format A/B +// 0x3 = RESERVED +// 1 LRALIGN I2S_LRCLK Alignment to data word. Not used for Right Justified mode +// 0x0 = Data word starts 1 I2S_SCLK delay after I2S_LRCLK +// transition (I2S format, PCM format A) +// 0x1 = Data word starts after I2S_LRCLK transition (left +// justified format, PCM format B) +// 0 LRPOL I2S_LRCLK Polarity when data is presented. +// 0x0 = I2S_LRCLK = 0 - Left, 1 - Right +// 1x0 = I2S_LRCLK = 0 - Right, 1 - Left +// The left subframe should be presented first regardless of +// the setting of LRPOL. + +#define SGTL5000_CHIP_SSS_CTRL 0x000A +// 14 DAP_MIX_LRSWAP DAP Mixer Input Swap +// 0x0 = Normal Operation +// 0x1 = Left and Right channels for the DAP MIXER Input are swapped. +// 13 DAP_LRSWAP DAP Mixer Input Swap +// 0x0 = Normal Operation +// 0x1 = Left and Right channels for the DAP Input are swapped +// 12 DAC_LRSWAP DAC Input Swap +// 0x0 = Normal Operation +// 0x1 = Left and Right channels for the DAC are swapped +// 10 I2S_LRSWAP I2S_DOUT Swap +// 0x0 = Normal Operation +// 0x1 = Left and Right channels for the I2S_DOUT are swapped +// 9:8 DAP_MIX_SELECT Select data source for DAP mixer +// 0x0 = ADC +// 0x1 = I2S_IN +// 0x2 = Reserved +// 0x3 = Reserved +// 7:6 DAP_SELECT Select data source for DAP +// 0x0 = ADC +// 0x1 = I2S_IN +// 0x2 = Reserved +// 0x3 = Reserved +// 5:4 DAC_SELECT Select data source for DAC (default=1) +// 0x0 = ADC +// 0x1 = I2S_IN +// 0x2 = Reserved +// 0x3 = DAP +// 1:0 I2S_SELECT Select data source for I2S_DOUT +// 0x0 = ADC +// 0x1 = I2S_IN +// 0x2 = Reserved +// 0x3 = DAP + +#define SGTL5000_CHIP_ADCDAC_CTRL 0x000E +// 13 VOL_BUSY_DAC_RIGHT Volume Busy DAC Right +// 0x0 = Ready +// 0x1 = Busy - This indicates the channel has not reached its +// programmed volume/mute level +// 12 VOL_BUSY_DAC_LEFT Volume Busy DAC Left +// 0x0 = Ready +// 0x1 = Busy - This indicates the channel has not reached its +// programmed volume/mute level +// 9 VOL_RAMP_EN Volume Ramp Enable (default=1) +// 0x0 = Disables volume ramp. New volume settings take immediate +// effect without a ramp +// 0x1 = Enables volume ramp +// This field affects DAC_VOL. The volume ramp effects both +// volume settings and mute When set to 1 a soft mute is enabled. +// 8 VOL_EXPO_RAMP Exponential Volume Ramp Enable +// 0x0 = Linear ramp over top 4 volume octaves +// 0x1 = Exponential ramp over full volume range +// This bit only takes effect if VOL_RAMP_EN is 1. +// 3 DAC_MUTE_RIGHT DAC Right Mute (default=1) +// 0x0 = Unmute +// 0x1 = Muted +// If VOL_RAMP_EN = 1, this is a soft mute. +// 2 DAC_MUTE_LEFT DAC Left Mute (default=1) +// 0x0 = Unmute +// 0x1 = Muted +// If VOL_RAMP_EN = 1, this is a soft mute. +// 1 ADC_HPF_FREEZE ADC High Pass Filter Freeze +// 0x0 = Normal operation +// 0x1 = Freeze the ADC high-pass filter offset register. The +// offset continues to be subtracted from the ADC data stream. +// 0 ADC_HPF_BYPASS ADC High Pass Filter Bypass +// 0x0 = Normal operation +// 0x1 = Bypassed and offset not updated + +#define SGTL5000_CHIP_DAC_VOL 0x0010 +// 15:8 DAC_VOL_RIGHT DAC Right Channel Volume. Set the Right channel DAC volume +// with 0.5017 dB steps from 0 to -90 dB +// 0x3B and less = Reserved +// 0x3C = 0 dB +// 0x3D = -0.5 dB +// 0xF0 = -90 dB +// 0xFC and greater = Muted +// If VOL_RAMP_EN = 1, there is an automatic ramp to the +// new volume setting. +// 7:0 DAC_VOL_LEFT DAC Left Channel Volume. Set the Left channel DAC volume +// with 0.5017 dB steps from 0 to -90 dB +// 0x3B and less = Reserved +// 0x3C = 0 dB +// 0x3D = -0.5 dB +// 0xF0 = -90 dB +// 0xFC and greater = Muted +// If VOL_RAMP_EN = 1, there is an automatic ramp to the +// new volume setting. + +#define SGTL5000_CHIP_PAD_STRENGTH 0x0014 +// 9:8 I2S_LRCLK I2S LRCLK Pad Drive Strength (default=1) +// Sets drive strength for output pads per the table below. +// VDDIO 1.8 V 2.5 V 3.3 V +// 0x0 = Disable +// 0x1 = 1.66 mA 2.87 mA 4.02 mA +// 0x2 = 3.33 mA 5.74 mA 8.03 mA +// 0x3 = 4.99 mA 8.61 mA 12.05 mA +// 7:6 I2S_SCLK I2S SCLK Pad Drive Strength (default=1) +// 5:4 I2S_DOUT I2S DOUT Pad Drive Strength (default=1) +// 3:2 CTRL_DATA I2C DATA Pad Drive Strength (default=3) +// 1:0 CTRL_CLK I2C CLK Pad Drive Strength (default=3) +// (all use same table as I2S_LRCLK) + +#define SGTL5000_CHIP_ANA_ADC_CTRL 0x0020 +// 8 ADC_VOL_M6DB ADC Volume Range Reduction +// This bit shifts both right and left analog ADC volume +// range down by 6.0 dB. +// 0x0 = No change in ADC range +// 0x1 = ADC range reduced by 6.0 dB +// 7:4 ADC_VOL_RIGHT ADC Right Channel Volume +// Right channel analog ADC volume control in 1.5 dB steps. +// 0x0 = 0 dB +// 0x1 = +1.5 dB +// ... +// 0xF = +22.5 dB +// This range is -6.0 dB to +16.5 dB if ADC_VOL_M6DB is set to 1. +// 3:0 ADC_VOL_LEFT ADC Left Channel Volume +// (same scale as ADC_VOL_RIGHT) + +#define SGTL5000_CHIP_ANA_HP_CTRL 0x0022 +// 14:8 HP_VOL_RIGHT Headphone Right Channel Volume (default 0x18) +// Right channel headphone volume control with 0.5 dB steps. +// 0x00 = +12 dB +// 0x01 = +11.5 dB +// 0x18 = 0 dB +// ... +// 0x7F = -51.5 dB +// 6:0 HP_VOL_LEFT Headphone Left Channel Volume (default 0x18) +// (same scale as HP_VOL_RIGHT) + +#define SGTL5000_CHIP_ANA_CTRL 0x0024 +// 8 MUTE_LO LINEOUT Mute, 0 = Unmute, 1 = Mute (default 1) +// 6 SELECT_HP Select the headphone input, 0 = DAC, 1 = LINEIN +// 5 EN_ZCD_HP Enable the headphone zero cross detector (ZCD) +// 0x0 = HP ZCD disabled +// 0x1 = HP ZCD enabled +// 4 MUTE_HP Mute the headphone outputs, 0 = Unmute, 1 = Mute (default) +// 2 SELECT_ADC Select the ADC input, 0 = Microphone, 1 = LINEIN +// 1 EN_ZCD_ADC Enable the ADC analog zero cross detector (ZCD) +// 0x0 = ADC ZCD disabled +// 0x1 = ADC ZCD enabled +// 0 MUTE_ADC Mute the ADC analog volume, 0 = Unmute, 1 = Mute (default) + +#define SGTL5000_CHIP_LINREG_CTRL 0x0026 +// 6 VDDC_MAN_ASSN Determines chargepump source when VDDC_ASSN_OVRD is set. +// 0x0 = VDDA +// 0x1 = VDDIO +// 5 VDDC_ASSN_OVRD Charge pump Source Assignment Override +// 0x0 = Charge pump source is automatically assigned based +// on higher of VDDA and VDDIO +// 0x1 = the source of charge pump is manually assigned by +// VDDC_MAN_ASSN If VDDIO and VDDA are both the same +// and greater than 3.1 V, VDDC_ASSN_OVRD and +// VDDC_MAN_ASSN should be used to manually assign +// VDDIO as the source for charge pump. +// 3:0 D_PROGRAMMING Sets the VDDD linear regulator output voltage in 50 mV steps. +// Must clear the LINREG_SIMPLE_POWERUP and STARTUP_POWERUP bits +// in the 0x0030 (CHIP_ANA_POWER) register after power-up, for +// this setting to produce the proper VDDD voltage. +// 0x0 = 1.60 +// 0xF = 0.85 + +#define SGTL5000_CHIP_REF_CTRL 0x0028 // bandgap reference bias voltage and currents +// 8:4 VAG_VAL Analog Ground Voltage Control +// These bits control the analog ground voltage in 25 mV steps. +// This should usually be set to VDDA/2 or lower for best +// performance (maximum output swing at minimum THD). This VAG +// reference is also used for the DAC and ADC voltage reference. +// So changing this voltage scales the output swing of the DAC +// and the output signal of the ADC. +// 0x00 = 0.800 V +// 0x1F = 1.575 V +// 3:1 BIAS_CTRL Bias control +// These bits adjust the bias currents for all of the analog +// blocks. By lowering the bias current a lower quiescent power +// is achieved. It should be noted that this mode can affect +// performance by 3-4 dB. +// 0x0 = Nominal +// 0x1-0x3=+12.5% +// 0x4=-12.5% +// 0x5=-25% +// 0x6=-37.5% +// 0x7=-50% +// 0 SMALL_POP VAG Ramp Control +// Setting this bit slows down the VAG ramp from ~200 to ~400 ms +// to reduce the startup pop, but increases the turn on/off time. +// 0x0 = Normal VAG ramp +// 0x1 = Slow down VAG ramp + +#define SGTL5000_CHIP_MIC_CTRL 0x002A // microphone gain & internal microphone bias +// 9:8 BIAS_RESISTOR MIC Bias Output Impedance Adjustment +// Controls an adjustable output impedance for the microphone bias. +// If this is set to zero the micbias block is powered off and +// the output is highZ. +// 0x0 = Powered off +// 0x1 = 2.0 kohm +// 0x2 = 4.0 kohm +// 0x3 = 8.0 kohm +// 6:4 BIAS_VOLT MIC Bias Voltage Adjustment +// Controls an adjustable bias voltage for the microphone bias +// amp in 250 mV steps. This bias voltage setting should be no +// more than VDDA-200 mV for adequate power supply rejection. +// 0x0 = 1.25 V +// ... +// 0x7 = 3.00 V +// 1:0 GAIN MIC Amplifier Gain +// Sets the microphone amplifier gain. At 0 dB setting the THD +// can be slightly higher than other paths- typically around +// ~65 dB. At other gain settings the THD are better. +// 0x0 = 0 dB +// 0x1 = +20 dB +// 0x2 = +30 dB +// 0x3 = +40 dB + +#define SGTL5000_CHIP_LINE_OUT_CTRL 0x002C +// 11:8 OUT_CURRENT Controls the output bias current for the LINEOUT amplifiers. The +// nominal recommended setting for a 10 kohm load with 1.0 nF load cap +// is 0x3. There are only 5 valid settings. +// 0x0=0.18 mA +// 0x1=0.27 mA +// 0x3=0.36 mA +// 0x7=0.45 mA +// 0xF=0.54 mA +// 5:0 LO_VAGCNTRL LINEOUT Amplifier Analog Ground Voltage +// Controls the analog ground voltage for the LINEOUT amplifiers +// in 25 mV steps. This should usually be set to VDDIO/2. +// 0x00 = 0.800 V +// ... +// 0x1F = 1.575 V +// ... +// 0x23 = 1.675 V +// 0x24-0x3F are invalid + +#define SGTL5000_CHIP_LINE_OUT_VOL 0x002E +// 12:8 LO_VOL_RIGHT LINEOUT Right Channel Volume (default=4) +// Controls the right channel LINEOUT volume in 0.5 dB steps. +// Higher codes have more attenuation. +// 4:0 LO_VOL_LEFT LINEOUT Left Channel Output Level (default=4) +// Used to normalize the output level of the left line output +// to full scale based on the values used to set +// LINE_OUT_CTRL->LO_VAGCNTRL and CHIP_REF_CTRL->VAG_VAL. +// In general this field should be set to: +// 40*log((VAG_VAL)/(LO_VAGCNTRL)) + 15 +// Suggested values based on typical VDDIO and VDDA voltages. +// VDDA VAG_VAL VDDIO LO_VAGCNTRL LO_VOL_* +// 1.8 V 0.9 3.3 V 1.55 0x06 +// 1.8 V 0.9 1.8 V 0.9 0x0F +// 3.3 V 1.55 1.8 V 0.9 0x19 +// 3.3 V 1.55 3.3 V 1.55 0x0F +// After setting to the nominal voltage, this field can be used +// to adjust the output level in +/-0.5 dB increments by using +// values higher or lower than the nominal setting. + +#define SGTL5000_CHIP_ANA_POWER 0x0030 // power down controls for the analog blocks. + // The only other power-down controls are BIAS_RESISTOR in the MIC_CTRL register + // and the EN_ZCD control bits in ANA_CTRL. +// 14 DAC_MONO While DAC_POWERUP is set, this allows the DAC to be put into left only +// mono operation for power savings. 0=mono, 1=stereo (default) +// 13 LINREG_SIMPLE_POWERUP Power up the simple (low power) digital supply regulator. +// After reset, this bit can be cleared IF VDDD is driven +// externally OR the primary digital linreg is enabled with +// LINREG_D_POWERUP +// 12 STARTUP_POWERUP Power up the circuitry needed during the power up ramp and reset. +// After reset this bit can be cleared if VDDD is coming from +// an external source. +// 11 VDDC_CHRGPMP_POWERUP Power up the VDDC charge pump block. If neither VDDA or VDDIO +// is 3.0 V or larger this bit should be cleared before analog +// blocks are powered up. +// 10 PLL_POWERUP PLL Power Up, 0 = Power down, 1 = Power up +// When cleared, the PLL is turned off. This must be set before +// CHIP_CLK_CTRL->MCLK_FREQ is programmed to 0x3. The +// CHIP_PLL_CTRL register must be configured correctly before +// setting this bit. +// 9 LINREG_D_POWERUP Power up the primary VDDD linear regulator, 0 = Power down, 1 = Power up +// 8 VCOAMP_POWERUP Power up the PLL VCO amplifier, 0 = Power down, 1 = Power up +// 7 VAG_POWERUP Power up the VAG reference buffer. +// Setting this bit starts the power up ramp for the headphone +// and LINEOUT. The headphone (and/or LINEOUT) powerup should +// be set BEFORE clearing this bit. When this bit is cleared +// the power-down ramp is started. The headphone (and/or LINEOUT) +// powerup should stay set until the VAG is fully ramped down +// (200 to 400 ms after clearing this bit). +// 0x0 = Power down, 0x1 = Power up +// 6 ADC_MONO While ADC_POWERUP is set, this allows the ADC to be put into left only +// mono operation for power savings. This mode is useful when +// only using the microphone input. +// 0x0 = Mono (left only), 0x1 = Stereo +// 5 REFTOP_POWERUP Power up the reference bias currents +// 0x0 = Power down, 0x1 = Power up +// This bit can be cleared when the part is a sleep state +// to minimize analog power. +// 4 HEADPHONE_POWERUP Power up the headphone amplifiers +// 0x0 = Power down, 0x1 = Power up +// 3 DAC_POWERUP Power up the DACs +// 0x0 = Power down, 0x1 = Power up +// 2 CAPLESS_HEADPHONE_POWERUP Power up the capless headphone mode +// 0x0 = Power down, 0x1 = Power up +// 1 ADC_POWERUP Power up the ADCs +// 0x0 = Power down, 0x1 = Power up +// 0 LINEOUT_POWERUP Power up the LINEOUT amplifiers +// 0x0 = Power down, 0x1 = Power up + +#define SGTL5000_CHIP_PLL_CTRL 0x0032 +// 15:11 INT_DIVISOR +// 10:0 FRAC_DIVISOR + +#define SGTL5000_CHIP_CLK_TOP_CTRL 0x0034 +// 11 ENABLE_INT_OSC Setting this bit enables an internal oscillator to be used for the +// zero cross detectors, the short detect recovery, and the +// charge pump. This allows the I2S clock to be shut off while +// still operating an analog signal path. This bit can be kept +// on when the I2S clock is enabled, but the I2S clock is more +// accurate so it is preferred to clear this bit when I2S is present. +// 3 INPUT_FREQ_DIV2 SYS_MCLK divider before PLL input +// 0x0 = pass through +// 0x1 = SYS_MCLK is divided by 2 before entering PLL +// This must be set when the input clock is above 17 Mhz. This +// has no effect when the PLL is powered down. + +#define SGTL5000_CHIP_ANA_STATUS 0x0036 +// 9 LRSHORT_STS This bit is high whenever a short is detected on the left or right +// channel headphone drivers. +// 8 CSHORT_STS This bit is high whenever a short is detected on the capless headphone +// common/center channel driver. +// 4 PLL_IS_LOCKED This bit goes high after the PLL is locked. + +#define SGTL5000_CHIP_ANA_TEST1 0x0038 // intended only for debug. +#define SGTL5000_CHIP_ANA_TEST2 0x003A // intended only for debug. + +#define SGTL5000_CHIP_SHORT_CTRL 0x003C +// 14:12 LVLADJR Right channel headphone short detector in 25 mA steps. +// 0x3=25 mA +// 0x2=50 mA +// 0x1=75 mA +// 0x0=100 mA +// 0x4=125 mA +// 0x5=150 mA +// 0x6=175 mA +// 0x7=200 mA +// This trip point can vary by ~30% over process so leave plenty +// of guard band to avoid false trips. This short detect trip +// point is also effected by the bias current adjustments made +// by CHIP_REF_CTRL->BIAS_CTRL and by CHIP_ANA_TEST1->HP_IALL_ADJ. +// 10:8 LVLADJL Left channel headphone short detector in 25 mA steps. +// (same scale as LVLADJR) +// 6:4 LVLADJC Capless headphone center channel short detector in 50 mA steps. +// 0x3=50 mA +// 0x2=100 mA +// 0x1=150 mA +// 0x0=200 mA +// 0x4=250 mA +// 0x5=300 mA +// 0x6=350 mA +// 0x7=400 mA +// 3:2 MODE_LR Behavior of left/right short detection +// 0x0 = Disable short detector, reset short detect latch, +// software view non-latched short signal +// 0x1 = Enable short detector and reset the latch at timeout +// (every ~50 ms) +// 0x2 = This mode is not used/invalid +// 0x3 = Enable short detector with only manual reset (have +// to return to 0x0 to reset the latch) +// 1:0 MODE_CM Behavior of capless headphone central short detection +// (same settings as MODE_LR) + +#define SGTL5000_DAP_CONTROL 0x0100 +#define SGTL5000_DAP_PEQ 0x0102 +#define SGTL5000_DAP_BASS_ENHANCE 0x0104 +#define SGTL5000_DAP_BASS_ENHANCE_CTRL 0x0106 +#define SGTL5000_DAP_AUDIO_EQ 0x0108 +#define SGTL5000_DAP_SGTL_SURROUND 0x010A +#define SGTL5000_DAP_FILTER_COEF_ACCESS 0x010C +#define SGTL5000_DAP_COEF_WR_B0_MSB 0x010E +#define SGTL5000_DAP_COEF_WR_B0_LSB 0x0110 +#define SGTL5000_DAP_AUDIO_EQ_BASS_BAND0 0x0116 // 115 Hz +#define SGTL5000_DAP_AUDIO_EQ_BAND1 0x0118 // 330 Hz +#define SGTL5000_DAP_AUDIO_EQ_BAND2 0x011A // 990 Hz +#define SGTL5000_DAP_AUDIO_EQ_BAND3 0x011C // 3000 Hz +#define SGTL5000_DAP_AUDIO_EQ_TREBLE_BAND4 0x011E // 9900 Hz +#define SGTL5000_DAP_MAIN_CHAN 0x0120 +#define SGTL5000_DAP_MIX_CHAN 0x0122 +#define SGTL5000_DAP_AVC_CTRL 0x0124 +#define SGTL5000_DAP_AVC_THRESHOLD 0x0126 +#define SGTL5000_DAP_AVC_ATTACK 0x0128 +#define SGTL5000_DAP_AVC_DECAY 0x012A +#define SGTL5000_DAP_COEF_WR_B1_MSB 0x012C +#define SGTL5000_DAP_COEF_WR_B1_LSB 0x012E +#define SGTL5000_DAP_COEF_WR_B2_MSB 0x0130 +#define SGTL5000_DAP_COEF_WR_B2_LSB 0x0132 +#define SGTL5000_DAP_COEF_WR_A1_MSB 0x0134 +#define SGTL5000_DAP_COEF_WR_A1_LSB 0x0136 +#define SGTL5000_DAP_COEF_WR_A2_MSB 0x0138 +#define SGTL5000_DAP_COEF_WR_A2_LSB 0x013A + +#define SGTL5000_I2C_ADDR_CS_LOW 0x0A // CTRL_ADR0_CS pin low (normal configuration) +#define SGTL5000_I2C_ADDR_CS_HIGH 0x2A // CTRL_ADR0_CS pin high + +void AudioControlSGTL5000_F32::setAddress(uint8_t level) +{ + if (level == LOW) i2c_addr = SGTL5000_I2C_ADDR_CS_LOW; + else i2c_addr = SGTL5000_I2C_ADDR_CS_HIGH; +} + +bool AudioControlSGTL5000_F32::enable() +{ + return enable(&Wire); +} + +bool FLASHMEM AudioControlSGTL5000_F32::enable(TwoWire *i2cBus, uint8_t addr, const uint32_t extMCLK, const uint32_t pllFreq) +{ + _wire = i2cBus; + i2c_addr = addr; + _wire->begin(); + delay(5); + // Check if we are in Master Mode and if the Teensy had a reset: + uint16_t n = read(SGTL5000_CHIP_I2S_CTRL); + if ((extMCLK > 0) && (n == (0x0030 | (1 << 7)))) + { + // Yes. Do not initialize. + muted = false; + semi_automated = true; + return true; + } + muted = true; + int r = write(SGTL5000_CHIP_ANA_POWER, 0x4060); // VDDD is externally driven with 1.8V + if (!r) return false; + write(SGTL5000_CHIP_LINREG_CTRL, 0x006C); // VDDA & VDDIO both over 3.1V + write(SGTL5000_CHIP_REF_CTRL, 0x01F2); // VAG=1.575, normal ramp, +12.5% bias current + write(SGTL5000_CHIP_LINE_OUT_CTRL, 0x0F22); // LO_VAGCNTRL=1.65V, OUT_CURRENT=0.54mA + write(SGTL5000_CHIP_SHORT_CTRL, 0x4446); // allow up to 125mA + write(SGTL5000_CHIP_ANA_CTRL, 0x0137); // enable zero cross detectors + + if (extMCLK > 0) + { + // SGTL is I2S Master + // Datasheet Pg. 14: Using the PLL - Asynchronous SYS_MCLK input + if (extMCLK > 17000000) + { + write(SGTL5000_CHIP_CLK_TOP_CTRL, 1); + } + else + { + write(SGTL5000_CHIP_CLK_TOP_CTRL, 0); + } + + uint32_t int_divisor = (pllFreq / extMCLK) & 0x1f; + uint32_t frac_divisor = (uint32_t)((((float)pllFreq / extMCLK) - int_divisor) * 2048.0f) & 0x7ff; + + write(SGTL5000_CHIP_PLL_CTRL, (int_divisor << 11) | frac_divisor); + write(SGTL5000_CHIP_ANA_POWER, 0x40FF | (1 << 10) | (1 << 8)); // power up: lineout, hp, adc, dac, PLL_POWERUP, VCOAMP_POWERUP + } + else + { + // SGTL is I2S Slave + write(SGTL5000_CHIP_ANA_POWER, 0x40FF); // power up: lineout, hp, adc, dac + } + + write(SGTL5000_CHIP_DIG_POWER, 0x0073); // power up all digital stuff + delay(400); + write(SGTL5000_CHIP_LINE_OUT_VOL, 0x1D1D); // default approx 1.3 volts peak-to-peak + + if (extMCLK > 0) + { + // SGTL is I2S Master + write(SGTL5000_CHIP_CLK_CTRL, 0x0004 | 0x03); // 44.1 kHz, 256*Fs, use PLL + write(SGTL5000_CHIP_I2S_CTRL, 0x0000 | (1 << 7)); // SCLK=64*Fs, 32bit, I2S format + } + else + { + // SGTL is I2S Slave + write(SGTL5000_CHIP_CLK_CTRL, 0x0004); // 44.1 kHz, 256*Fs + write(SGTL5000_CHIP_I2S_CTRL, 0x0000); // SCLK=64*Fs, 32bit, I2S format + } + + // default signal routing is ok? + write(SGTL5000_CHIP_SSS_CTRL, 0x0010); // ADC->I2S, I2S->DAC + write(SGTL5000_CHIP_ADCDAC_CTRL, 0x0000); // disable dac mute + write(SGTL5000_CHIP_DAC_VOL, 0x3C3C); // digital gain, 0dB + write(SGTL5000_CHIP_ANA_HP_CTRL, 0x7F7F); // set volume (lowest level) + write(SGTL5000_CHIP_ANA_CTRL, 0x0036); // enable zero cross detectors + + semi_automated = true; + return true; +} + +uint16_t AudioControlSGTL5000_F32::read(uint16_t reg) +{ + uint16_t val; + _wire->beginTransmission(i2c_addr); + _wire->write(reg >> 8); + _wire->write(reg); + if (_wire->endTransmission(false) != 0) + return 0; + if (_wire->requestFrom((int)i2c_addr, 2) < 2) + return 0; + val = _wire->read() << 8; + val |= _wire->read(); + return val; +} + +bool AudioControlSGTL5000_F32::write(uint16_t reg, uint16_t val) +{ + if (reg == SGTL5000_CHIP_ANA_CTRL) + ana_ctrl = val; + _wire->beginTransmission(i2c_addr); + _wire->write(reg >> 8); + _wire->write(reg); + _wire->write(val >> 8); + _wire->write(val); + if (_wire->endTransmission() == 0) + return true; + return false; +} + +uint16_t AudioControlSGTL5000_F32::modify(uint16_t reg, uint16_t val, uint16_t iMask) +{ + uint16_t val1 = (read(reg) & (~iMask)) | val; + if (!write(reg, val1)) + return 0; + return val1; +} + +bool AudioControlSGTL5000_F32::volumeInteger(uint16_t n) +{ + if (n == 0) + { + muted = true; + write(SGTL5000_CHIP_ANA_HP_CTRL, 0x7F7F); + return muteHeadphone(); + } + else if (n > 0x80) n = 0; + else n = 0x80 - n; + if (muted) + { + muted = false; + unmuteHeadphone(); + } + n = n | (n << 8); + return write(SGTL5000_CHIP_ANA_HP_CTRL, n); // set volume +} + +bool AudioControlSGTL5000_F32::volume(float left, float right) +{ + uint16_t m = ((0x7F - calcVol(right, 0x7F)) << 8) | (0x7F - calcVol(left, 0x7F)); + return write(SGTL5000_CHIP_ANA_HP_CTRL, m); +} + +bool AudioControlSGTL5000_F32::micGain(uint16_t dB) +{ + uint16_t preamp_gain, input_gain; + + if (dB >= 40) { preamp_gain = 3; dB -= 40;} + else if (dB >= 30) { preamp_gain = 2; dB -= 30;} + else if (dB >= 20) { preamp_gain = 1; dB -= 20;} + else { preamp_gain = 0;} + input_gain = (dB * 2) / 3; + if (input_gain > 15) input_gain = 15; + return write(SGTL5000_CHIP_MIC_CTRL, 0x0170 | preamp_gain) && write(SGTL5000_CHIP_ANA_ADC_CTRL, (input_gain << 4) | input_gain); +} + +bool AudioControlSGTL5000_F32::inputSelect(int n) +{ + if (n == AUDIO_INPUT_LINEIN) + { + return write(0x0020, 0x055) // +7.5dB gain (1.3Vp-p full scale) + && write(0x0024, ana_ctrl | (1 << 2)); // enable linein + } + else if (n == AUDIO_INPUT_MIC) + { + return write(0x002A, 0x0173) // mic preamp gain = +40dB + && write(0x0020, 0x088) // input gain +12dB (is this enough?) + && write(0x0024, ana_ctrl & ~(1 << 2)); // enable mic + } + else + { + return false; + } +} + +bool AudioControlSGTL5000_F32::headphoneSelect(int n) +{ + if (n == AUDIO_HEADPHONE_DAC) + { + return write(0x0024, ana_ctrl | (1 << 6)); // route DAC to headphones out + } + else if (n == AUDIO_HEADPHONE_LINEIN) + { + return write(0x0024, ana_ctrl & ~(1 << 6)); // route linein to headphones out + } + else + { + return false; + } +} +// CHIP_ANA_ADC_CTRL +// Actual measured full-scale peak-to-peak sine wave input for max signal +// 0: 3.12 Volts p-p +// 1: 2.63 Volts p-p +// 2: 2.22 Volts p-p +// 3: 1.87 Volts p-p +// 4: 1.58 Volts p-p +// 5: 1.33 Volts p-p +// 6: 1.11 Volts p-p +// 7: 0.94 Volts p-p +// 8: 0.79 Volts p-p +// 9: 0.67 Volts p-p +// 10: 0.56 Volts p-p +// 11: 0.48 Volts p-p +// 12: 0.40 Volts p-p +// 13: 0.34 Volts p-p +// 14: 0.29 Volts p-p +// 15: 0.24 Volts p-p +bool AudioControlSGTL5000_F32::lineInLevel(uint8_t left, uint8_t right) +{ + if (left > 15) left = 15; + if (right > 15) right = 15; + return write(SGTL5000_CHIP_ANA_ADC_CTRL, (left << 4) | right); +} + +// CHIP_LINE_OUT_VOL +// Actual measured full-scale peak-to-peak sine wave output voltage: +// 0-12: output has clipping +// 13: 3.16 Volts p-p +// 14: 2.98 Volts p-p +// 15: 2.83 Volts p-p +// 16: 2.67 Volts p-p +// 17: 2.53 Volts p-p +// 18: 2.39 Volts p-p +// 19: 2.26 Volts p-p +// 20: 2.14 Volts p-p +// 21: 2.02 Volts p-p +// 22: 1.91 Volts p-p +// 23: 1.80 Volts p-p +// 24: 1.71 Volts p-p +// 25: 1.62 Volts p-p +// 26: 1.53 Volts p-p +// 27: 1.44 Volts p-p +// 28: 1.37 Volts p-p +// 29: 1.29 Volts p-p +// 30: 1.22 Volts p-p +// 31: 1.16 Volts p-p +uint16_t AudioControlSGTL5000_F32::lineOutLevel(uint8_t n) +{ + if (n > 31) n = 31; + else if (n < 13) n = 13; + return modify(SGTL5000_CHIP_LINE_OUT_VOL, (n << 8) | n, (31 << 8) | 31); +} + +uint16_t AudioControlSGTL5000_F32::lineOutLevel(uint8_t left, uint8_t right) +{ + if (left > 31) + left = 31; + else if (left < 13) + left = 13; + if (right > 31) + right = 31; + else if (right < 13) + right = 13; + return modify(SGTL5000_CHIP_LINE_OUT_VOL, (right << 8) | left, (31 << 8) | 31); +} + +uint16_t AudioControlSGTL5000_F32::dacVolume(float n) // set both directly +{ + if ((read(SGTL5000_CHIP_ADCDAC_CTRL) & (3 << 2)) != ((n > 0 ? 0 : 3) << 2)) + { + modify(SGTL5000_CHIP_ADCDAC_CTRL, (n > 0 ? 0 : 3) << 2, 3 << 2); + } + unsigned char m = calcVol(n, 0xC0); + return modify(SGTL5000_CHIP_DAC_VOL, ((0xFC - m) << 8) | (0xFC - m), 65535); +} +uint16_t AudioControlSGTL5000_F32::dacVolume(float left, float right) +{ + uint16_t adcdac = ((right > 0 ? 0 : 2) | (left > 0 ? 0 : 1)) << 2; + if ((read(SGTL5000_CHIP_ADCDAC_CTRL) & (3 << 2)) != adcdac) + { + modify(SGTL5000_CHIP_ADCDAC_CTRL, adcdac, 1 << 2); + } + uint16_t m = (0xFC - calcVol(right, 0xC0)) << 8 | (0xFC - calcVol(left, 0xC0)); + return modify(SGTL5000_CHIP_DAC_VOL, m, 65535); +} + +bool AudioControlSGTL5000_F32::dacVolumeRamp() +{ + return modify(SGTL5000_CHIP_ADCDAC_CTRL, 0x300, 0x300); +} + +bool AudioControlSGTL5000_F32::dacVolumeRampLinear() +{ + return modify(SGTL5000_CHIP_ADCDAC_CTRL, 0x200, 0x300); +} + +bool AudioControlSGTL5000_F32::dacVolumeRampDisable() +{ + return modify(SGTL5000_CHIP_ADCDAC_CTRL, 0, 0x300); +} + +uint16_t AudioControlSGTL5000_F32::adcHighPassFilterEnable(void) +{ + return modify(SGTL5000_CHIP_ADCDAC_CTRL, 0, 3); +} + +uint16_t AudioControlSGTL5000_F32::adcHighPassFilterFreeze(void) +{ + return modify(SGTL5000_CHIP_ADCDAC_CTRL, 2, 3); +} + +uint16_t AudioControlSGTL5000_F32::adcHighPassFilterDisable(void) +{ + return modify(SGTL5000_CHIP_ADCDAC_CTRL, 1, 3); +} + +// DAP_CONTROL + +uint16_t AudioControlSGTL5000_F32::audioPreProcessorEnable(void) +{ + // audio processor used to pre-process analog input before Teensy + return write(SGTL5000_DAP_CONTROL, 1) && write(SGTL5000_CHIP_SSS_CTRL, 0x0013); +} + +uint16_t AudioControlSGTL5000_F32::audioPostProcessorEnable(void) +{ + // audio processor used to post-process Teensy output before headphones/lineout + return write(SGTL5000_DAP_CONTROL, 1) && write(SGTL5000_CHIP_SSS_CTRL, 0x0070); +} + +uint16_t AudioControlSGTL5000_F32::audioProcessorDisable(void) +{ + return write(SGTL5000_CHIP_SSS_CTRL, 0x0010) && write(SGTL5000_DAP_CONTROL, 0); +} + +// DAP_PEQ +uint16_t AudioControlSGTL5000_F32::eqFilterCount(uint8_t n) // valid to n&7, 0 thru 7 filters enabled. +{ + return modify(SGTL5000_DAP_PEQ, (n & 7), 7); +} + +// DAP_AUDIO_EQ +uint16_t AudioControlSGTL5000_F32::eqSelect(uint8_t n) // 0=NONE, 1=PEQ (7 IIR Biquad filters), 2=TONE (tone), 3=GEQ (5 band EQ) +{ + return modify(SGTL5000_DAP_AUDIO_EQ, n & 3, 3); +} + +uint16_t AudioControlSGTL5000_F32::eqBand(uint8_t bandNum, float n) +{ + if (semi_automated) + automate(1, 3); + return dap_audio_eq_band(bandNum, n); +} +void AudioControlSGTL5000_F32::eqBands(float bass, float mid_bass, float midrange, float mid_treble, float treble) +{ + if (semi_automated) + automate(1, 3); + dap_audio_eq_band(0, bass); + dap_audio_eq_band(1, mid_bass); + dap_audio_eq_band(2, midrange); + dap_audio_eq_band(3, mid_treble); + dap_audio_eq_band(4, treble); +} +void AudioControlSGTL5000_F32::eqBands(float bass, float treble) // dap_audio_eq(2); +{ + if (semi_automated) + automate(1, 2); + dap_audio_eq_band(0, bass); + dap_audio_eq_band(4, treble); +} + +// SGTL5000 PEQ Coefficient loader +void AudioControlSGTL5000_F32::eqFilter(uint8_t filterNum, int *filterParameters) +{ + // TODO: add the part that selects 7 PEQ filters. + if (semi_automated) + automate(1, 1, filterNum + 1); + modify(SGTL5000_DAP_FILTER_COEF_ACCESS, (uint16_t)filterNum, 15); + write(SGTL5000_DAP_COEF_WR_B0_MSB, (*filterParameters >> 4) & 65535); + write(SGTL5000_DAP_COEF_WR_B0_LSB, (*filterParameters++) & 15); + write(SGTL5000_DAP_COEF_WR_B1_MSB, (*filterParameters >> 4) & 65535); + write(SGTL5000_DAP_COEF_WR_B1_LSB, (*filterParameters++) & 15); + write(SGTL5000_DAP_COEF_WR_B2_MSB, (*filterParameters >> 4) & 65535); + write(SGTL5000_DAP_COEF_WR_B2_LSB, (*filterParameters++) & 15); + write(SGTL5000_DAP_COEF_WR_A1_MSB, (*filterParameters >> 4) & 65535); + write(SGTL5000_DAP_COEF_WR_A1_LSB, (*filterParameters++) & 15); + write(SGTL5000_DAP_COEF_WR_A2_MSB, (*filterParameters >> 4) & 65535); + write(SGTL5000_DAP_COEF_WR_A2_LSB, (*filterParameters++) & 15); + write(SGTL5000_DAP_FILTER_COEF_ACCESS, (uint16_t)0x100 | filterNum); +} + +/* Valid values for dap_avc parameters + + maxGain; Maximum gain that can be applied + 0 - 0 dB + 1 - 6.0 dB + 2 - 12 dB + + lbiResponse; Integrator Response + 0 - 0 mS + 1 - 25 mS + 2 - 50 mS + 3 - 100 mS + + hardLimit + 0 - Hard limit disabled. AVC Compressor/Expander enabled. + 1 - Hard limit enabled. The signal is limited to the programmed threshold (signal saturates at the threshold) + + threshold + floating point in range 0 to -96 dB + + attack + floating point figure is dB/s rate at which gain is increased + + decay + floating point figure is dB/s rate at which gain is reduced +*/ +uint16_t AudioControlSGTL5000_F32::autoVolumeControl(uint8_t maxGain, uint8_t lbiResponse, uint8_t hardLimit, float threshold, float attack, float decay) +{ + // if(semi_automated&&(!read(DAP_CONTROL)&1)) audioProcessorEnable(); + if (maxGain > 2) + maxGain = 2; + lbiResponse &= 3; + hardLimit &= 1; + uint8_t thresh = (pow(10, threshold / 20) * 0.636) * pow(2, 15); + uint8_t att = (1 - pow(10, -(attack / (20 * 44100)))) * pow(2, 19); + uint8_t dec = (1 - pow(10, -(decay / (20 * 44100)))) * pow(2, 23); + write(SGTL5000_DAP_AVC_THRESHOLD, thresh); + write(SGTL5000_DAP_AVC_ATTACK, att); + write(SGTL5000_DAP_AVC_DECAY, dec); + return modify(SGTL5000_DAP_AVC_CTRL, maxGain << 12 | lbiResponse << 8 | hardLimit << 5, 3 << 12 | 3 << 8 | 1 << 5); +} +uint16_t AudioControlSGTL5000_F32::autoVolumeEnable(void) +{ + return modify(SGTL5000_DAP_AVC_CTRL, 1, 1); +} +uint16_t AudioControlSGTL5000_F32::autoVolumeDisable(void) +{ + return modify(SGTL5000_DAP_AVC_CTRL, 0, 1); +} + +uint16_t AudioControlSGTL5000_F32::enhanceBass(float lr_lev, float bass_lev) +{ + return modify(SGTL5000_DAP_BASS_ENHANCE_CTRL, ((0x3F - calcVol(lr_lev, 0x3F)) << 8) | (0x7F - calcVol(bass_lev, 0x7F)), (0x3F << 8) | 0x7F); +} +uint16_t AudioControlSGTL5000_F32::enhanceBass(float lr_lev, float bass_lev, uint8_t hpf_bypass, uint8_t cutoff) +{ + modify(SGTL5000_DAP_BASS_ENHANCE, (hpf_bypass & 1) << 8 | (cutoff & 7) << 4, 1 << 8 | 7 << 4); + return enhanceBass(lr_lev, bass_lev); +} +uint16_t AudioControlSGTL5000_F32::enhanceBassEnable(void) +{ + return modify(SGTL5000_DAP_BASS_ENHANCE, 1, 1); +} +uint16_t AudioControlSGTL5000_F32::enhanceBassDisable(void) +{ + return modify(SGTL5000_DAP_BASS_ENHANCE, 0, 1); +} +uint16_t AudioControlSGTL5000_F32::surroundSound(uint8_t width) +{ + return modify(SGTL5000_DAP_SGTL_SURROUND, (width & 7) << 4, 7 << 4); +} +uint16_t AudioControlSGTL5000_F32::surroundSound(uint8_t width, uint8_t select) +{ + return modify(SGTL5000_DAP_SGTL_SURROUND, ((width & 7) << 4) | (select & 3), (7 << 4) | 3); +} +uint16_t AudioControlSGTL5000_F32::surroundSoundEnable(void) +{ + return modify(SGTL5000_DAP_SGTL_SURROUND, 3, 3); +} +uint16_t AudioControlSGTL5000_F32::surroundSoundDisable(void) +{ + return modify(SGTL5000_DAP_SGTL_SURROUND, 0, 3); +} + +unsigned char AudioControlSGTL5000_F32::calcVol(float n, unsigned char range) +{ + // n=(n*(((float)range)/100))+0.499; + n = (n * (float)range) + 0.499; + if ((unsigned char)n > range) + n = range; + return (unsigned char)n; +} + +// DAP_AUDIO_EQ_BASS_BAND0 & DAP_AUDIO_EQ_BAND1 & DAP_AUDIO_EQ_BAND2 etc etc +uint16_t AudioControlSGTL5000_F32::dap_audio_eq_band(uint8_t bandNum, float n) // by signed percentage -100/+100; dap_audio_eq(3); +{ + n = (n * 48) + 0.499; + if (n < -47) + n = -47; + if (n > 48) + n = 48; + n += 47; + return modify(SGTL5000_DAP_AUDIO_EQ_BASS_BAND0 + (bandNum * 2), (uint16_t)n, 127); +} + +void AudioControlSGTL5000_F32::automate(uint8_t dap, uint8_t eq) +{ + // if((dap!=0)&&(!(read(DAP_CONTROL)&1))) audioProcessorEnable(); + if ((read(SGTL5000_DAP_AUDIO_EQ) & 3) != eq) + eqSelect(eq); +} + +void AudioControlSGTL5000_F32::automate(uint8_t dap, uint8_t eq, uint8_t filterCount) +{ + automate(dap, eq); + if (filterCount > (read(SGTL5000_DAP_PEQ) & 7)) + eqFilterCount(filterCount); +} void AudioControlSGTL5000_F32::set_bitDepth(bit_depth_t bits) { - uint16_t regTmp = read(CHIP_I2S_CTRL); - regTmp &= ~(0x30); // clear bit 5:4 (DLEN) - regTmp |= ((uint8_t)bits << 4) & 0x30; // update DLEN + uint16_t regTmp = read(SGTL5000_CHIP_I2S_CTRL); + regTmp &= ~(0x30); // clear bit 5:4 (DLEN) + regTmp |= ((uint8_t)bits << 4) & 0x30; // update DLEN - write(CHIP_ADCDAC_CTRL, 0x000C); // mute DAC - write(CHIP_I2S_CTRL, regTmp); // write new config - write(CHIP_ADCDAC_CTRL, 0x0000); // unmute DAC + write(SGTL5000_CHIP_ADCDAC_CTRL, 0x000C); // mute DAC + write(SGTL5000_CHIP_I2S_CTRL, regTmp); // write new config + write(SGTL5000_CHIP_ADCDAC_CTRL, 0x0000); // unmute DAC } diff --git a/src/control_SGTL5000_F32.h b/src/control_SGTL5000_F32.h index dcbf776..0b7b901 100644 --- a/src/control_SGTL5000_F32.h +++ b/src/control_SGTL5000_F32.h @@ -1,40 +1,123 @@ #ifndef _CONTROL_SGTL5000_F32_H_ #define _CONTROL_SGTL5000_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. + */ + /** - * @file control_SGTL5000_ext.h - * @author Piotr Zapart - * @brief enables the bit depth setting for the SGTL5000 codec chip + * @file control_SGTL5000_F32.h + * @author Piotr Zapart + * @brief enables the bit depth setting for the SGTL5000 codec chip + configurable Wire interface * @version 0.1 * @date 2024-03-20 - * - * @copyright Copyright (c) 2024 www.hexefx.com - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software Foundation, - * either version 3 of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * You should have received a copy of the GNU General Public License along with this program. - * If not, see ." */ #include -#include +#include +#include +#include "AudioControl.h" -class AudioControlSGTL5000_F32 : public AudioControlSGTL5000 + +class AudioControlSGTL5000_F32 : public AudioControl { - //GUI: inputs:0, outputs:0 //this line used for automatic generation of GUI node - public: - AudioControlSGTL5000_F32(void) {}; +public: + AudioControlSGTL5000_F32(){}; + void setAddress(uint8_t gpioLevel); + bool enable(); + bool enable(TwoWire *i2cBus, uint8_t addr=0x0A, const uint32_t extMCLK=0, const uint32_t pllFreq = (4096.0l * AUDIO_SAMPLE_RATE_EXACT)); + bool disable(void) { return false; } + bool volume(float n) { return volumeInteger(n * 129 + 0.499f); } + bool inputLevel(float volume) {return false;} + bool muteHeadphone(void) { return write(0x0024, ana_ctrl | (1 << 4)); } + bool unmuteHeadphone(void) { return write(0x0024, ana_ctrl & ~(1 << 4)); } + bool muteLineout(void) { return write(0x0024, ana_ctrl | (1 << 8)); } + bool unmuteLineout(void) { return write(0x0024, ana_ctrl & ~(1 << 8)); } + bool inputSelect(int n); + bool headphoneSelect(int n); + + bool volume(float left, float right); + bool micGain(uint16_t dB); + bool lineInLevel(uint8_t n) { return lineInLevel(n, n); } + bool lineInLevel(uint8_t left, uint8_t right); + uint16_t lineOutLevel(uint8_t n); + uint16_t lineOutLevel(uint8_t left, uint8_t right); + uint16_t dacVolume(float n); + uint16_t dacVolume(float left, float right); + bool dacVolumeRamp(); + bool dacVolumeRampLinear(); + bool dacVolumeRampDisable(); + uint16_t adcHighPassFilterEnable(void); + uint16_t adcHighPassFilterFreeze(void); + uint16_t adcHighPassFilterDisable(void); + uint16_t audioPreProcessorEnable(void); + uint16_t audioPostProcessorEnable(void); + uint16_t audioProcessorDisable(void); + uint16_t eqFilterCount(uint8_t n); + uint16_t eqSelect(uint8_t n); + uint16_t eqBand(uint8_t bandNum, float n); + void eqBands(float bass, float mid_bass, float midrange, float mid_treble, float treble); + void eqBands(float bass, float treble); + void eqFilter(uint8_t filterNum, int *filterParameters); + uint16_t autoVolumeControl(uint8_t maxGain, uint8_t lbiResponse, uint8_t hardLimit, float threshold, float attack, float decay); + uint16_t autoVolumeEnable(void); + uint16_t autoVolumeDisable(void); + uint16_t enhanceBass(float lr_lev, float bass_lev); + uint16_t enhanceBass(float lr_lev, float bass_lev, uint8_t hpf_bypass, uint8_t cutoff); + uint16_t enhanceBassEnable(void); + uint16_t enhanceBassDisable(void); + uint16_t surroundSound(uint8_t width); + uint16_t surroundSound(uint8_t width, uint8_t select); + uint16_t surroundSoundEnable(void); + uint16_t surroundSoundDisable(void); + void killAutomation(void) { semi_automated = false; } + void setMasterMode(uint32_t freqMCLK_in); typedef enum - { + { I2S_BITS_32 = 0, I2S_BITS_24, I2S_BITS_20, I2S_BITS_16 }bit_depth_t; - void set_bitDepth(bit_depth_t bits); + +protected: + bool muted; + bool volumeInteger(uint16_t n); // range: 0x00 to 0x80 + uint16_t ana_ctrl; + uint8_t i2c_addr; + unsigned char calcVol(float n, unsigned char range); + uint16_t read(uint16_t reg); + bool write(uint16_t reg, uint16_t val); + uint16_t modify(uint16_t reg, uint16_t val, uint16_t iMask); + uint16_t dap_audio_eq_band(uint8_t bandNum, float n); + +private: + bool semi_automated; + void automate(uint8_t dap, uint8_t eq); + void automate(uint8_t dap, uint8_t eq, uint8_t filterCount); + TwoWire *_wire; }; -#endif // _CONTROL_SGTL5000_EXT_H_ +#endif // _CONTROL_SGTL5000_F32_H_ diff --git a/src/control_WM8731_F32.cpp b/src/control_WM8731_F32.cpp index 41978ef..e95ebd9 100644 --- a/src/control_WM8731_F32.cpp +++ b/src/control_WM8731_F32.cpp @@ -198,116 +198,179 @@ #define WM8731_REG_ACTIVE (9) #define WM8731_REG_RESET (15) - -bool AudioControlWM8731_F32::enable(bit_depth_t bits, uint8_t addr) +// ---------------------------------------------------------------------------------- +bool AudioControlWM8731_F32::enable(bit_depth_t bits, TwoWire *i2cBus, uint8_t addr) { + _wire = i2cBus; i2c_addr = addr; - Wire.begin(); + + _wire->begin(); delay(5); if (!write(WM8731_REG_RESET, 0)) { return false; // no WM8731 chip responding } + write(WM8731_REG_INTERFACE, WM8731_BITS_FORMAT(WM8731_FORMAT_I2S_MSB_LEFT) | - WM8731_BITS_IWL(bits)); // I2S, x bit, MCLK slave - write(WM8731_REG_SAMPLING, 0x20); // 256*Fs, 44.1 kHz, MCLK/1 - - // In order to prevent pops, the DAC should first be soft-muted (DACMU), - // the output should then be de-selected from the line and headphone output - // (DACSEL), then the DAC powered down (DACPD). - - write(WM8731_REG_DIGITAL, 0x08); // DAC soft mute + WM8731_BITS_IWL(bits)); // I2S, x bit, MCLK slave + + write(WM8731_REG_SAMPLING, WM8731_BITS_USB_NORMAL(0) | // normal mode + WM8731_BITS_BOSR(0) | // 256*fs + WM8731_BITS_SR(8) | // 44.1kHz + WM8731_BITS_CLKIDIV2(0) | // MCLK/1 + WM8731_BITS_CLKODIV2(0)); + + write(WM8731_REG_DIGITAL, WM8731_BITS_DACMU(1)); // Soft mute DAC write(WM8731_REG_ANALOG, 0x00); // disable all - write(WM8731_REG_POWERDOWN, 0x00); // codec powerdown - write(WM8731_REG_LHEADOUT, 0x80); // volume off write(WM8731_REG_RHEADOUT, 0x80); - - delay(100); // how long to power up? - + delay(300); write(WM8731_REG_ACTIVE, 1); delay(5); - write(WM8731_REG_DIGITAL, 0x00); // DAC unmuted - write(WM8731_REG_ANALOG, 0x10); // DAC selected - + write(WM8731_REG_DIGITAL, WM8731_BITS_DACMU(0)); // DAC unmuted + write(WM8731_REG_ANALOG, WM8731_BITS_DACSEL(1)); // DAC selected return true; } - +// ---------------------------------------------------------------------------------- void AudioControlWM8731_F32::dac_mute(bool m) { - write(WM8731_REG_DIGITAL, m ? WM8731_BITS_DACMU(1) : WM8731_BITS_DACMU(0)); // DAC soft mute + 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; } - -void AudioControlWM8731_F32::HPfilter(bool state) +// ---------------------------------------------------------------------------------- +void AudioControlWM8731_F32::hp_filter(bool state) { - write(WM8731_REG_DIGITAL, WM8731_BITS_DACMU(DACmute) | WM8731_BITS_ADCHPD(state)); + modify(WM8731_REG_DIGITAL, WM8731_BITS_ADCHPD(state), WM8731_BITS_ADCHPD_MASK); + //write(WM8731_REG_DIGITAL, WM8731_BITS_DACMU(DACmute) | WM8731_BITS_ADCHPD(state)); } - -bool AudioControlWM8731_F32::write(unsigned int reg, unsigned int val) +// ---------------------------------------------------------------------------------- +// Freeze the HP filter +void AudioControlWM8731_F32::dcbias_store(bool state) { + modify(WM8731_REG_DIGITAL, WM8731_BITS_HPOR(state), WM8731_BITS_HPOR_MASK); +} +// ---------------------------------------------------------------------------------- +// Mute both Line inputs +void AudioControlWM8731_F32::lineIn_mute(bool m) +{ + modify(WM8731_REG_LLINEIN, WM8731_BITS_LINMUTE(m), WM8731_BITS_LINMUTE_MASK); + modify(WM8731_REG_RLINEIN, WM8731_BITS_RINMUTE(m), WM8731_BITS_RINMUTE_MASK); +} +// ---------------------------------------------------------------------------------- +// Enable/Disable DAC output mixer switch +void AudioControlWM8731_F32::dac_enable(bool en) +{ + modify(WM8731_REG_ANALOG, WM8731_BITS_DACSEL(en), WM8731_BITS_DACSEL_MASK); +} +// ---------------------------------------------------------------------------------- +// Enable Dry (Bypass) output mixer switch +void AudioControlWM8731_F32::dry_enable(bool en) +{ + modify(WM8731_REG_ANALOG, WM8731_BITS_BYPASS(en), WM8731_BITS_BYPASS_MASK); + dry_sig = en; +} +// ---------------------------------------------------------------------------------- +// analog bypass switch +void AudioControlWM8731_F32::bypass_set(bool b) +{ + uint8_t bp_state = ((((uint8_t)dry_sig)<<1) & 0x01) | b; + switch(bp_state) + { + case 0b00: // Dry Off, Wet on -> pass Wet only + case 0b11: + dry_enable(false); + dac_enable(true); + break; + case 0b01: // dry OFF, bypass ON -> pass Dry only + dry_enable(false); + dac_enable(true); + break; + case 0b10: // Dry on, Wet on + dry_enable(true); + dac_enable(true); + break; + default: break; + } +} +// ---------------------------------------------------------------------------------- +bool AudioControlWM8731_F32::write(uint16_t regAddr, uint16_t val) +{ + reg[regAddr] = val; int attempt = 0; while (1) { attempt++; - Wire.beginTransmission(i2c_addr); - Wire.write((reg << 1) | ((val >> 8) & 1)); - Wire.write(val & 0xFF); - int status = Wire.endTransmission(); + _wire->beginTransmission(i2c_addr); + _wire->write((regAddr << 1) | ((val >> 8) & 1)); + _wire->write(val & 0xFF); + int status = _wire->endTransmission(); if (status == 0) return true; if (attempt >= 12) return false; delayMicroseconds(80); } } - - -bool AudioControlWM8731_F32::volumeInteger(unsigned int n) +// ---------------------------------------------------------------------------------- +uint16_t AudioControlWM8731_F32::modify(uint16_t regAddr, uint16_t val, uint16_t iMask) +{ + reg[regAddr] = (reg[regAddr] & (~iMask)) | val; + if (!write(regAddr, reg[regAddr])) return 0; + return reg[regAddr]; +} +// ---------------------------------------------------------------------------------- +// Set the headphone volume +bool AudioControlWM8731_F32::hp_volumeInteger(uint16_t n) { // n = 127 for max volume (+6 dB) // n = 48 for min volume (-73 dB) // n = 0 to 47 for mute - if (n > 127) - n = 127; - // Serial.print("volumeInteger, n = "); - // Serial.println(n); + if (n > 127) n = 127; write(WM8731_REG_LHEADOUT, n | 0x180); write(WM8731_REG_RHEADOUT, n | 0x80); return true; } - +// ---------------------------------------------------------------------------------- bool AudioControlWM8731_F32::inputLevel(float n) { // range is 0x00 (min) - 0x1F (max) - int _level = int(n * 31.f); - _level = _level > 0x1F ? 0x1F : _level; write(WM8731_REG_LLINEIN, _level); write(WM8731_REG_RLINEIN, _level); return true; } - -bool AudioControlWM8731_F32::inputSelect(input_select_t n) +// ---------------------------------------------------------------------------------- +bool AudioControlWM8731_F32::inputLevelraw(uint8_t n) +{ + // range is 0x00 (min) - 0x1F (max) + n = n > 0x1F ? 0x1F : n; + write(WM8731_REG_LLINEIN, n); + write(WM8731_REG_RLINEIN, n); + return true; +} +// ---------------------------------------------------------------------------------- +bool AudioControlWM8731_F32::inputSelect(int n) { - if (n == INPUT_SELECT_LINEIN) write(WM8731_REG_ANALOG, 0x12); - else if (n == INPUT_SELECT_MIC) write(WM8731_REG_ANALOG, 0x15); + if (n == AUDIO_INPUT_LINEIN) modify(WM8731_REG_ANALOG, WM8731_BITS_INSEL(0), WM8731_BITS_INSEL_MASK); + else if (n == AUDIO_INPUT_MIC) modify(WM8731_REG_ANALOG, WM8731_BITS_INSEL(1), WM8731_BITS_INSEL_MASK); else return false; return true; } /******************************************************************/ -bool AudioControlWM8731_F32_master::enable(bit_depth_t bits, uint8_t addr) +bool AudioControlWM8731_F32_master::enable(bit_depth_t bits, TwoWire *i2cBus, uint8_t addr) { + _wire = i2cBus; i2c_addr = addr; - Wire.begin(); + _wire->begin(); delay(5); // write(WM8731_REG_RESET, 0); write(WM8731_REG_INTERFACE, WM8731_BITS_FORMAT(WM8731_FORMAT_I2S_MSB_LEFT) | WM8731_BITS_IWL(bits)| - WM8731_BITS_MS(1)); // I2S, x bit, MCLK slave + WM8731_BITS_MS(1)); // I2S, x bit, MCLK master write(WM8731_REG_SAMPLING, 0x20); // 256*Fs, 44.1 kHz, MCLK/1 // In order to prevent pops, the DAC should first be soft-muted (DACMU), diff --git a/src/control_WM8731_F32.h b/src/control_WM8731_F32.h index 0f367be..404ce84 100644 --- a/src/control_WM8731_F32.h +++ b/src/control_WM8731_F32.h @@ -19,14 +19,16 @@ #define _CONTROL_WM8731_F32_H_ #include +#include +#include "AudioControl.h" #define WM8731_I2C_ADDR_CSB0 0x1A #define WM8731_I2C_ADDR_CSB1 0x1B -class AudioControlWM8731_F32 +class AudioControlWM8731_F32 : public AudioControl { public: - AudioControlWM8731_F32(){}; + AudioControlWM8731_F32() {} typedef enum { @@ -36,34 +38,50 @@ public: I2S_BITS_32 }bit_depth_t; - typedef enum + bool enable() { - INPUT_SELECT_LINEIN = 0, - INPUT_SELECT_MIC - }input_select_t; - - bool enable(bit_depth_t bits = I2S_BITS_16, uint8_t addr=WM8731_I2C_ADDR_CSB0); + return enable(I2S_BITS_32, &Wire, WM8731_I2C_ADDR_CSB0); + } + bool enable(bit_depth_t bits, TwoWire *i2cBus, uint8_t addr); bool disable(void) { return false; } - bool volume(float n) { return volumeInteger(n * 80.0f + 47.499f); } + bool volume(float n) { return hp_volumeInteger(n * 80.0f + 47.499f); } bool inputLevel(float n); // range: 0.0f to 1.0f - bool inputSelect(input_select_t n=INPUT_SELECT_LINEIN); + bool inputLevelraw(uint8_t n); // direc value 0-31 + bool inputSelect(int n=AUDIO_INPUT_LINEIN); + + void lineIn_mute(bool m); + void dry_enable(bool en); // bypass without muting the DAC + void hp_filter(bool state); + void dcbias_store(bool state); void dac_mute(bool m); - void HPfilter(bool state); + void dac_enable(bool en); + void bypass_set(bool b); + bool bypass_get() {return bp;} + bool bypass_tgl() {bp ^= 1; bypass_set(bp); return bp;} protected: - bool write(unsigned int reg, unsigned int val); - bool volumeInteger(unsigned int n); // range: 0x2F to 0x7F + bool write(uint16_t regAddr, uint16_t val); + uint16_t read(uint16_t reg); + uint16_t modify(uint16_t reg, uint16_t val, uint16_t iMask); + bool hp_volumeInteger(uint16_t n); // range: 0x2F to 0x7F private: - uint8_t bit_depth = I2S_BITS_16; + uint16_t reg[16]; + uint8_t bit_depth = I2S_BITS_32; uint8_t i2c_addr; bool DACmute = false; + TwoWire *_wire; + bool dry_sig = false; + bool bp = false; // used for analog bypass }; class AudioControlWM8731_F32_master : public AudioControlWM8731_F32 { public: - bool enable(bit_depth_t bits = I2S_BITS_16, uint8_t addr=WM8731_I2C_ADDR_CSB0); + bool enable(bit_depth_t bits = I2S_BITS_32, TwoWire *i2cBus = &Wire, uint8_t addr=WM8731_I2C_ADDR_CSB0); private: + uint8_t i2c_addr; + TwoWire *_wire; + bool dry_sig = false; }; #endif // _CONTROL_WM8731_EXTENDED_H_ diff --git a/src/effect_compressorStereo_F32.h b/src/effect_compressorStereo_F32.h index 77e57b3..e8abb78 100644 --- a/src/effect_compressorStereo_F32.h +++ b/src/effect_compressorStereo_F32.h @@ -18,6 +18,23 @@ #include //ARM DSP extensions. https://www.keil.com/pack/doc/CMSIS/DSP/html/index.html #include +#include "basic_DSPutils.h" + +// ranges used for normalized parameters. +// input is 0.0f to 1.0f, output RANGE_MIN to RANGE_MAX +#define COMPRESSOR_PREGAIN_RANGE_MIN (0.0f) +#define COMPRESSOR_PREGAIN_RANGE_MAX (4.0f) +#define COMPRESSOR_POSTGAIN_RANGE_MIN (0.0f) +#define COMPRESSOR_POSTGAIN_RANGE_MAX (4.0f) +#define COMPRESSOR_ATTACK_RANGE_MIN (0.001f) +#define COMPRESSOR_ATTACK_RANGE_MAX (0.1f) +#define COMPRESSOR_RELEASE_RANGE_MIN (0.1f) +#define COMPRESSOR_RELEASE_RANGE_MAX (1.0f) +#define COMPRESSOR_THRES_RANGE_MIN (0.0f) +#define COMPRESSOR_THRES_RANGE_MAX (-40.0f) +#define COMPRESSOR_RATIO_RANGE_MIN (0.0f) +#define COMPRESSOR_RATIO_RANGE_MAX (10.0f) + class AudioEffectCompressorStereo_F32 : public AudioStream_F32 { @@ -281,30 +298,48 @@ public: arm_biquad_cascade_df1_init_f32(&hp_filt_structR, hp_nstages, hp_coeff, hp_stateR); } void setPreGain(float g) { pre_gain = g; } + void setPreGain_normalized(float g) { pre_gain = map_sat(g, 0.0f, 1.0f, COMPRESSOR_PREGAIN_RANGE_MIN, COMPRESSOR_PREGAIN_RANGE_MAX); } void setPreGain_dB(float gain_dB) { setPreGain(pow(10.0f, gain_dB / 20.0f)); } void setPostGain(float g) { post_gain = g; } + void setPostGain_normalized(float g) { post_gain = map_sat(g, 0.0f, 1.0f, COMPRESSOR_POSTGAIN_RANGE_MIN, COMPRESSOR_POSTGAIN_RANGE_MAX); } void setPostGain_dB(float gain_dB) { setPostGain(pow(10.0f, gain_dB / 20.0f)); } + void setCompressionRatio(float cr) { comp_ratio = max(0.001f, cr); // limit to positive values updateThresholdAndCompRatioConstants(); } + void setCompressionRatio_normalized(float cr) + { + cr = map_sat(cr, 0.0f, 1.0f, COMPRESSOR_RATIO_RANGE_MIN, COMPRESSOR_RATIO_RANGE_MAX); + setCompressionRatio(cr); + } + void setAttack_sec(float a) { attack_sec = a; attack_const = expf(-1.0f / (attack_sec * fs_Hz)); // expf() is much faster than exp() - // also update the time constant for the envelope extraction setLevelTimeConst_sec(min(attack_sec, release_sec) / 5.0f); // make the level time-constant one-fifth the gain time constants } + void setAttack_normalized(float a) + { + a = map_sat(a, 0.0f, 1.0f, COMPRESSOR_ATTACK_RANGE_MIN, COMPRESSOR_ATTACK_RANGE_MAX); + setAttack_sec(a); + } + void setRelease_sec(float r) { release_sec = r; release_const = expf(-1.0f / (release_sec * fs_Hz)); // expf() is much faster than exp() - // also update the time constant for the envelope extraction setLevelTimeConst_sec(min(attack_sec, release_sec) / 5.0f); // make the level time-constant one-fifth the gain time constants } + void setRelease_normalized(float r) + { + r = map_sat(r, 0.0f, 1.0f, COMPRESSOR_RELEASE_RANGE_MIN, COMPRESSOR_RELEASE_RANGE_MAX); + setRelease_sec(r); + } void setLevelTimeConst_sec(float t_sec) { const float min_t_sec = 0.002f; // this is the minimum allowed value @@ -316,6 +351,12 @@ public: thresh_dBFS = val; setThreshPow(pow(10.0f, thresh_dBFS / 10.0f)); } + void setThresh_normalized(float val) + { + val = map_sat(val, 0.0f, 1.0f, COMPRESSOR_THRES_RANGE_MIN, COMPRESSOR_THRES_RANGE_MAX); + setThresh_dBFS(val); + } + void enableHPFilter(boolean flag) { use_HP_prefilter = flag; }; // methods to return information about this module diff --git a/src/effect_delaystereo_F32.cpp b/src/effect_delaystereo_F32.cpp index 9d4fea6..a13413a 100644 --- a/src/effect_delaystereo_F32.cpp +++ b/src/effect_delaystereo_F32.cpp @@ -38,19 +38,27 @@ AudioEffectDelayStereo_F32::AudioEffectDelayStereo_F32(uint32_t dly_range_ms, bo void AudioEffectDelayStereo_F32::begin(uint32_t dly_range_ms, bool use_psram) { + initialized = false; // failsafe if psram is required but not found // limit the delay time to 500ms (88200 bytes at 44.1kHz) + psram_mode = use_psram; + #if ARDUINO_TEENSY41 if (psram_mode && external_psram_size == 0) { psram_mode = false; - if (dly_range_ms > 500) dly_range_ms = 500; + if (dly_range_ms > 200) dly_range_ms = 200; } + #else + psram_mode = false; + if (dly_range_ms > 200) dly_range_ms = 200; + + #endif bool memOk = true; - dly_length = ((float32_t)(dly_range_ms+500)/1000.0f) * AUDIO_SAMPLE_RATE_EXACT; - if (!dly0a.init(dly_length, use_psram)) memOk = false; - if (!dly0b.init(dly_length, use_psram)) memOk = false; - if (!dly1a.init(dly_length, use_psram)) memOk = false; - if (!dly1b.init(dly_length, use_psram)) memOk = false; + dly_length = ((float32_t)(dly_range_ms)/1000.0f) * AUDIO_SAMPLE_RATE_EXACT; + if (!dly0a.init(dly_length, psram_mode)) memOk = false; + if (!dly0b.init(dly_length, psram_mode)) memOk = false; + if (!dly1a.init(dly_length, psram_mode)) memOk = false; + if (!dly1b.init(dly_length, psram_mode)) memOk = false; flt0L.init(BASS_LOSS_FREQ, &bassCut_k, TREBLE_LOSS_FREQ, &trebleCut_k); flt1L.init(BASS_LOSS_FREQ, &bass_k, TREBLE_LOSS_FREQ, &treble_k); flt0R.init(BASS_LOSS_FREQ, &bassCut_k, TREBLE_LOSS_FREQ, &trebleCut_k); @@ -254,6 +262,7 @@ void AudioEffectDelayStereo_F32::freeze(bool state) */ bool AudioEffectDelayStereo_F32::memCleanup() { + static uint8_t dlyIdx = 0; bool result = false; if (dlyIdx == 0) // value 0 is used to reset the addr diff --git a/src/effect_delaystereo_F32.h b/src/effect_delaystereo_F32.h index e495f37..8e46feb 100644 --- a/src/effect_delaystereo_F32.h +++ b/src/effect_delaystereo_F32.h @@ -280,7 +280,8 @@ public: delay(tempo_ticks); } return tempo_ticks; - } + } + bool is_initialized() {return initialized;} private: audio_block_f32_t *inputQueueArray[2]; diff --git a/src/effect_gainStereo_F32.h b/src/effect_gainStereo_F32.h index fee317c..426db12 100644 --- a/src/effect_gainStereo_F32.h +++ b/src/effect_gainStereo_F32.h @@ -34,6 +34,7 @@ public: audio_block_f32_t *blockL, *blockR; blockL = AudioStream_F32::receiveWritable_f32(0); blockR = AudioStream_F32::receiveWritable_f32(1); + float gL, gR; if (!blockL || !blockR) { if (blockL) @@ -44,9 +45,10 @@ public: } gainL += (gainLset - gainL) * 0.25f; gainR += (gainRset - gainR) * 0.25f; - - arm_scale_f32(blockL->data, gainL, blockL->data, blockL->length); // use ARM DSP for speed! - arm_scale_f32(blockR->data, gainR, blockR->data, blockR->length); + if (phase_flip) { gL = -gainL; gR = -gainR; } + else { gL = gainL; gR = gainR; } + arm_scale_f32(blockL->data, gL, blockL->data, blockL->length); // use ARM DSP for speed! + arm_scale_f32(blockR->data, gR, blockR->data, blockR->length); AudioStream_F32::transmit(blockL, 0); AudioStream_F32::transmit(blockR, 1); AudioStream_F32::release(blockL); @@ -90,11 +92,19 @@ public: } float32_t getPan() { return pan;} + void phase_inv(bool inv) + { + __disable_irq(); + phase_flip = inv; + __enable_irq(); + } + private: audio_block_f32_t *inputQueueArray_f32[2]; // memory pointer for the input to this module float32_t gain = 1.0f; // default value float32_t gainL, gainR, gainLset, gainRset; float32_t pan, panL, panR; + bool phase_flip = false; }; #endif \ No newline at end of file diff --git a/src/effect_guitarBooster_F32.h b/src/effect_guitarBooster_F32.h index ed81e46..6a1ac74 100644 --- a/src/effect_guitarBooster_F32.h +++ b/src/effect_guitarBooster_F32.h @@ -59,6 +59,7 @@ public: tone(1.0f); hpPost_k = omega(GBOOST_BOTTOM_MINF); lp2_k = omega(GBOOST_LP2_F); + gainRange = 4.0f; } void drive(float32_t value) { @@ -68,6 +69,27 @@ public: gainSet = value; __enable_irq(); } + /** + * @brief Normalized drive, scaled to 1.0 ... gainRange value + * + * @param value 0.0f - 1.0f + */ + void drive_normalized(float32_t value) + { + value = fabs(value); + value = constrain(value, 0.0f, 1.0f); + value = 1.0f + value * upsample_k * gainRange; + __disable_irq() + gainSet = value; + __enable_irq(); + } + void driveRange(float32_t value) + { + __disable_irq() + gainRange = value; + __enable_irq(); + } + void bottom(float32_t bottom); void tone(float32_t t); void bias(float32_t b) @@ -146,8 +168,8 @@ private: float32_t dryGain = 0.0f; float32_t wetGain = 1.0f; float32_t DCbias = 0.175f; - - float32_t gainSet = 1.0f; + float32_t gainRange = 4.0f; + 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_hp = 1.0f; float32_t levelSet = 1.0f; diff --git a/src/effect_noiseGateStereo_F32.h b/src/effect_noiseGateStereo_F32.h index 506c2b0..6de32ef 100644 --- a/src/effect_noiseGateStereo_F32.h +++ b/src/effect_noiseGateStereo_F32.h @@ -16,6 +16,16 @@ #include //ARM DSP extensions. for speed! #include +// ranges used for normalized param settings +#define NOISEGATE_THRES_MIN (0.0f) +#define NOISEGATE_THRES_MAX (-100.0f) +#define NOISEGATE_OPENT_MIN (0.001f) +#define NOISEGATE_OPENT_MAX (0.1f) +#define NOISEGATE_HOLDT_MIN (0.001f) +#define NOISEGATE_HOLDT_MAX (0.1f) +#define NOISEGATE_CLOSET_MIN (0.001f) +#define NOISEGATE_CLOSET_MAX (0.1f) + class AudioEffectNoiseGateStereo_F32 : public AudioStream_F32 { public: @@ -108,21 +118,41 @@ public: // convert dbFS to linear value to comapre against later linearThreshold = pow10f(dbfs / 20.0f); } + void setThreshold_normalized(float dbfs) + { + dbfs = map_sat(dbfs, 0.0f, 1.0f, NOISEGATE_THRES_MIN, NOISEGATE_THRES_MAX); + setThreshold(dbfs); + } void setOpeningTime(float timeInSeconds) { openingTimeConst = expf(-1.0f / (timeInSeconds * fs)); } + void setOpeningTime_normalized(float time) + { + time = map_sat(time, 0.0f, 1.0f, NOISEGATE_OPENT_MIN, NOISEGATE_OPENT_MAX); + setOpeningTime(time); + } void setClosingTime(float timeInSeconds) { closingTimeConst = expf(-1.0f / (timeInSeconds * fs)); } + void setClosingTime_normalized(float time) + { + time = map_sat(time, 0.0f, 1.0f, NOISEGATE_CLOSET_MIN, NOISEGATE_CLOSET_MAX); + setClosingTime(time); + } void setHoldTime(float timeInSeconds) { holdTimeNumSamples = timeInSeconds * fs; } + void setHoldTime_normalized(float time) + { + time = map_sat(time, 0.0f, 1.0f, NOISEGATE_HOLDT_MIN, NOISEGATE_HOLDT_MAX); + setHoldTime(time); + } bool infoIsOpen() { diff --git a/src/effect_reverbsc_F32.cpp b/src/effect_reverbsc_F32.cpp index f8532a9..b7e25d9 100644 --- a/src/effect_reverbsc_F32.cpp +++ b/src/effect_reverbsc_F32.cpp @@ -49,13 +49,20 @@ AudioEffectReverbSc_F32::AudioEffectReverbSc_F32(bool use_psram) : AudioStream_F n_bytes = 0; if (use_psram) { + #if ARDUINO_TEENSY41 // no PSRAM detected - enter the memoery failsafe mode = fixed bypass if (external_psram_size == 0) { flags.mem_fail = 1; + initialised = true; return; } aux_ = (float32_t *) extmem_malloc(aux_size_bytes); + #else + flags.mem_fail = 1; + initialised = true; + return; + #endif } else { diff --git a/src/effect_xfaderStereo_F32.h b/src/effect_xfaderStereo_F32.h index db3384f..f6a9cc3 100644 --- a/src/effect_xfaderStereo_F32.h +++ b/src/effect_xfaderStereo_F32.h @@ -41,39 +41,44 @@ public: void update() { audio_block_f32_t *blockLa, *blockRa, *blockLb, *blockRb; - audio_block_f32_t *blockOutLa, *blockOutRa,*blockOutLb, *blockOutRb; + audio_block_f32_t *blockOutLa, *blockOutRa,*blockOutLb, *blockOutRb, *blockZero; + + blockZero = AudioStream_F32::allocate_f32(); + if(!blockZero) return; + memset(&blockZero->data[0], 0, blockZero->length*sizeof(float32_t)); + + blockLa = AudioStream_F32::receiveReadOnly_f32(0); blockRa = AudioStream_F32::receiveReadOnly_f32(1); blockLb = AudioStream_F32::receiveReadOnly_f32(2); blockRb = AudioStream_F32::receiveReadOnly_f32(3); - if (!blockLa || !blockRa || !blockLb || !blockRb) - { - if (blockLa) AudioStream_F32::release(blockLa); - if (blockRa) AudioStream_F32::release(blockRa); - if (blockLb) AudioStream_F32::release(blockLb); - if (blockRb) AudioStream_F32::release(blockRb); - return; - } + if (!blockLa) blockLa = blockZero; + if (!blockLb) blockLb = blockZero; + if (!blockRa) blockRa = blockZero; + if (!blockRb) blockRb = blockZero; + // max A, B mited if (gainA == 1.0f) { AudioStream_F32::transmit(blockLa, 0); AudioStream_F32::transmit(blockRa, 1); - AudioStream_F32::release(blockLa); - AudioStream_F32::release(blockRa); - AudioStream_F32::release(blockLb); - AudioStream_F32::release(blockRb); + if (blockLa != blockZero) AudioStream_F32::release(blockLa); + if (blockRa != blockZero) AudioStream_F32::release(blockRa); + if (blockLb != blockZero) AudioStream_F32::release(blockLb); + if (blockRb != blockZero) AudioStream_F32::release(blockRb); + AudioStream_F32::release(blockZero); return; } if (gainB == 1.0f) { AudioStream_F32::transmit(blockLb, 0); AudioStream_F32::transmit(blockRb, 1); - AudioStream_F32::release(blockLa); - AudioStream_F32::release(blockRa); - AudioStream_F32::release(blockLb); - AudioStream_F32::release(blockRb); + if (blockLa != blockZero) AudioStream_F32::release(blockLa); + if (blockRa != blockZero) AudioStream_F32::release(blockRa); + if (blockLb != blockZero) AudioStream_F32::release(blockLb); + if (blockRb != blockZero) AudioStream_F32::release(blockRb); + AudioStream_F32::release(blockZero); return; } blockOutLa = AudioStream_F32::allocate_f32(); @@ -97,14 +102,16 @@ public: arm_add_f32(blockOutRa->data, blockOutRb->data, blockOutRa->data, blockOutRa->length); AudioStream_F32::transmit(blockOutLa, 0); AudioStream_F32::transmit(blockOutRa, 1); - AudioStream_F32::release(blockLa); - AudioStream_F32::release(blockRa); - AudioStream_F32::release(blockLb); - AudioStream_F32::release(blockRb); + if (blockLa != blockZero) AudioStream_F32::release(blockLa); + if (blockRa != blockZero) AudioStream_F32::release(blockRa); + if (blockLb != blockZero) AudioStream_F32::release(blockLb); + if (blockRb != blockZero) AudioStream_F32::release(blockRb); + AudioStream_F32::release(blockZero); AudioStream_F32::release(blockOutLa); AudioStream_F32::release(blockOutRa); AudioStream_F32::release(blockOutLb); - AudioStream_F32::release(blockOutRb); + AudioStream_F32::release(blockOutRb); + } private: audio_block_f32_t *inputQueueArray_f32[4]; diff --git a/src/filter_ir_cabsim_F32.cpp b/src/filter_ir_cabsim_F32.cpp index d20696a..a19ebdf 100644 --- a/src/filter_ir_cabsim_F32.cpp +++ b/src/filter_ir_cabsim_F32.cpp @@ -222,6 +222,8 @@ void AudioFilterIRCabsim_F32::ir_load(uint8_t idx) delay.reset(); ir_loaded = 1; + ir_loadState = IR_LOAD_FINISHED; + AudioInterrupts(); #endif diff --git a/src/filter_ir_cabsim_F32.h b/src/filter_ir_cabsim_F32.h index e83745e..78427a8 100644 --- a/src/filter_ir_cabsim_F32.h +++ b/src/filter_ir_cabsim_F32.h @@ -78,6 +78,7 @@ public: return doubleTrack; } bool doubler_get() {return doubleTrack;} + bool init_done() {return initialized;} private: audio_block_f32_t *inputQueueArray_f32[2]; uint16_t block_size = AUDIO_BLOCK_SAMPLES; diff --git a/src/hexefx_audio_F32.h b/src/hexefx_audio_F32.h index 49784ed..6d51170 100644 --- a/src/hexefx_audio_F32.h +++ b/src/hexefx_audio_F32.h @@ -3,7 +3,10 @@ #include "control_WM8731_F32.h" #include "control_SGTL5000_F32.h" +#include "control_ES8388_F32.h" +#include "input_i2s_ext_F32.h" +#include "output_i2s_ext_F32.h" // extended version #include "input_i2s2_F32.h" #include "output_i2s2_F32.h" diff --git a/src/input_i2s_ext_F32.cpp b/src/input_i2s_ext_F32.cpp new file mode 100644 index 0000000..d61895b --- /dev/null +++ b/src/input_i2s_ext_F32.cpp @@ -0,0 +1,240 @@ +/* + * input_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 +// Removed unused pieces. RSL 30 May 2022 + +/* + * Rewritten ny Piotr Zapart 04.2024: + * - made the 32bit mode default + * - new scale methods based on arm dsp library + * - For Teensy4.x only as Teensy3 is obsolete +*/ + +#include //do we really need this? (Chip: 2020-10-31) +#include "input_i2s_ext_F32.h" +#include "output_i2s_ext_F32.h" +#include "basic_DSPutils.h" +#include + +// DMAMEM __attribute__((aligned(32))) +static uint64_t i2s_rx_buffer[AUDIO_BLOCK_SAMPLES]; // Two 32-bit transfers per sample. +audio_block_f32_t *AudioInputI2S_ext_F32::block_left_f32 = NULL; +audio_block_f32_t *AudioInputI2S_ext_F32::block_right_f32 = NULL; +uint16_t AudioInputI2S_ext_F32::block_offset = 0; +bool AudioInputI2S_ext_F32::update_responsibility = false; +DMAChannel AudioInputI2S_ext_F32::dma(false); + +int AudioInputI2S_ext_F32::flag_out_of_memory = 0; +unsigned long AudioInputI2S_ext_F32::update_counter = 0; + +float AudioInputI2S_ext_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; +int AudioInputI2S_ext_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; + +#define I2S_BUFFER_TO_USE_BYTES (AudioOutputI2S_ext_F32::audio_block_samples * sizeof(i2s_rx_buffer[0])) + + +void AudioInputI2S_ext_F32::begin() +{ + dma.begin(true); // Allocate the DMA channel first + + AudioOutputI2S_ext_F32::sample_rate_Hz = sample_rate_Hz; // these were given in the AudioSettings in the contructor + AudioOutputI2S_ext_F32::audio_block_samples = audio_block_samples; // these were given in the AudioSettings in the contructor + + // TODO: should we set & clear the I2S_RCSR_SR bit here? + AudioOutputI2S_ext_F32::config_i2s(); +#if defined(__IMXRT1062__) + CORE_PIN8_CONFIG = 3; // 1:RX_DATA0 + IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2; + + dma.TCD->SADDR = (void *)((uint32_t)&I2S1_RDR0 + 0); + dma.TCD->SOFF = 0; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLNO = 4; + dma.TCD->SLAST = 0; + dma.TCD->DADDR = i2s_rx_buffer; + dma.TCD->DOFF = 4; + dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4; + dma.TCD->DLASTSGA = -I2S_BUFFER_TO_USE_BYTES; + dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_RX); + + I2S1_RCSR = I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR; +#endif + update_responsibility = update_setup(); + dma.enable(); + dma.attachInterrupt(isr); + + update_counter = 0; +} + +void AudioInputI2S_ext_F32::isr(void) +{ + uint32_t daddr, offset; + const int32_t *src, *end; + float32_t *dest_left_f32, *dest_right_f32; + audio_block_f32_t *left_f32, *right_f32; + +#if defined(__IMXRT1062__) + daddr = (uint32_t)(dma.TCD->DADDR); +#endif + dma.clearInterrupt(); + + if (daddr < (uint32_t)i2s_rx_buffer + I2S_BUFFER_TO_USE_BYTES / 2) + { + // DMA is receiving to the first half of the buffer + // need to remove data from the second half + src = (int32_t *)&i2s_rx_buffer[audio_block_samples / 2]; + end = (int32_t *)&i2s_rx_buffer[audio_block_samples]; + update_counter++; // let's increment the counter here to ensure that we get every ISR resulting in audio + if (AudioInputI2S_ext_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 = (int32_t *)&i2s_rx_buffer[0]; + end = (int32_t *)&i2s_rx_buffer[audio_block_samples / 2]; + } + left_f32 = AudioInputI2S_ext_F32::block_left_f32; + right_f32 = AudioInputI2S_ext_F32::block_right_f32; + if (left_f32 != NULL && right_f32 != NULL) + { + offset = AudioInputI2S_ext_F32::block_offset; + if (offset <= ((uint32_t)audio_block_samples / 2)) + { + dest_left_f32 = &(left_f32->data[offset]); + dest_right_f32 = &(right_f32->data[offset]); + AudioInputI2S_ext_F32::block_offset = offset + audio_block_samples / 2; + do + { + *dest_left_f32++ = (float32_t)*src++; + *dest_right_f32++ = (float32_t)*src++; + } while (src < end); + } + } +} + +void AudioInputI2S_ext_F32::update_1chan(int chan, audio_block_f32_t *&out_f32) +{ + if (!out_f32) + return; + // scale the float values so that the maximum possible audio values span -1.0 to + 1.0 + arm_scale_f32(out_f32->data, I32_TO_F32_NORM_FACTOR, out_f32->data, audio_block_samples); + // prepare to transmit by setting the update_counter (which helps tell if data is skipped or out-of-order) + out_f32->id = update_counter; + // transmit the f32 data! + AudioStream_F32::transmit(out_f32, chan); + // release the memory blocks + AudioStream_F32::release(out_f32); +} + +void AudioInputI2S_ext_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 AudioInputI2Sslave_ext_F32::begin(void) +{ + dma.begin(true); // Allocate the DMA channel first + AudioOutputI2Sslave_ext_F32::config_i2s(); +} diff --git a/src/input_i2s_ext_F32.h b/src/input_i2s_ext_F32.h new file mode 100644 index 0000000..5f3983d --- /dev/null +++ b/src/input_i2s_ext_F32.h @@ -0,0 +1,87 @@ +/* + * ***** input_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 +// Removed unused pieces. RSL 30 May 2022 + +#ifndef _input_i2s_ext_f32_h_ +#define _input_i2s_ext_f32_h_ + +#include +#include +#include "AudioStream_F32.h" +#include "DMAChannel.h" + +class AudioInputI2S_ext_F32 : public AudioStream_F32 +{ + // GUI: inputs:0, outputs:2 //this line used for automatic generation of GUI nodes +public: + AudioInputI2S_ext_F32(void) : AudioStream_F32(0, NULL) { begin(); } // uses default AUDIO_SAMPLE_RATE and BLOCK_SIZE_SAMPLES from AudioStream.h + AudioInputI2S_ext_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); + void begin(void); + int get_isOutOfMemory(void) { return flag_out_of_memory; } + void clear_isOutOfMemory(void) { flag_out_of_memory = 0; } + +protected: + AudioInputI2S_ext_F32(int dummy) : AudioStream_F32(0, NULL) {} // to be used only inside AudioInputI2Sslave !! + static bool update_responsibility; + static DMAChannel dma; + 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; + static bool msbFirstMode; // some codecs like the new AKM series (AK4558) use MSB exclusively +}; + +class AudioInputI2Sslave_ext_F32 : public AudioInputI2S_ext_F32 +{ +public: + AudioInputI2Sslave_ext_F32(void) : AudioInputI2S_ext_F32(0) { begin(); } + void begin(void); + friend void dma_ch1_isr(void); +}; +#endif diff --git a/src/output_i2s2_F32.cpp b/src/output_i2s2_F32.cpp index 380c4ff..dd3ce88 100644 --- a/src/output_i2s2_F32.cpp +++ b/src/output_i2s2_F32.cpp @@ -250,9 +250,7 @@ void AudioOutputI2S2_F32::update(void) block_f32 = receiveReadOnly_f32(1); // input 1 = right channel if (block_f32) { - arm_scale_f32(block_f32->data, (float32_t)F32_TO_I32_NORM_FACTOR, - block_f32_scaled->data,audio_block_samples); - + scale_float_to_int32range(block_f32->data, block_f32_scaled->data, audio_block_samples); __disable_irq(); if (block_right_1st == NULL) { diff --git a/src/output_i2s_ext_F32.cpp b/src/output_i2s_ext_F32.cpp new file mode 100644 index 0000000..d6cd1ce --- /dev/null +++ b/src/output_i2s_ext_F32.cpp @@ -0,0 +1,432 @@ +/* + * ***** 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 +// Removed old commented out code. RSL 30 May 2022 + +#include "output_i2s_ext_F32.h" +#include "basic_DSPutils.h" +#include +#include //to get access to Audio/utlity/imxrt_hw.h...do we really need this??? WEA 2020-10-31 + +audio_block_f32_t *AudioOutputI2S_ext_F32::block_left_1st = NULL; +audio_block_f32_t *AudioOutputI2S_ext_F32::block_right_1st = NULL; +audio_block_f32_t *AudioOutputI2S_ext_F32::block_left_2nd = NULL; +audio_block_f32_t *AudioOutputI2S_ext_F32::block_right_2nd = NULL; +uint16_t AudioOutputI2S_ext_F32::block_left_offset = 0; +uint16_t AudioOutputI2S_ext_F32::block_right_offset = 0; +bool AudioOutputI2S_ext_F32::update_responsibility = false; +DMAChannel AudioOutputI2S_ext_F32::dma(false); +DMAMEM __attribute__((aligned(32))) static uint64_t i2s_tx_buffer[AUDIO_BLOCK_SAMPLES]; + +float AudioOutputI2S_ext_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; +int AudioOutputI2S_ext_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; + +#if defined(__IMXRT1062__) +#include //from Teensy Audio library. For set_audioClock() +#endif + +#define I2S_BUFFER_TO_USE_BYTES (AudioOutputI2S_ext_F32::audio_block_samples * sizeof(i2s_tx_buffer[0])) + + +void AudioOutputI2S_ext_F32::begin() +{ + + dma.begin(true); // Allocate the DMA channel first + + block_left_1st = NULL; + block_right_1st = NULL; + + AudioOutputI2S_ext_F32::config_i2s(sample_rate_Hz); + +#if defined(__IMXRT1062__) + CORE_PIN7_CONFIG = 3; // 1:TX_DATA0 + dma.TCD->SADDR = i2s_tx_buffer; + dma.TCD->SOFF = 4; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLNO = 4; + dma.TCD->SLAST = -I2S_BUFFER_TO_USE_BYTES; + dma.TCD->DOFF = 0; + dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4; + dma.TCD->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 0); + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); + dma.enable(); // newer location of this line in Teensy Audio library + I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; + I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; +#endif + update_responsibility = update_setup(); + dma.attachInterrupt(AudioOutputI2S_ext_F32::isr); + enabled = 1; +} + +void AudioOutputI2S_ext_F32::isr(void) +{ +#if defined(__IMXRT1062__) + int32_t *dest; + audio_block_f32_t *blockL, *blockR; + uint32_t saddr, offsetL, offsetR; + + saddr = (uint32_t)(dma.TCD->SADDR); + dma.clearInterrupt(); + if (saddr < (uint32_t)i2s_tx_buffer + I2S_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 = (int32_t *)&i2s_tx_buffer[audio_block_samples / 2]; + if (AudioOutputI2S_ext_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 = (int32_t *)i2s_tx_buffer; + } + + blockL = AudioOutputI2S_ext_F32::block_left_1st; + blockR = AudioOutputI2S_ext_F32::block_right_1st; + offsetL = AudioOutputI2S_ext_F32::block_left_offset; + offsetR = AudioOutputI2S_ext_F32::block_right_offset; + + int32_t *d = dest; + if (blockL && blockR) + { + float32_t *pL = blockL->data + offsetL; + float32_t *pR = blockR->data + offsetR; + for (int i = 0; i < audio_block_samples / 2; i++) + { + *d++ = (int32_t)*pL++; + *d++ = (int32_t)*pR++; // interleave + } + offsetL += audio_block_samples / 2; + offsetR += audio_block_samples / 2; + } + else if (blockL) + { + float32_t *pL = blockL->data + offsetL; + for (int i = 0; i < audio_block_samples / 2 * 2; i += 2) + { + *(d + i) = (int32_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) = (int32_t)*pR++; + } // interleave + offsetR += audio_block_samples / 2; + } + else + { + memset(dest, 0, audio_block_samples * 4); + return; + } + + arm_dcache_flush_delete(dest, sizeof(i2s_tx_buffer) / 2); + + if (offsetL < (uint16_t)audio_block_samples) + { + AudioOutputI2S_ext_F32::block_left_offset = offsetL; + } + else + { + AudioOutputI2S_ext_F32::block_left_offset = 0; + AudioStream_F32::release(blockL); + AudioOutputI2S_ext_F32::block_left_1st = AudioOutputI2S_ext_F32::block_left_2nd; + AudioOutputI2S_ext_F32::block_left_2nd = NULL; + } + if (offsetR < (uint16_t)audio_block_samples) + { + AudioOutputI2S_ext_F32::block_right_offset = offsetR; + } + else + { + AudioOutputI2S_ext_F32::block_right_offset = 0; + AudioStream_F32::release(blockR); + AudioOutputI2S_ext_F32::block_right_1st = AudioOutputI2S_ext_F32::block_right_2nd; + AudioOutputI2S_ext_F32::block_right_2nd = NULL; + } +#endif +} + +// 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 AudioOutputI2S_ext_F32::update(void) +{ + 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("AudioOutputI2S_ext_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); + } + // Optional scaling for easy volume control. Leave outputScale==1.0f for default + if (outputScale < 1.0f || outputScale > 1.0f) + arm_scale_f32(block_f32->data, outputScale, block_f32->data, block_f32->length); + + scale_float_to_int32range(block_f32->data, block_f32_scaled->data, audio_block_samples); + // 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) + { + // Optional scaling for easy volume control. Leave outputScale==1.0f for default + if (outputScale < 1.0f || outputScale > 1.0f) + arm_scale_f32(block_f32->data, outputScale, block_f32->data, block_f32->length); + // scale F32 to Int32 + scale_float_to_int32range(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 AudioOutputI2S_ext_F32::config_i2s(void) { config_i2s(AudioOutputI2S_ext_F32::sample_rate_Hz); } +void AudioOutputI2S_ext_F32::config_i2s(float fs_Hz) +{ +#if defined(__IMXRT1062__) + CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON); + + // if either transmitter or receiver is enabled, do nothing + if (I2S1_TCSR & I2S_TCSR_TE) + return; + if (I2S1_RCSR & I2S_RCSR_RE) + return; + // PLL: + 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 SAI1_CLK register locations + CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI1_CLK_SEL_MASK)) | CCM_CSCMR1_SAI1_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4 + CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK)) | CCM_CS1CDR_SAI1_CLK_PRED(n1 - 1) // &0x07 + | CCM_CS1CDR_SAI1_CLK_PODF(n2 - 1); // &0x3f + + // Select MCLK + IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 & ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK)) | (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0)); + + CORE_PIN23_CONFIG = 3; // 1:MCLK + CORE_PIN21_CONFIG = 3; // 1:RX_BCLK + CORE_PIN20_CONFIG = 3; // 1:RX_SYNC + + int rsync = 0; + int tsync = 1; + + I2S1_TMR = 0; + + I2S1_TCR1 = I2S_TCR1_RFW(1); + I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP // sync=0; tx is async; + | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1)); + I2S1_TCR3 = I2S_TCR3_TCE; + + I2S1_TCR4 = I2S_TCR4_FRSZ((2 - 1)) | I2S_TCR4_SYWD((32 - 1)) | I2S_TCR4_MF | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP; + I2S1_TCR5 = I2S_TCR5_WNW((32 - 1)) | I2S_TCR5_W0W((32 - 1)) | I2S_TCR5_FBT((32 - 1)); + + I2S1_RMR = 0; + + I2S1_RCR1 = I2S_RCR1_RFW(1); + I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP // sync=0; rx is async; + | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1)); + I2S1_RCR3 = I2S_RCR3_RCE; + I2S1_RCR4 = I2S_RCR4_FRSZ((2 - 1)) | I2S_RCR4_SYWD((32 - 1)) | I2S_RCR4_MF | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; + I2S1_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 AudioOutputI2Sslave_ext_F32::begin(void) +{ + dma.begin(true); // Allocate the DMA channel first + + // pinMode(2, OUTPUT); + block_left_1st = NULL; + block_right_1st = NULL; + + AudioOutputI2Sslave_ext_F32::config_i2s(); +#if defined(__IMXRT1062__) + CORE_PIN7_CONFIG = 3; // 1:TX_DATA0 + dma.TCD->SADDR = i2s_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); + // dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR1 + 2); + dma.TCD->DOFF = 0; + dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + // dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI2_TX); + dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); + dma.enable(); + + I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; + I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; + +#endif + + update_responsibility = update_setup(); + // dma.enable(); + dma.attachInterrupt(AudioOutputI2S_ext_F32::isr); +} + +void AudioOutputI2Sslave_ext_F32::config_i2s(void) +{ +#if defined(__IMXRT1062__) + + CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON); + + // if either transmitter or receiver is enabled, do nothing + if (I2S1_TCSR & I2S_TCSR_TE) + return; + if (I2S1_RCSR & I2S_RCSR_RE) + return; + + // not using MCLK in slave mode - hope that's ok? + // CORE_PIN23_CONFIG = 3; // AD_B1_09 ALT3=SAI1_MCLK + CORE_PIN21_CONFIG = 3; // AD_B1_11 ALT3=SAI1_RX_BCLK + CORE_PIN20_CONFIG = 3; // AD_B1_10 ALT3=SAI1_RX_SYNC + IOMUXC_SAI1_RX_BCLK_SELECT_INPUT = 1; // 1=GPIO_AD_B1_11_ALT3, page 868 + IOMUXC_SAI1_RX_SYNC_SELECT_INPUT = 1; // 1=GPIO_AD_B1_10_ALT3, page 872 + + // configure transmitter + I2S1_TMR = 0; + I2S1_TCR1 = I2S_TCR1_RFW(1); // watermark at half fifo size + I2S1_TCR2 = I2S_TCR2_SYNC(1) | I2S_TCR2_BCP; + I2S1_TCR3 = I2S_TCR3_TCE; + I2S1_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF | I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_RCR4_FSD; + I2S1_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); + + // configure receiver + I2S1_RMR = 0; + I2S1_RCR1 = I2S_RCR1_RFW(1); + I2S1_RCR2 = I2S_RCR2_SYNC(0) | I2S_TCR2_BCP; + I2S1_RCR3 = I2S_RCR3_RCE; + I2S1_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF | I2S_RCR4_FSE | I2S_RCR4_FSP; + I2S1_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); + +#endif +} diff --git a/src/output_i2s_ext_F32.h b/src/output_i2s_ext_F32.h new file mode 100644 index 0000000..2118b71 --- /dev/null +++ b/src/output_i2s_ext_F32.h @@ -0,0 +1,99 @@ +/* + * ***** 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 +// Removed old commented out code. RSL 30 May 2022 + +#ifndef output_i2s_ext_f32_h_ +#define output_i2s_ext_f32_h_ + +#include +#include +#include "AudioStream_F32.h" +#include "DMAChannel.h" + +class AudioOutputI2S_ext_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: + AudioOutputI2S_ext_F32(void) : AudioStream_F32(2, inputQueueArray) { begin();} + // Allow variable sample rate and block size: + AudioOutputI2S_ext_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray) + { + sample_rate_Hz = settings.sample_rate_Hz; + audio_block_samples = settings.audio_block_samples; + begin(); + } + // outputScale is a gain control for both left and right. If set exactly + // to 1.0f it is left as a pass-through. + void setGain(float _oscale) {outputScale = _oscale; } + virtual void update(void); + void begin(void); + friend class AudioInputI2S_ext_F32; + #if defined(__IMXRT1062__) + friend class AudioOutputI2SQuad_F32; + friend class AudioInputI2SQuad_F32; + #endif +protected: + AudioOutputI2S_ext_F32(int dummy): AudioStream_F32(2, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !! + static void config_i2s(void); + static void config_i2s(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(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; + float outputScale = 1.0f; // Quick volume control +}; + +class AudioOutputI2Sslave_ext_F32 : public AudioOutputI2S_ext_F32 +{ +public: + AudioOutputI2Sslave_ext_F32(void) : AudioOutputI2S_ext_F32(0) { begin(); } ; + void begin(void); + friend class AudioInputI2Sslave_ext_F32; + friend void dma_ch0_isr(void); +protected: + static void config_i2s(void); +}; +#endif