@@ -0,0 +1,232 @@ | |||
/* 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; | |||
} | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
/* 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 |
@@ -0,0 +1,387 @@ | |||
/* 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; | |||
} |
@@ -0,0 +1,223 @@ | |||
/* 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 |
@@ -0,0 +1,467 @@ | |||
/* 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 | |||
@@ -0,0 +1,97 @@ | |||
/* 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 |
@@ -0,0 +1,204 @@ | |||
/* 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 |
@@ -0,0 +1,46 @@ | |||
#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); | |||
} |