/* Audio Library for Teensy 3.X | |||||
* Copyright (c) 2019, 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. | |||||
*/ | |||||
/* | |||||
by Alexander Walch | |||||
*/ | |||||
#include "Quantizer.h" | |||||
#define SAMPLEINVALID(sample) (!isfinite(sample) || abs(sample) >= 1.2) //use only for floating point samples (\in [-1.,1.]) | |||||
Quantizer::Quantizer(float audio_sample_rate){ | |||||
#ifdef DEBUG_QUANTIZER | |||||
while(!Serial); | |||||
#endif | |||||
randomSeed(1); | |||||
if(audio_sample_rate==44100.f){ | |||||
_noiseSFilter[0]=-0.06935825f; | |||||
_noiseSFilter[1]=0.52540845f; | |||||
_noiseSFilter[2]= -1.20537028f; | |||||
_noiseSFilter[3]= 2.09422811f; | |||||
_noiseSFilter[4]= -3.2177438f; | |||||
_noiseSFilter[5]= 4.04852027f; | |||||
_noiseSFilter[6]= -3.83872701f; | |||||
_noiseSFilter[7]= 3.30584589f; | |||||
_noiseSFilter[8]= -2.38682527f; | |||||
// all coefficients in correct order: | |||||
// {1. , -2.38682527, 3.30584589, -3.83872701, 4.04852027, | |||||
// -3.2177438 , 2.09422811, -1.20537028, 0.52540845, -0.06935825}; | |||||
} else if(audio_sample_rate==48000.f){ | |||||
_noiseSFilter[0]=0.1967454f; | |||||
_noiseSFilter[1]=-0.30086406f; | |||||
_noiseSFilter[2]= 0.09575588f; | |||||
_noiseSFilter[3]= 0.58209648f; | |||||
_noiseSFilter[4]= -1.88579617f; | |||||
_noiseSFilter[5]= 3.37325788f; | |||||
_noiseSFilter[6]= -3.88076402f; | |||||
_noiseSFilter[7]= 3.58558504f; | |||||
_noiseSFilter[8]= -2.54334066f; | |||||
// all coefficients in correct order: | |||||
// {1. , -2.54334066, 3.58558504, -3.88076402, 3.37325788, | |||||
// -1.88579617, 0.58209648, 0.09575588, -0.30086406, 0.1967454}; | |||||
// } | |||||
} | |||||
else { | |||||
_noiseSFilter[0]=0.f; | |||||
_noiseSFilter[1]=0.f; | |||||
_noiseSFilter[2]=0.f; | |||||
_noiseSFilter[3]=0.f; | |||||
_noiseSFilter[4]=0.f; | |||||
_noiseSFilter[5]=0.f; | |||||
_noiseSFilter[6]=0.f; | |||||
_noiseSFilter[7]=0.f; | |||||
_noiseSFilter[8]=0.f; | |||||
} | |||||
_bufferEnd0=&_buffer0[NOISE_SHAPE_F_LENGTH]; | |||||
reset(); | |||||
} | |||||
void Quantizer::configure(bool noiseShaping, bool dither, float factor){ | |||||
_noiseShaping=noiseShaping; | |||||
_dither=dither; | |||||
_factor=factor; | |||||
reset(); | |||||
} | |||||
void Quantizer::reset(){ | |||||
_bPtr0=_buffer0; | |||||
_bPtr1=_buffer1; | |||||
_fOutputLastIt0=0.; | |||||
_fOutputLastIt1=0.; | |||||
memset(_buffer0, 0, NOISE_SHAPE_F_LENGTH*sizeof(float)); | |||||
memset(_buffer1, 0, NOISE_SHAPE_F_LENGTH*sizeof(float)); | |||||
} | |||||
void Quantizer::quantize(float* input, int16_t* output, uint16_t length){ | |||||
float xn, xnD, error; | |||||
const float f2 = 1.f/1000000.f; | |||||
#ifdef DEBUG_QUANTIZER | |||||
float debugFF=1024.f; | |||||
const float factor=(powf(2.f, 15.f)-1.f)/debugFF; | |||||
#endif | |||||
for (uint16_t i =0; i< length; i++){ | |||||
xn= SAMPLEINVALID(*input) ? 0. : *input*_factor; //-_fOutputLastIt0 according to paper | |||||
++input; | |||||
if (_noiseShaping){ | |||||
xn+=_fOutputLastIt0; | |||||
} | |||||
if(_dither){ | |||||
const uint32_t r0=random(1000000); | |||||
const uint32_t r1=random(1000000); | |||||
xnD=xn + (r0 + r1)*f2-1.f; | |||||
} | |||||
else { | |||||
xnD=xn; | |||||
} | |||||
float xnDR=round(xnD); | |||||
if (_noiseShaping){ | |||||
//compute quatization error: | |||||
error=xnDR- xn; | |||||
*_bPtr0++=error; | |||||
if (_bPtr0==_bufferEnd0){ | |||||
_bPtr0=_buffer0; | |||||
} | |||||
float* f=_noiseSFilter; | |||||
_fOutputLastIt0=(*_bPtr0++ * *f++); | |||||
if (_bPtr0==_bufferEnd0){ | |||||
_bPtr0=_buffer0; | |||||
} | |||||
for (uint16_t j =1; j< NOISE_SHAPE_F_LENGTH; j++){ | |||||
_fOutputLastIt0+=(*_bPtr0++ * *f++); | |||||
if (_bPtr0==_bufferEnd0){ | |||||
_bPtr0=_buffer0; | |||||
} | |||||
} | |||||
} | |||||
#ifdef DEBUG_QUANTIZER | |||||
xnDR*=debugFF; | |||||
#endif | |||||
if (xnDR > _factor){ | |||||
*output=(int16_t)_factor; | |||||
} | |||||
else if (xnDR < -_factor){ | |||||
*output=(int16_t)_factor; | |||||
} | |||||
else { | |||||
*output=(int16_t)xnDR; | |||||
} | |||||
++output; | |||||
} | |||||
} | |||||
void Quantizer::quantize(float* input0, float* input1, int32_t* outputInterleaved, uint16_t length){ | |||||
float xn0, xnD0, error0,xnDR0, xn1, xnD1, error1,xnDR1; | |||||
const float f2 = 1.f/1000000.f; | |||||
#ifdef DEBUG_QUANTIZER | |||||
float debugFF=1024.f; | |||||
const float factor=(powf(2.f, 15.f)-1.f)/debugFF; | |||||
#endif | |||||
for (uint16_t i =0; i< length; i++){ | |||||
xn0= SAMPLEINVALID(*input0) ? 0. : *input0*_factor; //-_fOutputLastIt0 according to paper | |||||
++input0; | |||||
xn1= SAMPLEINVALID(*input1) ? 0. : *input1*_factor; //-_fOutputLastIt0 according to paper | |||||
++input1; | |||||
if (_noiseShaping){ | |||||
xn0+=_fOutputLastIt0; | |||||
xn1+=_fOutputLastIt1; | |||||
} | |||||
if(_dither){ | |||||
uint32_t r0=random(1000000); | |||||
uint32_t r1=random(1000000); | |||||
xnD0=xn0 + (r0 + r1)*f2-1.f; | |||||
r0=random(1000000); | |||||
r1=random(1000000); | |||||
xnD1=xn1 + (r0 + r1)*f2-1.f; | |||||
} | |||||
else { | |||||
xnD0=xn0; | |||||
xnD1=xn1; | |||||
} | |||||
xnDR0=round(xnD0); | |||||
xnDR1=round(xnD1); | |||||
if (_noiseShaping){ | |||||
//compute quatization error0: | |||||
error0=xnDR0- xn0; | |||||
error1=xnDR1- xn1; | |||||
*_bPtr0++=error0; | |||||
*_bPtr1++=error1; | |||||
if (_bPtr0==_bufferEnd0){ | |||||
_bPtr0=_buffer0; | |||||
_bPtr1=_buffer1; | |||||
} | |||||
float* f=_noiseSFilter; | |||||
_fOutputLastIt0=(*_bPtr0++ * *f); | |||||
_fOutputLastIt1=(*_bPtr1++ * *f++); | |||||
if (_bPtr0==_bufferEnd0){ | |||||
_bPtr0=_buffer0; | |||||
_bPtr1=_buffer1; | |||||
} | |||||
for (uint16_t j =1 ; j< NOISE_SHAPE_F_LENGTH; j++){ | |||||
_fOutputLastIt0+=(*_bPtr0++ * *f); | |||||
_fOutputLastIt1+=(*_bPtr1++ * *f++); | |||||
if (_bPtr0==_bufferEnd0){ | |||||
_bPtr0=_buffer0; | |||||
_bPtr1=_buffer1; | |||||
} | |||||
} | |||||
} | |||||
#ifdef DEBUG_QUANTIZER | |||||
xnDR0*=debugFF; | |||||
#endif | |||||
if (xnDR0 > _factor){ | |||||
*outputInterleaved++=(int32_t)_factor; | |||||
} | |||||
else if (xnDR0 < -_factor){ | |||||
*outputInterleaved++=-(int32_t)_factor; | |||||
} | |||||
else { | |||||
*outputInterleaved++=(int32_t)xnDR0; | |||||
} | |||||
if (xnDR1 > _factor){ | |||||
*outputInterleaved++=(int32_t)_factor; | |||||
} | |||||
else if (xnDR1 < -_factor){ | |||||
*outputInterleaved++=-(int32_t)_factor; | |||||
} | |||||
else { | |||||
*outputInterleaved++=(int32_t)xnDR1; | |||||
} | |||||
} | |||||
} |
/* Audio Library for Teensy 3.X | |||||
* Copyright (c) 2019, 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. | |||||
*/ | |||||
/* | |||||
by Alexander Walch | |||||
*/ | |||||
#ifndef quantizer_h_ | |||||
#define quantizer_h_ | |||||
#include "Arduino.h" | |||||
//#define DEBUG_QUANTIZER | |||||
#define NOISE_SHAPE_F_LENGTH 9 //order of filter is 10, but the first coefficient equals 1 and doesn't need to be stored | |||||
class Quantizer { | |||||
public: | |||||
///@param audio_sample_rate currently only 44.1kHz and 48kHz are supported | |||||
Quantizer(float audio_sample_rate); | |||||
void configure(bool noiseShaping, bool dither, float factor); | |||||
void quantize(float* input, int16_t* output, uint16_t length); | |||||
//attention outputInterleaved must have length 2*length | |||||
void quantize(float* input0, float* input1, int32_t* outputInterleaved, uint16_t length); | |||||
void reset(); | |||||
private: | |||||
bool _noiseShaping=true; | |||||
bool _dither=true; | |||||
float _fOutputLastIt0=0.f; | |||||
float _fOutputLastIt1=0.f; | |||||
float _buffer0[NOISE_SHAPE_F_LENGTH]; | |||||
float _buffer1[NOISE_SHAPE_F_LENGTH]; | |||||
float* _bPtr0=_buffer0; | |||||
float* _bufferEnd0; | |||||
float* _bPtr1=_buffer1; | |||||
float _noiseSFilter[NOISE_SHAPE_F_LENGTH ]; | |||||
float _factor; | |||||
}; | |||||
#endif |
/* Audio Library for Teensy 3.X | |||||
* Copyright (c) 2019, 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. | |||||
*/ | |||||
/* | |||||
by Alexander Walch | |||||
*/ | |||||
#include "Resampler.h" | |||||
#include <math.h> | |||||
Resampler::Resampler(StepAdaptionParameters settings){ | |||||
#ifdef DEBUG_RESAMPLER | |||||
while (!Serial); | |||||
#endif | |||||
_settings=settings; | |||||
kaiserWindowSamples[0]=1.; | |||||
double step=1./(NO_EXACT_KAISER_SAMPLES-1); | |||||
double* xSq=kaiserWindowXsq; | |||||
for (uint16_t i = 1; i <NO_EXACT_KAISER_SAMPLES; i++){ | |||||
double x=(double)i*step; | |||||
*xSq++=(1.-x*x); | |||||
} | |||||
} | |||||
void Resampler::getKaiserExact(float beta){ | |||||
const double thres=1e-10; | |||||
double* winS=&kaiserWindowSamples[1]; | |||||
double* t=tempRes; | |||||
for (uint16_t i = 1; i <NO_EXACT_KAISER_SAMPLES; i++){ | |||||
*winS++=1.; | |||||
*t++=1.; | |||||
} | |||||
double denomLastSummand=1.; | |||||
const double halfBetaSq=beta*beta/4.; | |||||
double denom=1.; | |||||
double i=1.; | |||||
while(i < 1000){ | |||||
denomLastSummand*=(halfBetaSq/(i*i)); | |||||
i+=1.; | |||||
denom+=denomLastSummand; | |||||
t=tempRes; | |||||
winS=&kaiserWindowSamples[1]; | |||||
double* xSq=kaiserWindowXsq; | |||||
for (uint16_t j=0; j< NO_EXACT_KAISER_SAMPLES-1;j++){ | |||||
(*t)*=(*xSq); | |||||
double summand=(denomLastSummand*(*t)); | |||||
(*winS)+=summand; | |||||
if (summand< thres){ | |||||
break; | |||||
} | |||||
++winS; | |||||
++t; | |||||
++xSq; | |||||
} | |||||
if (denomLastSummand< thres){ | |||||
break; | |||||
} | |||||
} | |||||
winS=&kaiserWindowSamples[1]; | |||||
for (int32_t i = 0; i <NO_EXACT_KAISER_SAMPLES-1; i++){ | |||||
*winS++/=denom; | |||||
} | |||||
} | |||||
void Resampler::setKaiserWindow(float beta, int32_t noSamples){ | |||||
getKaiserExact(beta); | |||||
double step=(float)(NO_EXACT_KAISER_SAMPLES-1.)/(noSamples-1.); | |||||
double xPos=step; | |||||
float* filterCoeff=filter; | |||||
*filterCoeff=1.; | |||||
++filterCoeff; | |||||
int32_t lower=(int)(xPos); | |||||
double* windowLower=&kaiserWindowSamples[lower]; | |||||
double* windowUpper=&kaiserWindowSamples[lower+1]; | |||||
for (int32_t i =0; i< noSamples-2; i++){ | |||||
double lambda=xPos-lower; | |||||
if (lambda > 1.){ | |||||
lambda-=1.; | |||||
++windowLower; | |||||
++windowUpper; | |||||
lower++; | |||||
} | |||||
*filterCoeff++=(float)(lambda*(*windowUpper)+(1.-lambda)*(*windowLower)); | |||||
xPos+=step; | |||||
if (xPos>=NO_EXACT_KAISER_SAMPLES-1 || lower >=NO_EXACT_KAISER_SAMPLES-1){ | |||||
break; | |||||
} | |||||
} | |||||
*filterCoeff=*windowUpper; | |||||
} | |||||
void Resampler::setFilter(int32_t halfFiltLength,int32_t overSampling, float cutOffFrequ, float kaiserBeta){ | |||||
const int32_t noSamples=halfFiltLength*overSampling+1; | |||||
setKaiserWindow(kaiserBeta, noSamples); | |||||
float* filterCoeff=filter; | |||||
*filterCoeff++=cutOffFrequ; | |||||
double step=halfFiltLength/(noSamples-1.); | |||||
double xPos=step; | |||||
double factor=M_PI*cutOffFrequ; | |||||
for (int32_t i = 0; i<noSamples-1; i++ ){ | |||||
*filterCoeff++*=(float)((sin(xPos*factor)/(xPos*M_PI))); | |||||
xPos+=step; | |||||
} | |||||
} | |||||
double Resampler::getStep() const { | |||||
return _stepAdapted; | |||||
} | |||||
void Resampler::reset(){ | |||||
_initialized=false; | |||||
} | |||||
void Resampler::configure(float fs, float newFs, float attenuation, int32_t minHalfFilterLength){ | |||||
// Serial.print("configure, fs: "); | |||||
// Serial.println(fs); | |||||
if (fs<=0. || newFs <=0.){ | |||||
_initialized=false; | |||||
return; | |||||
} | |||||
_step=(double)fs/newFs; | |||||
_configuredStep=_step; | |||||
_stepAdapted=_step; | |||||
_sum=0.; | |||||
_oldDiffs[0]=0.; | |||||
_oldDiffs[1]=0.; | |||||
for (uint8_t i =0; i< MAX_NO_CHANNELS; i++){ | |||||
memset(_buffer[i], 0, sizeof(float)*MAX_HALF_FILTER_LENGTH*2); | |||||
} | |||||
float cutOffFrequ, kaiserBeta; | |||||
_overSamplingFactor=1024; | |||||
if (fs <= newFs){ | |||||
cutOffFrequ=1.; | |||||
kaiserBeta=10; | |||||
_halfFilterLength=minHalfFilterLength; | |||||
} | |||||
else{ | |||||
cutOffFrequ=newFs/fs; | |||||
double b=2.*(0.5*newFs-20000)/fs; //this transition band width causes aliasing. However the generated frequencies are above 20kHz | |||||
#ifdef DEBUG_RESAMPLER | |||||
Serial.print("b: "); | |||||
Serial.println(b); | |||||
#endif | |||||
double hfl=(int32_t)((attenuation-8)/(2.*2.285*TWO_PI*b)+0.5); | |||||
if (hfl >= minHalfFilterLength && hfl <= MAX_HALF_FILTER_LENGTH){ | |||||
_halfFilterLength=hfl; | |||||
#ifdef DEBUG_RESAMPLER | |||||
Serial.print("Attenuation: "); | |||||
#endif | |||||
} | |||||
else if (hfl < minHalfFilterLength){ | |||||
_halfFilterLength=minHalfFilterLength; | |||||
attenuation=((2*_halfFilterLength+1)-1)*(2.285*TWO_PI*b)+8; | |||||
#ifdef DEBUG_RESAMPLER | |||||
Serial.println("Resmapler: sinc filter length increased"); | |||||
Serial.print("Attenuation increased to "); | |||||
#endif | |||||
} | |||||
else{ | |||||
_halfFilterLength=MAX_HALF_FILTER_LENGTH; | |||||
attenuation=((2*_halfFilterLength+1)-1)*(2.285*TWO_PI*b)+8; | |||||
#ifdef DEBUG_RESAMPLER | |||||
Serial.println("Resmapler: needed sinc filter length too long"); | |||||
Serial.print("Attenuation decreased to "); | |||||
#endif | |||||
} | |||||
#ifdef DEBUG_RESAMPLER | |||||
Serial.print(attenuation); | |||||
Serial.println("dB"); | |||||
#endif | |||||
if (attenuation>50.){ | |||||
kaiserBeta=0.1102*(attenuation-8.7); | |||||
} | |||||
else if (21<=attenuation && attenuation<=50){ | |||||
kaiserBeta=0.5842*(float)pow(attenuation-21.,0.4)+0.07886*(attenuation-21.); | |||||
} | |||||
else{ | |||||
kaiserBeta=0.; | |||||
} | |||||
int32_t noSamples=_halfFilterLength*_overSamplingFactor+1; | |||||
if (noSamples > MAX_FILTER_SAMPLES){ | |||||
int32_t f = (noSamples-1)/(MAX_FILTER_SAMPLES-1)+1; | |||||
_overSamplingFactor/=f; | |||||
} | |||||
} | |||||
#ifdef DEBUG_RESAMPLER | |||||
Serial.print("fs: "); | |||||
Serial.println(fs); | |||||
Serial.print("cutOffFrequ: "); | |||||
Serial.println(cutOffFrequ); | |||||
Serial.print("filter length: "); | |||||
Serial.println(2*_halfFilterLength+1); | |||||
Serial.print("overSampling: "); | |||||
Serial.println(_overSamplingFactor); | |||||
Serial.print("kaiserBeta: "); | |||||
Serial.println(kaiserBeta, 12); | |||||
Serial.print("_step: "); | |||||
Serial.println(_step, 12); | |||||
#endif | |||||
setFilter(_halfFilterLength, _overSamplingFactor, cutOffFrequ, kaiserBeta); | |||||
_filterLength=_halfFilterLength*2; | |||||
for (uint8_t i =0; i< MAX_NO_CHANNELS; i++){ | |||||
_endOfBuffer[i]=&_buffer[i][_filterLength]; | |||||
} | |||||
_cPos=-_halfFilterLength; //marks the current center position of the filter | |||||
_initialized=true; | |||||
} | |||||
bool Resampler::initialized() const { | |||||
return _initialized; | |||||
} | |||||
void Resampler::resample(float* input0, float* input1, uint16_t inputLength, uint16_t& processedLength, float* output0, float* output1,uint16_t outputLength, uint16_t& outputCount) { | |||||
outputCount=0; | |||||
int32_t successorIndex=(int32_t)(ceil(_cPos)); //negative number -> currently the _buffer0 of the last iteration is used | |||||
float* ip0, *ip1, *fPtr; | |||||
float filterC; | |||||
float si0[2]; | |||||
float si1[2]; | |||||
while (floor(_cPos + _halfFilterLength) < inputLength && outputCount < outputLength){ | |||||
float dist=successorIndex-_cPos; | |||||
const float distScaled=dist*_overSamplingFactor; | |||||
int32_t rightIndex=abs((int32_t)(ceil(distScaled))-_overSamplingFactor*_halfFilterLength); | |||||
const int32_t indexData=successorIndex-_halfFilterLength; | |||||
if (indexData>=0){ | |||||
ip0=input0+indexData; | |||||
ip1=input1+indexData; | |||||
} | |||||
else { | |||||
ip0=_buffer[0]+indexData+_filterLength; | |||||
ip1=_buffer[1]+indexData+_filterLength; | |||||
} | |||||
fPtr=filter+rightIndex; | |||||
if (rightIndex==_overSamplingFactor*_halfFilterLength){ | |||||
si1[0]=*ip0++**fPtr; | |||||
si1[1]=*ip1++**fPtr; | |||||
memset(si0, 0, 2*sizeof(float)); | |||||
fPtr-=_overSamplingFactor; | |||||
rightIndex=(int32_t)(ceil(distScaled))+_overSamplingFactor; //needed below | |||||
} | |||||
else { | |||||
memset(si0, 0, 2*sizeof(float)); | |||||
memset(si1, 0, 2*sizeof(float)); | |||||
rightIndex=(int32_t)(ceil(distScaled)); //needed below | |||||
} | |||||
for (uint16_t i =0 ; i<_halfFilterLength; i++){ | |||||
if(ip0==_endOfBuffer[0]){ | |||||
ip0=input0; | |||||
ip1=input1; | |||||
} | |||||
si1[0]+=*ip0**fPtr; | |||||
si1[1]+=*ip1**fPtr; | |||||
filterC=*(fPtr+1); | |||||
si0[0]+=*ip0*filterC; | |||||
si0[1]+=*ip1*filterC; | |||||
fPtr-=_overSamplingFactor; | |||||
++ip0; | |||||
++ip1; | |||||
} | |||||
fPtr=filter+rightIndex-1; | |||||
for (uint16_t i =0 ; i<_halfFilterLength; i++){ | |||||
if(ip0==_endOfBuffer[0]){ | |||||
ip0=input0; | |||||
ip1=input1; | |||||
} | |||||
si0[0]+=*ip0**fPtr; | |||||
si0[1]+=*ip1**fPtr; | |||||
filterC=*(fPtr+1); | |||||
si1[0]+=*ip0*filterC; | |||||
si1[1]+=*ip1*filterC; | |||||
fPtr+=_overSamplingFactor; | |||||
++ip0; | |||||
++ip1; | |||||
} | |||||
const float w0=ceil(distScaled)-distScaled; | |||||
const float w1=1.-w0; | |||||
*output0++=si0[0]*w0 + si1[0]*w1; | |||||
*output1++=si0[1]*w0 + si1[1]*w1; | |||||
outputCount++; | |||||
_cPos+=_stepAdapted; | |||||
while (_cPos >successorIndex){ | |||||
successorIndex++; | |||||
} | |||||
} | |||||
if(outputCount < outputLength){ | |||||
//ouput vector not full -> we ran out of input samples | |||||
processedLength=inputLength; | |||||
} | |||||
else{ | |||||
processedLength=min(inputLength, (int16_t)floor(_cPos + _halfFilterLength)); | |||||
} | |||||
//fill _buffer | |||||
const int32_t indexData=processedLength-_filterLength; | |||||
if (indexData>=0){ | |||||
ip0=input0+indexData; | |||||
ip1=input1+indexData; | |||||
const unsigned long long bytesToCopy= _filterLength*sizeof(float); | |||||
memcpy((void *)_buffer[0], (void *)ip0, bytesToCopy); | |||||
memcpy((void *)_buffer[1], (void *)ip1, bytesToCopy); | |||||
} | |||||
else { | |||||
float* b0=_buffer[0]; | |||||
float* b1=_buffer[1]; | |||||
ip0=_buffer[0]+indexData+_filterLength; | |||||
ip1=_buffer[1]+indexData+_filterLength; | |||||
for (uint16_t i =0; i< _filterLength; i++){ | |||||
if(ip0==_endOfBuffer[0]){ | |||||
ip0=input0; | |||||
ip1=input1; | |||||
} | |||||
*b0++ = *ip0++; | |||||
*b1++ = *ip1++; | |||||
} | |||||
} | |||||
_cPos-=processedLength; | |||||
if (_cPos < -_halfFilterLength){ | |||||
_cPos=-_halfFilterLength; | |||||
} | |||||
} | |||||
void Resampler::fixStep(){ | |||||
if (!_initialized){ | |||||
return; | |||||
} | |||||
_step=_stepAdapted; | |||||
_sum=0.; | |||||
_oldDiffs[0]=0.; | |||||
_oldDiffs[1]=0.; | |||||
} | |||||
void Resampler::addToPos(double val){ | |||||
if(val < 0){ | |||||
return; | |||||
} | |||||
_cPos+=val; | |||||
} | |||||
bool Resampler::addToSampleDiff(double diff){ | |||||
_oldDiffs[0]=_oldDiffs[1]; | |||||
_oldDiffs[1]=(1.-_settings.alpha)*_oldDiffs[1]+_settings.alpha*diff; | |||||
const double slope=_oldDiffs[1]-_oldDiffs[0]; | |||||
_sum+=diff; | |||||
double correction=_settings.kp*diff+_settings.kd*slope+_settings.ki*_sum; | |||||
const double oldStepAdapted=_stepAdapted; | |||||
_stepAdapted=_step+correction; | |||||
if (abs(_stepAdapted/_configuredStep-1.) > _settings.maxAdaption){ | |||||
_initialized=false; | |||||
return false; | |||||
} | |||||
bool settled=false; | |||||
if ((abs(oldStepAdapted- _stepAdapted)/_stepAdapted < _settledThrs*abs(diff) && abs(diff) > 1.5*1e-6)) { | |||||
settled=true; | |||||
} | |||||
return settled; | |||||
} | |||||
double Resampler::getXPos() const{ | |||||
return _cPos+(double)_halfFilterLength; | |||||
} |
/* Audio Library for Teensy 3.X | |||||
* Copyright (c) 2019, 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. | |||||
*/ | |||||
/* | |||||
by Alexander Walch | |||||
*/ | |||||
#ifndef resampler_h_ | |||||
#define resampler_h_ | |||||
#include "Arduino.h" | |||||
//#define DEBUG_RESAMPLER //activates debug output | |||||
#define MAX_FILTER_SAMPLES 40961 //=1024*20 +1 | |||||
#define NO_EXACT_KAISER_SAMPLES 1025 | |||||
#define MAX_HALF_FILTER_LENGTH 80 | |||||
#define MAX_NO_CHANNELS 8 | |||||
class Resampler { | |||||
public: | |||||
struct StepAdaptionParameters { | |||||
StepAdaptionParameters(){} | |||||
double alpha =0.2; //exponential smoothing parameter | |||||
double maxAdaption = 0.01; //maximum relative allowed adaption of resampler step 0.01 = 1% | |||||
double kp= 0.6; | |||||
double ki=0.00012; | |||||
double kd= 1.8; | |||||
}; | |||||
Resampler(StepAdaptionParameters settings=StepAdaptionParameters()); | |||||
void reset(); | |||||
///@param attenuation target attenuation [dB] of the anti-aliasing filter. Only used if newFs<fs. The attenuation can't be reached if the needed filter length exceeds 2*MAX_FILTER_SAMPLES+1 | |||||
///@param minHalfFilterLength If newFs >= fs, the filter length of the resampling filter is 2*minHalfFilterLength+1. If fs y newFs the filter is maybe longer to reach the desired attenuation | |||||
void configure(float fs, float newFs, float attenuation=100, int32_t minHalfFilterLength=20); | |||||
///@param input0 first input array/ channel | |||||
///@param input1 second input array/ channel | |||||
///@param inputLength length of each input array | |||||
///@param processedLength number of samples of the input that were resampled to fill the output array | |||||
///@param output0 first output array/ channel | |||||
///@param output1 second output array/ channel | |||||
///@param outputLength length of each output array | |||||
///@param outputCount number of samples of each output array, that were filled with data | |||||
void resample(float* input0, float* input1, uint16_t inputLength, uint16_t& processedLength, float* output0, float* output1,uint16_t outputLength, uint16_t& outputCount); | |||||
bool addToSampleDiff(double diff); | |||||
double getXPos() const; | |||||
double getStep() const; | |||||
void addToPos(double val); | |||||
void fixStep(); | |||||
bool initialized() const; | |||||
//resampling NOCHANNELS channels. Performance is increased a lot if the number of channels is known at compile time -> the number of channels is a template argument | |||||
template <uint8_t NOCHANNELS> | |||||
inline void resample(float** inputs, uint16_t inputLength, uint16_t& processedLength, float** outputs, uint16_t outputLength, uint16_t& outputCount){ | |||||
outputCount=0; | |||||
int32_t successorIndex=(int32_t)(ceil(_cPos)); //negative number -> currently the _buffer0 of the last iteration is used | |||||
float* ip[NOCHANNELS]; | |||||
float* fPtr; | |||||
float si0[NOCHANNELS]; | |||||
float* si0Ptr; | |||||
float si1[NOCHANNELS]; | |||||
float* si1Ptr; | |||||
while (floor(_cPos + _halfFilterLength) < inputLength && outputCount < outputLength){ | |||||
float dist=successorIndex-_cPos; | |||||
float distScaled=dist*_overSamplingFactor; | |||||
int32_t rightIndex=abs((int32_t)(ceil(distScaled))-_overSamplingFactor*_halfFilterLength); | |||||
const int32_t indexData=successorIndex-_halfFilterLength; | |||||
if (indexData>=0){ | |||||
for (uint8_t i =0; i< NOCHANNELS; i++){ | |||||
ip[i]=inputs[i]+indexData; | |||||
} | |||||
} | |||||
else { | |||||
for (uint8_t i =0; i< NOCHANNELS; i++){ | |||||
ip[i]=_buffer[i]+indexData+_filterLength; | |||||
} | |||||
} | |||||
fPtr=filter+rightIndex; | |||||
memset(si0, 0, NOCHANNELS*sizeof(float)); | |||||
if (rightIndex==_overSamplingFactor*_halfFilterLength){ | |||||
si1Ptr=si1; | |||||
for (uint8_t i=0; i< NOCHANNELS; i++){ | |||||
*(si1Ptr++)=*ip[i]++**fPtr; | |||||
} | |||||
fPtr-=_overSamplingFactor; | |||||
rightIndex=(int32_t)(ceil(distScaled))+_overSamplingFactor; //needed below | |||||
} | |||||
else { | |||||
memset(si1, 0, NOCHANNELS*sizeof(float)); | |||||
rightIndex=(int32_t)(ceil(distScaled)); //needed below | |||||
} | |||||
for (uint16_t i =0 ; i<_halfFilterLength; i++){ | |||||
if(ip[0]==_endOfBuffer[0]){ | |||||
for (uint8_t i =0; i< NOCHANNELS; i++){ | |||||
ip[i]=inputs[i]; | |||||
} | |||||
} | |||||
const float fPtrSucc=*(fPtr+1); | |||||
si0Ptr=si0; | |||||
si1Ptr=si1; | |||||
for (uint8_t i =0; i< NOCHANNELS; i++){ | |||||
*(si0Ptr++)+=*ip[i]*fPtrSucc; | |||||
*(si1Ptr++)+=*ip[i]**fPtr; | |||||
++ip[i]; | |||||
} | |||||
fPtr-=_overSamplingFactor; | |||||
} | |||||
fPtr=filter+rightIndex-1; | |||||
for (uint16_t i =0 ; i<_halfFilterLength; i++){ | |||||
if(ip[0]==_endOfBuffer[0]){ | |||||
for (uint8_t i =0; i< NOCHANNELS; i++){ | |||||
ip[i]=inputs[i]; | |||||
} | |||||
} | |||||
const float fPtrSucc=*(fPtr+1); | |||||
si0Ptr=si0; | |||||
si1Ptr=si1; | |||||
for (uint8_t i =0; i< NOCHANNELS; i++){ | |||||
*(si0Ptr++)+=*ip[i]**fPtr; | |||||
*(si1Ptr++)+=*ip[i]*fPtrSucc; | |||||
++ip[i]; | |||||
} | |||||
fPtr+=_overSamplingFactor; | |||||
} | |||||
const float w0=ceil(distScaled)-distScaled; | |||||
const float w1=1.-w0; | |||||
si0Ptr=si0; | |||||
si1Ptr=si1; | |||||
for (uint8_t i =0; i< NOCHANNELS; i++){ | |||||
*outputs[i]++=*(si0Ptr++)*w0 + *(si1Ptr++)*w1; | |||||
} | |||||
outputCount++; | |||||
_cPos+=_stepAdapted; | |||||
while (_cPos >successorIndex){ | |||||
successorIndex++; | |||||
} | |||||
} | |||||
if(outputCount < outputLength){ | |||||
//ouput vector not full -> we ran out of input samples | |||||
processedLength=inputLength; | |||||
} | |||||
else{ | |||||
processedLength=min(inputLength, (int16_t)floor(_cPos + _halfFilterLength)); | |||||
} | |||||
//fill _buffer | |||||
const int32_t indexData=processedLength-_filterLength; | |||||
if (indexData>=0){ | |||||
const unsigned long long bytesToCopy= _filterLength*sizeof(float); | |||||
float** inPtr=inputs; | |||||
for (uint8_t i =0; i< NOCHANNELS; i++){ | |||||
memcpy((void *)_buffer[i], (void *)((*inPtr)+indexData), bytesToCopy); | |||||
++inPtr; | |||||
} | |||||
} | |||||
else { | |||||
float** inPtr=inputs; | |||||
for (uint8_t i =0; i< NOCHANNELS; i++){ | |||||
float* b=_buffer[i]; | |||||
float* ip=b+indexData+_filterLength; | |||||
for (uint16_t j =0; j< _filterLength; j++){ | |||||
if(ip==_endOfBuffer[i]){ | |||||
ip=*inPtr; | |||||
} | |||||
*b++ = *ip++; | |||||
} | |||||
++inPtr; | |||||
} | |||||
} | |||||
_cPos-=processedLength; | |||||
if (_cPos < -_halfFilterLength){ | |||||
_cPos=-_halfFilterLength; | |||||
} | |||||
} | |||||
private: | |||||
void getKaiserExact(float beta); | |||||
void setKaiserWindow(float beta, int32_t noSamples); | |||||
void setFilter(int32_t halfFiltLength,int32_t overSampling, float cutOffFrequ, float kaiserBeta); | |||||
float filter[MAX_FILTER_SAMPLES]; | |||||
double kaiserWindowSamples[NO_EXACT_KAISER_SAMPLES]; | |||||
double tempRes[NO_EXACT_KAISER_SAMPLES-1]; | |||||
double kaiserWindowXsq[NO_EXACT_KAISER_SAMPLES-1]; | |||||
float _buffer[MAX_NO_CHANNELS][MAX_HALF_FILTER_LENGTH*2]; | |||||
float* _endOfBuffer[MAX_NO_CHANNELS]; | |||||
int32_t _overSamplingFactor; | |||||
int32_t _halfFilterLength; | |||||
int32_t _filterLength; | |||||
bool _initialized=false; | |||||
const double _settledThrs = 1e-6; | |||||
StepAdaptionParameters _settings; | |||||
double _configuredStep; | |||||
double _step; | |||||
double _stepAdapted; | |||||
double _cPos; | |||||
double _sum; | |||||
double _oldDiffs[2]; | |||||
}; | |||||
#endif |
/* Audio Library for Teensy 3.X | |||||
* Copyright (c) 2019, 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. | |||||
*/ | |||||
/* | |||||
by Alexander Walch | |||||
*/ | |||||
#if defined(__IMXRT1052__) || defined(__IMXRT1062__) | |||||
#include "async_input_spdif3.h" | |||||
#include "biquad.h" | |||||
#include <utility/imxrt_hw.h> | |||||
//Parameters | |||||
namespace { | |||||
#define SPDIF_RX_BUFFER_LENGTH AUDIO_BLOCK_SAMPLES | |||||
const int32_t bufferLength=8*AUDIO_BLOCK_SAMPLES; | |||||
const uint16_t noSamplerPerIsr=SPDIF_RX_BUFFER_LENGTH/4; | |||||
} | |||||
volatile bool AsyncAudioInputSPDIF3::resetResampler=true; | |||||
#ifdef DEBUG_SPDIF_IN | |||||
volatile bool AsyncAudioInputSPDIF3::bufferOverflow=false; | |||||
#endif | |||||
volatile uint32_t AsyncAudioInputSPDIF3::microsLast; | |||||
DMAMEM __attribute__((aligned(32))) | |||||
static int32_t spdif_rx_buffer[SPDIF_RX_BUFFER_LENGTH]; | |||||
static float bufferR[bufferLength]; | |||||
static float bufferL[bufferLength]; | |||||
volatile int32_t AsyncAudioInputSPDIF3::buffer_offset = 0; // read by resample/ written in spdif input isr -> copied at the beginning of 'resmaple' protected by __disable_irq() in resample | |||||
int32_t AsyncAudioInputSPDIF3::resample_offset = 0; // read/written by resample/ read in spdif input isr -> no protection needed? | |||||
volatile bool AsyncAudioInputSPDIF3::lockChanged=false; | |||||
volatile bool AsyncAudioInputSPDIF3::locked=false; | |||||
DMAChannel AsyncAudioInputSPDIF3::dma(false); | |||||
AsyncAudioInputSPDIF3::~AsyncAudioInputSPDIF3(){ | |||||
delete [] _bufferLPFilter.pCoeffs; | |||||
delete [] _bufferLPFilter.pState; | |||||
delete quantizer[0]; | |||||
delete quantizer[1]; | |||||
} | |||||
PROGMEM | |||||
AsyncAudioInputSPDIF3::AsyncAudioInputSPDIF3(bool dither, bool noiseshaping,float attenuation, int32_t minHalfFilterLength) : AudioStream(0, NULL) { | |||||
_attenuation=attenuation; | |||||
_minHalfFilterLength=minHalfFilterLength; | |||||
const float factor = powf(2, 15)-1.f; // to 16 bit audio | |||||
quantizer[0]=new Quantizer(AUDIO_SAMPLE_RATE_EXACT); | |||||
quantizer[0]->configure(noiseshaping, dither, factor); | |||||
quantizer[1]=new Quantizer(AUDIO_SAMPLE_RATE_EXACT); | |||||
quantizer[1]->configure(noiseshaping, dither, factor); | |||||
begin(); | |||||
} | |||||
PROGMEM | |||||
void AsyncAudioInputSPDIF3::begin() | |||||
{ | |||||
dma.begin(true); // Allocate the DMA channel first | |||||
const uint32_t noByteMinorLoop=2*4; | |||||
dma.TCD->SOFF = 4; | |||||
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); | |||||
dma.TCD->NBYTES_MLNO = DMA_TCD_NBYTES_MLOFFYES_NBYTES(noByteMinorLoop) | DMA_TCD_NBYTES_SMLOE | | |||||
DMA_TCD_NBYTES_MLOFFYES_MLOFF(-8); | |||||
dma.TCD->SLAST = -8; | |||||
dma.TCD->DOFF = 4; | |||||
dma.TCD->CITER_ELINKNO = sizeof(spdif_rx_buffer) / noByteMinorLoop; | |||||
dma.TCD->DLASTSGA = -sizeof(spdif_rx_buffer); | |||||
dma.TCD->BITER_ELINKNO = sizeof(spdif_rx_buffer) / noByteMinorLoop; | |||||
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; | |||||
dma.TCD->SADDR = (void *)((uint32_t)&SPDIF_SRL); | |||||
dma.TCD->DADDR = spdif_rx_buffer; | |||||
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SPDIF_RX); | |||||
SPDIF_SCR |=SPDIF_SCR_DMA_RX_EN; //DMA Receive Request Enable | |||||
dma.enable(); | |||||
dma.attachInterrupt(isr); | |||||
config_spdifIn(); | |||||
#ifdef DEBUG_SPDIF_IN | |||||
while (!Serial); | |||||
#endif | |||||
_bufferLPFilter.pCoeffs=new float[5]; | |||||
_bufferLPFilter.numStages=1; | |||||
_bufferLPFilter.pState=new float[2]; | |||||
getCoefficients(_bufferLPFilter.pCoeffs, BiquadType::LOW_PASS, 0., 5., AUDIO_SAMPLE_RATE_EXACT/AUDIO_BLOCK_SAMPLES, 0.5); | |||||
} | |||||
bool AsyncAudioInputSPDIF3::isLocked() const { | |||||
__disable_irq(); | |||||
bool l=locked; | |||||
__enable_irq(); | |||||
return l; | |||||
} | |||||
void AsyncAudioInputSPDIF3::spdif_interrupt(){ | |||||
if(SPDIF_SIS & SPDIF_SIS_LOCK){ | |||||
if (!locked){ | |||||
locked=true; | |||||
lockChanged=true; | |||||
} | |||||
} | |||||
else if(SPDIF_SIS & SPDIF_SIS_LOCKLOSS){ | |||||
if (locked){ | |||||
locked=false; | |||||
lockChanged=true; | |||||
resetResampler=true; | |||||
} | |||||
} | |||||
SPDIF_SIC |= SPDIF_SIC_LOCKLOSS;//clear SPDIF_SIC_LOCKLOSS interrupt | |||||
SPDIF_SIC |= SPDIF_SIC_LOCK; //clear SPDIF_SIC_LOCK interrupt | |||||
} | |||||
void AsyncAudioInputSPDIF3::resample(int16_t* data_left, int16_t* data_right, int32_t& block_offset){ | |||||
block_offset=0; | |||||
if(!_resampler.initialized()){ | |||||
return; | |||||
} | |||||
__disable_irq(); | |||||
if(!locked){ | |||||
__enable_irq(); | |||||
return; | |||||
} | |||||
int32_t bOffset=buffer_offset; | |||||
int32_t resOffset=resample_offset; | |||||
__enable_irq(); | |||||
uint16_t inputBufferStop = bOffset >= resOffset ? bOffset-resOffset : bufferLength-resOffset; | |||||
if (inputBufferStop==0){ | |||||
return; | |||||
} | |||||
uint16_t processedLength; | |||||
uint16_t outputCount=0; | |||||
uint16_t outputLength=AUDIO_BLOCK_SAMPLES; | |||||
float resampledBufferL[AUDIO_BLOCK_SAMPLES]; | |||||
float resampledBufferR[AUDIO_BLOCK_SAMPLES]; | |||||
_resampler.resample(&bufferL[resOffset],&bufferR[resOffset], inputBufferStop, processedLength, resampledBufferL, resampledBufferR, outputLength, outputCount); | |||||
resOffset=(resOffset+processedLength)%bufferLength; | |||||
block_offset=outputCount; | |||||
if (bOffset > resOffset && block_offset< AUDIO_BLOCK_SAMPLES){ | |||||
inputBufferStop= bOffset-resOffset; | |||||
outputLength=AUDIO_BLOCK_SAMPLES-block_offset; | |||||
_resampler.resample(&bufferL[resOffset],&bufferR[resOffset], inputBufferStop, processedLength, resampledBufferL+block_offset, resampledBufferR+block_offset, outputLength, outputCount); | |||||
resOffset=(resOffset+processedLength)%bufferLength; | |||||
block_offset+=outputCount; | |||||
} | |||||
quantizer[0]->quantize(resampledBufferL, data_left, block_offset); | |||||
quantizer[1]->quantize(resampledBufferR, data_right, block_offset); | |||||
__disable_irq(); | |||||
resample_offset=resOffset; | |||||
__enable_irq(); | |||||
} | |||||
void AsyncAudioInputSPDIF3::isr(void) | |||||
{ | |||||
dma.clearInterrupt(); | |||||
microsLast=micros(); | |||||
const int32_t *src, *end; | |||||
uint32_t daddr = (uint32_t)(dma.TCD->DADDR); | |||||
if (daddr < (uint32_t)spdif_rx_buffer + sizeof(spdif_rx_buffer) / 2) { | |||||
// DMA is receiving to the first half of the buffer | |||||
// need to remove data from the second half | |||||
src = (int32_t *)&spdif_rx_buffer[SPDIF_RX_BUFFER_LENGTH/2]; | |||||
end = (int32_t *)&spdif_rx_buffer[SPDIF_RX_BUFFER_LENGTH]; | |||||
//if (AsyncAudioInputSPDIF3::update_responsibility) AudioStream::update_all(); | |||||
} else { | |||||
// DMA is receiving to the second half of the buffer | |||||
// need to remove data from the first half | |||||
src = (int32_t *)&spdif_rx_buffer[0]; | |||||
end = (int32_t *)&spdif_rx_buffer[SPDIF_RX_BUFFER_LENGTH/2]; | |||||
} | |||||
if (buffer_offset >=resample_offset || | |||||
(buffer_offset + SPDIF_RX_BUFFER_LENGTH/4) < resample_offset) { | |||||
#if IMXRT_CACHE_ENABLED >=1 | |||||
arm_dcache_delete((void*)src, sizeof(spdif_rx_buffer) / 2); | |||||
#endif | |||||
float *destR = &(bufferR[buffer_offset]); | |||||
float *destL = &(bufferL[buffer_offset]); | |||||
const float factor= pow(2., 23.)+1; | |||||
do { | |||||
int32_t n=(*src) & 0x800000 ? (*src)|0xFF800000 : (*src) & 0xFFFFFF; | |||||
*destL++ = (float)(n)/factor; | |||||
++src; | |||||
n=(*src) & 0x800000 ? (*src)|0xFF800000 : (*src) & 0xFFFFFF; | |||||
*destR++ = (float)(n)/factor; | |||||
++src; | |||||
} while (src < end); | |||||
buffer_offset=(buffer_offset+SPDIF_RX_BUFFER_LENGTH/4)%bufferLength; | |||||
} | |||||
#ifdef DEBUG_SPDIF_IN | |||||
else { | |||||
bufferOverflow=true; | |||||
} | |||||
#endif | |||||
} | |||||
double AsyncAudioInputSPDIF3::getNewValidInputFrequ(){ | |||||
//page 2129: FrequMeas[23:0]=FreqMeas_CLK / BUS_CLK * 2^10 * GAIN | |||||
if (SPDIF_SRPC & SPDIF_SRPC_LOCK){ | |||||
const double f=(float)F_BUS_ACTUAL/(1024.*1024.*24.*128.);// bit clock = 128 * sampling frequency | |||||
const double freqMeas=(SPDIF_SRFM & 0xFFFFFF)*f; | |||||
if (_lastValidInputFrequ != freqMeas){//frequency not stable yet; | |||||
_lastValidInputFrequ=freqMeas; | |||||
return -1.; | |||||
} | |||||
return _lastValidInputFrequ; | |||||
} | |||||
return -1.; | |||||
} | |||||
double AsyncAudioInputSPDIF3::getBufferedTime() const{ | |||||
__disable_irq(); | |||||
double n=_bufferedTime; | |||||
__enable_irq(); | |||||
return n; | |||||
} | |||||
void AsyncAudioInputSPDIF3::configure(){ | |||||
__disable_irq(); | |||||
if(resetResampler){ | |||||
_resampler.reset(); | |||||
resetResampler=false; | |||||
} | |||||
if(!locked){ | |||||
__enable_irq(); | |||||
#ifdef DEBUG_SPDIF_IN | |||||
Serial.println("lock lost"); | |||||
#endif | |||||
return; | |||||
} | |||||
#ifdef DEBUG_SPDIF_IN | |||||
const bool bOverf=bufferOverflow; | |||||
bufferOverflow=false; | |||||
#endif | |||||
const bool lc=lockChanged; | |||||
__enable_irq(); | |||||
#ifdef DEBUG_SPDIF_IN | |||||
if (bOverf){ | |||||
Serial.print("buffer overflow, buffer offset: "); | |||||
Serial.print(buffer_offset); | |||||
Serial.print(", resample_offset: "); | |||||
Serial.println(resample_offset); | |||||
if (!_resampler.initialized()){ | |||||
Serial.println("_resampler not initialized. "); | |||||
} | |||||
} | |||||
#endif | |||||
if (lc || !_resampler.initialized()){ | |||||
const double inputF=getNewValidInputFrequ(); //returns: -1 ... invalid frequency | |||||
if (inputF > 0.){ | |||||
__disable_irq(); | |||||
lockChanged=false; //only reset lockChanged if a valid frequency was received (inputFrequ > 0.) | |||||
__enable_irq(); | |||||
//we got a valid sample frequency | |||||
const double frequDiff=inputF/_inputFrequency-1.; | |||||
if (abs(frequDiff) > 0.01 || !_resampler.initialized()){ | |||||
//the new sample frequency differs from the last one -> configure the _resampler again | |||||
_inputFrequency=inputF; | |||||
const int32_t targetLatency=round(_targetLatencyS*inputF); | |||||
_targetLatencyS=max(0.001,(noSamplerPerIsr*3./2./_inputFrequency)); | |||||
__disable_irq(); | |||||
resample_offset = targetLatency <= buffer_offset ? buffer_offset - targetLatency : bufferLength -(targetLatency-buffer_offset); | |||||
__enable_irq(); | |||||
_resampler.configure(inputF, AUDIO_SAMPLE_RATE_EXACT, _attenuation, _minHalfFilterLength); | |||||
#ifdef DEBUG_SPDIF_IN | |||||
Serial.print("_maxLatency: "); | |||||
Serial.println(_maxLatency); | |||||
Serial.print("targetLatency: "); | |||||
Serial.println(targetLatency); | |||||
Serial.print("relative frequ diff: "); | |||||
Serial.println(frequDiff, 8); | |||||
Serial.print("configure _resampler with frequency "); | |||||
Serial.println(inputF,8); | |||||
#endif | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void AsyncAudioInputSPDIF3::monitorResampleBuffer(){ | |||||
if(!_resampler.initialized()){ | |||||
return; | |||||
} | |||||
__disable_irq(); | |||||
const double dmaOffset=(micros()-microsLast)*1e-6; //[seconds] | |||||
double bTime = resample_offset <= buffer_offset ? (buffer_offset-resample_offset-_resampler.getXPos())/_lastValidInputFrequ+dmaOffset : (bufferLength-resample_offset +buffer_offset-_resampler.getXPos())/_lastValidInputFrequ+dmaOffset; //[seconds] | |||||
double diff = bTime- (_blockDuration+ _targetLatencyS); //seconds | |||||
biquad_cascade_df2T<double, arm_biquad_cascade_df2T_instance_f32, float>(&_bufferLPFilter, &diff, &diff, 1); | |||||
bool settled=_resampler.addToSampleDiff(diff); | |||||
if (bTime > _maxLatency || bTime-dmaOffset<= _blockDuration || settled) { | |||||
double distance=(_blockDuration+_targetLatencyS-dmaOffset)*_lastValidInputFrequ+_resampler.getXPos(); | |||||
diff=0.; | |||||
if (distance > bufferLength-noSamplerPerIsr){ | |||||
diff=bufferLength-noSamplerPerIsr-distance; | |||||
distance=bufferLength-noSamplerPerIsr; | |||||
} | |||||
if (distance < 0.){ | |||||
distance=0.; | |||||
diff=- (_blockDuration+ _targetLatencyS); | |||||
} | |||||
double resample_offsetF=buffer_offset-distance; | |||||
resample_offset=(int32_t)floor(resample_offsetF); | |||||
_resampler.addToPos(resample_offsetF-resample_offset); | |||||
while (resample_offset<0){ | |||||
resample_offset+=bufferLength; | |||||
} | |||||
//int32_t b_offset=buffer_offset; | |||||
#ifdef DEBUG_SPDIF_IN | |||||
double bTimeFixed = resample_offset <= buffer_offset ? (buffer_offset-resample_offset-_resampler.getXPos())/_lastValidInputFrequ+dmaOffset : (bufferLength-resample_offset +buffer_offset-_resampler.getXPos())/_lastValidInputFrequ+dmaOffset; //[seconds] | |||||
#endif | |||||
__enable_irq(); | |||||
#ifdef DEBUG_SPDIF_IN | |||||
// Serial.print("settled: "); | |||||
// Serial.println(settled); | |||||
Serial.print("bTime: "); | |||||
Serial.print(bTime*1e6,3); | |||||
Serial.print("_maxLatency: "); | |||||
Serial.println(_maxLatency*1e6,3); | |||||
Serial.print("bTime-dmaOffset: "); | |||||
Serial.print((bTime-dmaOffset)*1e6,3); | |||||
Serial.print(", _blockDuration: "); | |||||
Serial.print(_blockDuration*1e6,3); | |||||
Serial.print("bTimeFixed: "); | |||||
Serial.print(bTimeFixed*1e6,3); | |||||
#endif | |||||
preload(&_bufferLPFilter, diff); | |||||
_resampler.fixStep(); | |||||
} | |||||
else { | |||||
__enable_irq(); | |||||
} | |||||
_bufferedTime=_targetLatencyS+diff; | |||||
} | |||||
void AsyncAudioInputSPDIF3::update(void) | |||||
{ | |||||
configure(); | |||||
monitorResampleBuffer(); //important first call 'monitorResampleBuffer' then 'resample' | |||||
audio_block_t *block_left =allocate(); | |||||
audio_block_t *block_right =nullptr; | |||||
if (block_left!= nullptr) { | |||||
block_right = allocate(); | |||||
if (block_right == nullptr) { | |||||
release(block_left); | |||||
block_left = nullptr; | |||||
} | |||||
} | |||||
if (block_left && block_right) { | |||||
int32_t block_offset; | |||||
resample(block_left->data, block_right->data,block_offset); | |||||
if(block_offset < AUDIO_BLOCK_SAMPLES){ | |||||
memset(block_left->data+block_offset, 0, (AUDIO_BLOCK_SAMPLES-block_offset)*sizeof(int16_t)); | |||||
memset(block_right->data+block_offset, 0, (AUDIO_BLOCK_SAMPLES-block_offset)*sizeof(int16_t)); | |||||
#ifdef DEBUG_SPDIF_IN | |||||
Serial.print("filled only "); | |||||
Serial.print(block_offset); | |||||
Serial.println(" samples."); | |||||
#endif | |||||
} | |||||
transmit(block_left, 0); | |||||
release(block_left); | |||||
block_left=nullptr; | |||||
transmit(block_right, 1); | |||||
release(block_right); | |||||
block_right=nullptr; | |||||
} | |||||
#ifdef DEBUG_SPDIF_IN | |||||
else { | |||||
Serial.println("Not enough blocks available. Too few audio memory?"); | |||||
} | |||||
#endif | |||||
} | |||||
double AsyncAudioInputSPDIF3::getInputFrequency() const{ | |||||
__disable_irq(); | |||||
double f=_lastValidInputFrequ; | |||||
__enable_irq(); | |||||
return f; | |||||
} | |||||
double AsyncAudioInputSPDIF3::getTargetLantency() const { | |||||
__disable_irq(); | |||||
double l=_targetLatencyS; | |||||
__enable_irq(); | |||||
return l ; | |||||
} | |||||
void AsyncAudioInputSPDIF3::config_spdifIn(){ | |||||
//CCM Clock Gating Register 5, imxrt1060_rev1.pdf page 1145 | |||||
CCM_CCGR5 |=CCM_CCGR5_SPDIF(CCM_CCGR_ON); //turn spdif clock on - necessary for receiver! | |||||
SPDIF_SCR |=SPDIF_SCR_RXFIFO_OFF_ON; //turn receive fifo off 1->off, 0->on | |||||
SPDIF_SCR&=~(SPDIF_SCR_RXFIFO_CTR); //reset rx fifo control: normal opertation | |||||
SPDIF_SCR&=~(SPDIF_SCR_RXFIFOFULL_SEL(3)); //reset rx full select | |||||
SPDIF_SCR|=SPDIF_SCR_RXFIFOFULL_SEL(2); //full interrupt if at least 8 sample in Rx left and right FIFOs | |||||
SPDIF_SCR|=SPDIF_SCR_RXAUTOSYNC; //Rx FIFO auto sync on | |||||
SPDIF_SCR&=(~SPDIF_SCR_USRC_SEL(3)); //No embedded U channel | |||||
CORE_PIN15_CONFIG = 3; //pin 15 set to alt3 -> spdif input | |||||
/// from eval board sample code | |||||
// IOMUXC_SetPinConfig( | |||||
// IOMUXC_GPIO_AD_B1_03_SPDIF_IN, /* GPIO_AD_B1_03 PAD functional properties : */ | |||||
// 0x10B0u); /* Slew Rate Field: Slow Slew Rate | |||||
// Drive Strength Field: R0/6 | |||||
// Speed Field: medium(100MHz) | |||||
// Open Drain Enable Field: Open Drain Disabled | |||||
// Pull / Keep Enable Field: Pull/Keeper Enabled | |||||
// Pull / Keep Select Field: Keeper | |||||
// Pull Up / Down Config. Field: 100K Ohm Pull Down | |||||
// Hyst. Enable Field: Hysteresis Disabled */ | |||||
CORE_PIN15_PADCONFIG=0x10B0; | |||||
SPDIF_SCR &=(~SPDIF_SCR_RXFIFO_OFF_ON); //receive fifo is turned on again | |||||
SPDIF_SRPC &= ~SPDIF_SRPC_CLKSRC_SEL(15); //reset clock selection page 2136 | |||||
//SPDIF_SRPC |=SPDIF_SRPC_CLKSRC_SEL(6); //if (DPLL Locked) SPDIF_RxClk else tx_clk (SPDIF0_CLK_ROOT) | |||||
//page 2129: FrequMeas[23:0]=FreqMeas_CLK / BUS_CLK * 2^10 * GAIN | |||||
SPDIF_SRPC &=~SPDIF_SRPC_GAINSEL(7); //reset gain select 0 -> gain = 24*2^10 | |||||
//SPDIF_SRPC |= SPDIF_SRPC_GAINSEL(3); //gain select: 8*2^10 | |||||
//============================================== | |||||
//interrupts | |||||
SPDIF_SIE |= SPDIF_SIE_LOCK; //enable spdif receiver lock interrupt | |||||
SPDIF_SIE |=SPDIF_SIE_LOCKLOSS; | |||||
lockChanged=true; | |||||
attachInterruptVector(IRQ_SPDIF, spdif_interrupt); | |||||
NVIC_SET_PRIORITY(IRQ_SPDIF, 208); // 255 = lowest priority, 208 = priority of update | |||||
NVIC_ENABLE_IRQ(IRQ_SPDIF); | |||||
SPDIF_SIC |= SPDIF_SIC_LOCK; //clear SPDIF_SIC_LOCK interrupt | |||||
SPDIF_SIC |= SPDIF_SIC_LOCKLOSS;//clear SPDIF_SIC_LOCKLOSS interrupt | |||||
locked=(SPDIF_SRPC & SPDIF_SRPC_LOCK); | |||||
} | |||||
#endif | |||||
/* Audio Library for Teensy 3.X | |||||
* Copyright (c) 2019, 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. | |||||
*/ | |||||
/* | |||||
by Alexander Walch | |||||
*/ | |||||
#ifndef async_input_spdif3_h_ | |||||
#define async_input_spdif3_h_ | |||||
#include "Resampler.h" | |||||
#include "Quantizer.h" | |||||
#include "Arduino.h" | |||||
#include "AudioStream.h" | |||||
#include "DMAChannel.h" | |||||
#include <arm_math.h> | |||||
//#define DEBUG_SPDIF_IN //activates debug output | |||||
class AsyncAudioInputSPDIF3 : public AudioStream | |||||
{ | |||||
public: | |||||
///@param attenuation target attenuation [dB] of the anti-aliasing filter. Only used if newFs<fs. The attenuation can't be reached if the needed filter length exceeds 2*MAX_FILTER_SAMPLES+1 | |||||
///@param minHalfFilterLength If newFs >= fs, the filter length of the resampling filter is 2*minHalfFilterLength+1. If fs y newFs the filter is maybe longer to reach the desired attenuation | |||||
AsyncAudioInputSPDIF3(bool dither, bool noiseshaping,float attenuation, int32_t minHalfFilterLength); | |||||
~AsyncAudioInputSPDIF3(); | |||||
virtual void update(void); | |||||
void begin(); | |||||
void stop(); | |||||
double getBufferedTime() const; | |||||
double getInputFrequency() const; | |||||
bool isLocked() const; | |||||
double getTargetLantency() const; | |||||
protected: | |||||
static DMAChannel dma; | |||||
static void isr(void); | |||||
private: | |||||
void resample(int16_t* data_left, int16_t* data_right, int32_t& block_offset); | |||||
void monitorResampleBuffer(); | |||||
void configure(); | |||||
double getNewValidInputFrequ(); | |||||
void config_spdifIn(); | |||||
//accessed in isr ==== | |||||
static volatile int32_t buffer_offset; | |||||
static int32_t resample_offset; | |||||
static volatile uint32_t microsLast; | |||||
//==================== | |||||
// spdif lock-changed interrupt | |||||
static volatile bool locked; | |||||
static volatile bool lockChanged; | |||||
static volatile bool resetResampler; | |||||
static void spdif_interrupt(); | |||||
#ifdef MEASURE_FREQ | |||||
static FrequencyMeasurement frequMeasure; | |||||
#endif | |||||
//============================= | |||||
float _attenuation; | |||||
int32_t _minHalfFilterLength; | |||||
Resampler _resampler; | |||||
Quantizer* quantizer[2]; | |||||
arm_biquad_cascade_df2T_instance_f32 _bufferLPFilter; | |||||
volatile double _bufferedTime; | |||||
volatile double _lastValidInputFrequ; | |||||
double _inputFrequency; | |||||
double _targetLatencyS; //target latency [seconds] | |||||
const double _blockDuration=AUDIO_BLOCK_SAMPLES/AUDIO_SAMPLE_RATE; //[seconds] | |||||
const double _maxLatency=2.*_blockDuration; | |||||
#ifdef DEBUG_SPDIF_IN | |||||
static volatile bool bufferOverflow; | |||||
#endif | |||||
}; | |||||
#endif |
/* Audio Library for Teensy 3.X | |||||
* Copyright (c) 2019, 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. | |||||
*/ | |||||
/* | |||||
by Alexander Walch | |||||
*/ | |||||
#ifndef biquad_coeffs_h_ | |||||
#define biquad_coeffs_h_ | |||||
#include "Arduino.h" | |||||
#include <arm_math.h> | |||||
enum class BiquadType { | |||||
LOW_PASS, HIGH_PASS, BAND_PASS, NOTCH, ALL_PASS, PEAKING, LOW_SHELF, HIGH_SHELF | |||||
}; | |||||
template <typename T> | |||||
void getCoefficients(T* coeffs, BiquadType type, double dbGain, double freq, double srate, double bandwidthOrQOrS, bool isBandwidthOrS=false){ | |||||
const double A =(type == BiquadType::PEAKING || type == BiquadType::LOW_SHELF || type == BiquadType::HIGH_SHELF) ? pow(10., dbGain / 40.) : pow(10, dbGain / 20); | |||||
const double omega = 2 * M_PI * freq / srate; | |||||
const double sn = sin(omega); | |||||
const double cs = cos(omega); | |||||
double alpha; | |||||
if (!isBandwidthOrS) // Q | |||||
alpha = sn / (2 * bandwidthOrQOrS); | |||||
else if (type == BiquadType::LOW_SHELF || type == BiquadType::HIGH_SHELF) // S | |||||
alpha = sn / 2 * sqrt((A + 1 / A) * (1 / bandwidthOrQOrS - 1) + 2); | |||||
else // BW | |||||
alpha = sn * sinh(_M_LN2 / 2 * bandwidthOrQOrS * omega / sn); | |||||
const double beta = 2 * sqrt(A) * alpha; | |||||
double b0, b1, b2, a0Inv, a1, a2; | |||||
switch (type) | |||||
{ | |||||
case BiquadType::LOW_PASS: | |||||
b0 = (1 - cs) / 2; | |||||
b1 = 1 - cs; | |||||
b2 = (1 - cs) / 2; | |||||
a0Inv = 1/(1 + alpha); | |||||
a1 = -2 * cs; | |||||
a2 = 1 - alpha; | |||||
break; | |||||
case BiquadType::HIGH_PASS: | |||||
b0 = (1 + cs) / 2; | |||||
b1 = -(1 + cs); | |||||
b2 = (1 + cs) / 2; | |||||
a0Inv = 1/(1 + alpha); | |||||
a1 = -2 * cs; | |||||
a2 = 1 - alpha; | |||||
break; | |||||
case BiquadType::BAND_PASS: | |||||
b0 = alpha; | |||||
b1 = 0; | |||||
b2 = -alpha; | |||||
a0Inv = 1/(1 + alpha); | |||||
a1 = -2 * cs; | |||||
a2 = 1 - alpha; | |||||
break; | |||||
case BiquadType::NOTCH: | |||||
b0 = 1; | |||||
b1 = -2 * cs; | |||||
b2 = 1; | |||||
a0Inv = 1/(1 + alpha); | |||||
a1 = -2 * cs; | |||||
a2 = 1 - alpha; | |||||
break; | |||||
case BiquadType::ALL_PASS: | |||||
b0 = 1 - alpha; | |||||
b1 = -2 * cs; | |||||
b2 = 1 + alpha; | |||||
a0Inv = 1/(1 + alpha); | |||||
a1 = -2 * cs; | |||||
a2 = 1 - alpha; | |||||
break; | |||||
case BiquadType::PEAKING: | |||||
b0 = 1 + (alpha * A); | |||||
b1 = -2 * cs; | |||||
b2 = 1 - (alpha * A); | |||||
a0Inv = 1/(1 + (alpha / A)); | |||||
a1 = -2 * cs; | |||||
a2 = 1 - (alpha / A); | |||||
break; | |||||
case BiquadType::LOW_SHELF: | |||||
b0 = A * ((A + 1) - (A - 1) * cs + beta); | |||||
b1 = 2 * A * ((A - 1) - (A + 1) * cs); | |||||
b2 = A * ((A + 1) - (A - 1) * cs - beta); | |||||
a0Inv = (A + 1) + (A - 1) * cs + beta; | |||||
a1 = -2 * ((A - 1) + (A + 1) * cs); | |||||
a2 = (A + 1) + (A - 1) * cs - beta; | |||||
break; | |||||
case BiquadType::HIGH_SHELF: | |||||
b0 = A * ((A + 1) + (A - 1) * cs + beta); | |||||
b1 = -2 * A * ((A - 1) + (A + 1) * cs); | |||||
b2 = A * ((A + 1) + (A - 1) * cs - beta); | |||||
a0Inv = 1/((A + 1) - (A - 1) * cs + beta); | |||||
a1 = 2 * ((A - 1) - (A + 1) * cs); | |||||
a2 = (A + 1) - (A - 1) * cs - beta; | |||||
break; | |||||
} | |||||
*coeffs++=(T)(b0 * a0Inv); | |||||
*coeffs++=(T)(b1 * a0Inv); | |||||
*coeffs++=(T)(b2 * a0Inv); | |||||
*coeffs++=(T)(-a1 * a0Inv); | |||||
*coeffs=(T)(-a2 * a0Inv); | |||||
} | |||||
template <typename T, typename BIQUAD, typename BTYPE> | |||||
void biquad_cascade_df2T(const BIQUAD* S, T* pSrc, T* pDst, uint32_t blockSize) { | |||||
BTYPE* b0 =S->pCoeffs; | |||||
BTYPE* b1=S->pCoeffs+1; | |||||
BTYPE* b2=S->pCoeffs+2; | |||||
BTYPE* a1Neg=S->pCoeffs+3; | |||||
BTYPE* a2Neg=S->pCoeffs+4; | |||||
BTYPE* state=S->pState; | |||||
if(S->numStages==1){ | |||||
BTYPE yn; | |||||
for (uint32_t j=0; j<blockSize; j++ ){ | |||||
yn = *b0 * *pSrc + *state; | |||||
*state = *b1 * *pSrc + *a1Neg * yn + *(state+1); | |||||
*(state+1) = *b2 * *pSrc++ + *a2Neg * yn; | |||||
*pDst++=(T)yn; | |||||
} | |||||
} | |||||
else { | |||||
BTYPE pDstD[blockSize]; | |||||
BTYPE* pDstDP=pDstD; | |||||
for (uint32_t j=0; j<blockSize; j++ ){ | |||||
*pDstDP = *b0 * *pSrc + *state; | |||||
*state = *b1 * *pSrc + *a1Neg * *pDstDP + *(state+1); | |||||
*(state+1) = *b2 * *pSrc++ + *a2Neg * *pDstDP++; | |||||
} | |||||
b0+=5; | |||||
b1+=5; | |||||
b2+=5; | |||||
a1Neg+=5; | |||||
a2Neg+=5; | |||||
state+=2; | |||||
for (uint8_t i =0; i< S->numStages - 2;i++){ | |||||
pDstDP=pDstD; | |||||
BTYPE xn; | |||||
for (uint32_t j=0; j<blockSize; j++ ){ | |||||
xn=*pDstDP; | |||||
*pDstDP = *b0 * xn + *state; | |||||
*state = *b1 * xn + *a1Neg * *pDstDP + *(state+1); | |||||
*(state+1) = *b2 * xn + *a2Neg * *pDstDP++; | |||||
} | |||||
b0+=5; | |||||
b1+=5; | |||||
b2+=5; | |||||
a1Neg+=5; | |||||
a2Neg+=5; | |||||
state+=2; | |||||
} | |||||
BTYPE yn; | |||||
pDstDP=pDstD; | |||||
for (uint32_t j=0; j<blockSize; j++ ){ | |||||
yn = *b0 * *pDstDP + *state; | |||||
*state = *b1 * *pDstDP + *a1Neg * yn + *(state+1); | |||||
*(state+1) = *b2 * *pDstDP++ + *a2Neg * yn; | |||||
*pDst++=(T)yn; | |||||
} | |||||
} | |||||
} | |||||
template <typename B> | |||||
void preload(const B* S, double val=0.){ | |||||
// double* b1=B->pCoeffs+1; | |||||
// double* b2=B->pCoeffs+2; | |||||
// double* a1Neg=B->pCoeffs+3; | |||||
// double* a2Neg=B->pCoeffs+4; | |||||
*(S->pState+1) = (*(S->pCoeffs+2) + *(S->pCoeffs+4)) * val; | |||||
*(S->pState) = (*(S->pCoeffs+1) + *(S->pCoeffs+3)) * val + *(S->pState+1); | |||||
} | |||||
#endif |
#include "output_spdif3.h" | |||||
#include "async_input_spdif3.h" | |||||
#include <Audio.h> | |||||
#include <SerialFlash.h> | |||||
AudioOutputSPDIF3 spdifOut; | |||||
AsyncAudioInputSPDIF3 spdifIn(true, true, 100, 20); //dither = true, noiseshaping = true, anti-aliasing attenuation=100dB, minimum resampling filter length=20 | |||||
// | |||||
AudioConnection patchCord1(spdifIn, 0, spdifOut, 0); | |||||
AudioConnection patchCord2(spdifIn, 1, spdifOut, 1); | |||||
void setup() { | |||||
// put your setup code here, to run once: | |||||
AudioMemory(12); | |||||
while (!Serial); | |||||
} | |||||
void loop() { | |||||
double bufferedTine=spdifIn.getBufferedTime(); | |||||
//double targetLatency = spdifIn.getTargetLantency(); | |||||
Serial.print("buffered time [micro seconds]: "); | |||||
Serial.println(bufferedTine*1e6,2); | |||||
// Serial.print(", target: "); | |||||
// Serial.println(targetLatency*1e6,2); | |||||
double pUsageIn=spdifIn.processorUsage(); | |||||
Serial.print("processor usage [%]: "); | |||||
Serial.println(pUsageIn); | |||||
// bool islocked=spdifIn.isLocked(); | |||||
// Serial.print("isLocked: "); | |||||
// Serial.println(islocked); | |||||
// double f=spdifIn.getInputFrequency(); | |||||
// Serial.print("frequency: "); | |||||
// Serial.println(f); | |||||
// Serial.print("Memory max: "); | |||||
// Serial.println(AudioMemoryUsageMax()); | |||||
delay(500); | |||||
} |