Forum Rule: Always post complete source code & details to reproduce any issue!
Page 1 of 2 1 2 LastLast
Results 1 to 25 of 42

Thread: New project to verify an asrc using polynomial approximations for its coefficients

  1. #1
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107

    New project to verify an asrc using polynomial approximations for its coefficients

    Hello. I want to start a new project. I had simulated a floating point software asynchronous sample rate converter in C & Matlab with some satisfying results for +-250 ppm adjustments.

    I am using a polyphase oversampling (x15) sinc filter with 89 delay line taps. I use a 3rd order polynomial to approximate the 89 coefficients values (function of the current fractional delay) requiring 4 convolutions per sample (but less for stereo). The advantage to this solution is that it only requires 45*15*4 (2700) coefficients for a THD+N of approximately -130dB.

    I would like to verify these performances using the Teensy 4.0 connected to an spdif input. I am unfortunately not yet familiar enough with use and creating of libraries. I would like to code in an ino sketch if possible.

    I have a few questions for the forum:

    1. Can I connect the spdif3 input object to a record queue clocked by the external clock furnished by it? (See my image below.) I would then add buffering & pass thru my asrc into a play queue synchronized to an i2s output?

    2. Can I remain in floating point code and coefficients and use the int2float() and float2int() objects available in the OpenAudio_ArduinoLibrary.h?

    3. Is there a way to determine the fractional delay between the spdif external clock and the Teensy word clock?

    Here's the setup: Click image for larger version. 

Name:	GUITool.jpg 
Views:	64 
Size:	30.6 KB 
ID:	26786


    Here's some results of my asrc using 24-bit I/O wav files:


    Click image for larger version. 

Name:	sineout.jpg 
Views:	67 
Size:	34.2 KB 
ID:	26787

  2. #2
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    and: Click image for larger version. 

Name:	THDN.jpg 
Views:	77 
Size:	46.3 KB 
ID:	26793

  3. #3
    Senior Member
    Join Date
    Aug 2019
    Posts
    100
    Hi,

    1. Can I connect the spdif3 input object to a record queue clocked by the external clock furnished by it? (See my image below.) I would then add buffering & pass thru my asrc into a play queue synchronized to an i2s output?
    Unfortunately that won't work. There is alway only 1 block that clocks the audio pipeline (triggers the call of the update function of all blocks). Depending on the order of the constructors in your program, it will either be the spdif3_1 or i2s1. In any case at the block that doesn't clock the pipline there will be buffer under- or overflows.

    Can I remain in floating point code and coefficients and use the int2float() and float2int() objects available in the OpenAudio_ArduinoLibrary.h?
    I don't know this methods. Do they have an advantage over implementing an own function, that just iterates over the samples and transforms them to float sample by sample?

    Is there a way to determine the fractional delay between the spdif external clock and the Teensy word clock?
    I don't think that, that is possible. But you can have a look at the reference manual:
    https://www.pjrc.com/teensy/IMXRT1060RM_rev2.pdf
    Chapter 40 describes the spdif interface and chapter 38 the SAI interface. Maybe you find functions that help you there. There is for example the possiblity to raise an interrupt at each start of an incoming word. (page 2002, field WSIE of SAI Receive Control Register (RCSR)). Here somebody posted an example that shows how to enable the interrupt:
    https://forum.pjrc.com/threads/67910...tart+interrupt

  4. #4
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    Quote Originally Posted by alex6679 View Post
    Hi,



    Unfortunately that won't work. There is alway only 1 block that clocks the audio pipeline (triggers the call of the update function of all blocks). Depending on the order of the constructors in your program, it will either be the spdif3_1 or i2s1. In any case at the block that doesn't clock the pipline there will be buffer under- or overflows.



    I don't know this methods. Do they have an advantage over implementing an own function, that just iterates over the samples and transforms them to float sample by sample?



    I don't think that, that is possible. But you can have a look at the reference manual:
    https://www.pjrc.com/teensy/IMXRT1060RM_rev2.pdf
    Chapter 40 describes the spdif interface and chapter 38 the SAI interface. Maybe you find functions that help you there. There is for example the possiblity to raise an interrupt at each start of an incoming word. (page 2002, field WSIE of SAI Receive Control Register (RCSR)). Here somebody posted an example that shows how to enable the interrupt:
    https://forum.pjrc.com/threads/67910...tart+interrupt
    Hello Alex

    Comments to:

    Your answer Question 1. What can the AudioInputSPDIF3 connect to unless it is in master mode (synchronized with the Teensy clock)? I don't really see the use of it.
    Your answer Question 2. I agree. I can easily do the transfer of samples from/to floating point in my own program.
    Your answer Question 3. I will try and understand these documents and the library code but, up to now, it's been very obscure to me.
    Last Comment: It seems to me that it would be very difficult to validate my simple asrc C code in an *.ino sketch.

    Thanx
    Bill

  5. #5
    Senior Member
    Join Date
    Aug 2019
    Posts
    100
    I guess it doesn't make sense to connect AudioInputSPDIF3 to anything if it is not the master. But if it is in master mode, you could for example record audio data.

    If you want to resample data from the spdif input, then I think the best way to test your asrc code is to write an own audio library class like the AsyncAudioInputSPDIF3.

    You could basically copy AudioInputSPDIF3. The input samples arrive via dma transfer, that calls the 'isr' method. Just fill a buffer here instead of audio blocks.
    The objec that is the master, like the i2s output you proposed, will trigger calls of the 'update' method. Here you could take the samples of the buffer and resample them into audio blocks.

  6. #6
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    Quote Originally Posted by alex6679 View Post
    I guess it doesn't make sense to connect AudioInputSPDIF3 to anything if it is not the master. But if it is in master mode, you could for example record audio data.

    If you want to resample data from the spdif input, then I think the best way to test your asrc code is to write an own audio library class like the AsyncAudioInputSPDIF3.

    You could basically copy AudioInputSPDIF3. The input samples arrive via dma transfer, that calls the 'isr' method. Just fill a buffer here instead of audio blocks.
    The objec that is the master, like the i2s output you proposed, will trigger calls of the 'update' method. Here you could take the samples of the buffer and resample them into audio blocks.
    Well, I integrated my srconv and associated fuctions in a Teensy *.ino sketch for now. Input samples from program memory using AudioPlayMemory object and sending srconv output buffers to AudioRecordQueue for the SD card . Obviously, I'm resampling with 0 frequency offset. Just wanted to verify the srconv still works in Teensy environment (it's OK, but too bad we have to use 16-bit I/O samples). I'm using 42% of the available 600Mhz disponible. All my resampling funtions are in C (not C++) and most variables are in double format. Lots of work to do and lots of documents to read. I'll probably put a pause on this issue or look for another way to validation this algorithm.

  7. #7
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    Continuing on a little bit. Why, in my code below, do I not improve my cycles consumed when switching from multiply 32x32 to 32x16? See #ifdef __ARM_ARCH_7EM__.


    Code:
     int32_t sinxcalc_fix(int32_t *pint,int ntaps,double Tdelay, int order,int phase, int numphase,int32_t *dlyline)
    {
    	int numcofoffset,j,l,phaseoffset,loopsiz1,loopsiz2,numcof;
    	int32_t totalout,out;
    	double Tdelayval,sgnval;
    	//---------- polynomial approximation of sinc filter coefs according to Tdelay and convolution with input signal delay line to generate 1 output ---
    	//useful for mono signals
    	numcof=(ntaps*numphase+1)/2;
    	if ((ntaps%2)==0)
    	{
    		loopsiz1=ntaps/2;
    		loopsiz2=ntaps/2;
    	}
    	else
    	{
    		if (phase>((numphase-1)/2))
    		{
    			loopsiz1=(ntaps-1)/2;
    			loopsiz2=(ntaps+1)/2;
    		}
    		else
    		{
    			loopsiz1=(ntaps+1)/2;
    			loopsiz2=(ntaps-1)/2;
    		}
    	}
    	Tdelayval=1.;
    	sgnval=1.;
    	totalout=0;
    	numcofoffset=0;
      //---------cycles--------------
        //  diverse=410
        //  update()=74
        //  order loop iteration=484 (instead of 1324 in double)
        //  total 410+74+4*484 =2418 (instead of 5780 in double)
        //  uses 18% of max cycles(600MHz) available
    	for (l=0;l<=order;l++)
    	{
    		out=0;
    		phaseoffset=phase;
    		for (j=0;j<loopsiz1;j++)
    		{
    #ifdef __ARM_ARCH_7EM__
    			out=signed_multiply_accumulate_32x16t(out,pint[phaseoffset+numcofoffset],dlyline[j]);
    #else
    
    			out=multiply_accumulate_32x32_rshift32_rounded(out,dlyline[j],pint[phaseoffset+numcofoffset]);
    #endif
    			phaseoffset=phaseoffset+numphase;
    		}
    		out=(int32_t)(out<<1);
    		out=(int32_t)((double)out*(double)numphase*Tdelayval);
    		totalout=totalout+out;
    		out=0;
    		phaseoffset=numphase-phase-1;
    		for (j=0;j<loopsiz2;j++)
    		{
    #ifdef __ARM_ARCH_7EM__
    			out=signed_multiply_accumulate_32x16t(out,pint[phaseoffset+numcofoffset],dlyline[j]);
    #else
    			out=multiply_accumulate_32x32_rshift32_rounded(out,dlyline[ntaps-j-1],pint[phaseoffset+numcofoffset]);
    #endif
    			phaseoffset=phaseoffset+numphase;
    		}
    		out=(int32_t)(out<<1);
    		out=(int32_t)((double)out*(double)numphase*Tdelayval*sgnval);
    		totalout=totalout+out;
    		numcofoffset+=numcof;
    		Tdelayval=Tdelayval*Tdelay;
    		sgnval=-sgnval;
    	}
    	return(totalout);
    }

  8. #8
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,856
    Quote Originally Posted by Bill Glass View Post
    Continuing on a little bit. Why, in my code below, do I not improve my cycles consumed when switching from multiply 32x32 to 32x16? See #ifdef __ARM_ARCH_7EM__.
    ...
    T_4.0 is a 32 bit processor AFAIK with a single cycle integer multiply instruction ...

  9. #9
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    OK. Guess those 32x16 multiplies in dspinst.h are there for lower Teensy versions. In this function, I'm executing a FIR with 89 taps. I'm getting 484/89 or 5.4 cycles/tap including overhead stuff. Looks like I've got some more pruning to do.

    Thanx

  10. #10
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    15,856
    Quote Originally Posted by Bill Glass View Post
    OK. Guess those 32x16 multiplies in dspinst.h are there for lower Teensy versions. In this function, I'm executing a FIR with 89 taps. I'm getting 484/89 or 5.4 cycles/tap including overhead stuff. Looks like I've got some more pruning to do.

    Thanx
    Paul posted some note on the DSP instructions long ago - they do multiplies on two 16 bit values 'packed' (in a single 32 bit word) or something that is super efficient

  11. #11
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    Quote Originally Posted by defragster View Post
    T_4.0 is a 32 bit processor AFAIK with a single cycle integer multiply instruction ...
    Makes me wonder. Does the instruction, multiply_accumulate_32x32_rshift32_rounded execute in 1 cycle?

  12. #12
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,756
    Yes, AFAIK all DSP instructions execute in one cycle.
    Edit: On _all_ 3.x or 4.x Teensy(s), not LC

  13. #13
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,756
    Here is a summary: https://developer.arm.com/documentat...ctions?lang=en
    Nearly all instruction take one cycle.
    But there is the pipeline, which can cause stalls.

    It would be nice if it had in addition the min/max/abs instruction of ESP and its "No overhead" loop instruction. That's still missing

  14. #14
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    Quote Originally Posted by Frank B View Post
    Here is a summary: https://developer.arm.com/documentat...ctions?lang=en
    Nearly all instruction take one cycle.
    But there is the pipeline, which can cause stalls.

    It would be nice if it had in addition the min/max/abs instruction of ESP and its "No overhead" loop instruction. That's still missing
    OK. Can't explain why I get 5.5 cycles/tap otherwise. Thanx

  15. #15
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,756
    Quote Originally Posted by Bill Glass View Post
    OK. Can't explain why I get 5.5 cycles/tap otherwise. Thanx
    Might be loop overhead.
    Maybe try to unroll 2 or 4 times.. the compiler can do that for you. There is a #pragma for that, but I don't remember exactly...

  16. #16
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    Quote Originally Posted by Frank B View Post
    Might be loop overhead.
    Maybe try to unroll 2 or 4 times.. the compiler can do that for you. There is a #pragma for that, but I don't remember exactly...
    Unrolled 4 times by hand -> better 5.5->4.1 cycles/tap. Thanks for the tip.
    For the PRAGMA, I don't know how to do that. My code is in the Arduino IDE sketch directory and I let the IDE automatically compile my code.
    Maybe that's my problem. I don't know how to force the optimization level and I don't know what compiler options are used by default by the IDE.

  17. #17
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,756
    Quote Originally Posted by Bill Glass View Post
    Unrolled 4 times by hand -> better 5.5->4.1 cycles/tap. Thanks for the tip.
    For the PRAGMA, I don't know how to do that. My code is in the Arduino IDE sketch directory and I let the IDE automatically compile my code.
    Maybe that's my problem. I don't know how to force the optimization level and I don't know what compiler options are used by default by the IDE.
    Ok, as I don't use GCC 5.4 most of the time, I forgot that it can not do that.
    Later versions (I think > GCC 8) have this pragma.

    You can see it in action here: https://godbolt.org/z/rz98KPW5v (play with the unroll value - it even works with odd values like 3)

    Godbolt is great for other experiments too, as it it very easy and fast to see the assembly. It's a great tool.

  18. #18
    Senior Member
    Join Date
    Apr 2014
    Location
    -
    Posts
    9,756
    You can also see that GCC is pretty smart. Some of the asm code in the Audio lib is not really needed. That compiler knows how to translate C code to asm... (If it is written correctly), also some of the "Volatile asm" there is counter productive., and there may be cases where it would work better without "volatile".

    (But for the DSP instructions which do 2 16Bit operations I don't know if they are doable in C)

    https://godbolt.org/z/8Go5jv5Yr

    or this:

    https://godbolt.org/z/zGKKcWT6o
    Last edited by Frank B; 12-23-2021 at 10:04 AM.

  19. #19
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    Well, thanks but all of that is a little bit over my head. I'm not really a software guy. I'm more into signal processing and C programming to make things work. I was hoping to use my C functions in the sketch environment and process audio streams via record & play queues. My question was whether letting the automatic compilation via the IDE of my .ino sketch optimizes cycles. 4.1 cycles per tap is still 4x greater than what it apparently should be.

    Thanks for all those links!

  20. #20
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    Well, I still would like to find a way to evaluate my polynomial polyphase asrc on the Teensy.

    My conditions (due to my limited programming knowledge) are:

    1. Don't modifying or add new libraries.
    2. Program code in C (not C++).
    3. Work exclusively in the IDE sketch environment.
    4. Use the spdif(or other) master input and the i2s output for samples.
    5. Find a way to accurately determine the I/O offset sample frequencies.

    If have a working C code for an Src which consumes 128 samples from an input file and generates 128+-N resampled output samples to a FIFO. The FIFO empties 128 of these samples to an output file. The output file is accessed more or less frequently than the input file according to the I/O frequency differences. If anyone is interested or has any suggestions, I show my code below:

    Code:
     /*
                           PROGRAM to execute an asrc between a master spdif input and a master Teensy i2s output
    
    
                                                        (frequency measurement)
                                                                   |
                                                                   |
          (128 sample input)spdif3---->(128 inblock samples)--->Srcblock---->(128+-x outblock samples)---->(512-sample FIFO)----->i2s(128 sample output)
    
          The asrc sinc filter continuously varies its group delay. The coefficients are re-calculated for every new output sample. A 3rd-order polynomial is used. It 
          requires the equivalent of 4 convolutions per sample (4 mono, 3 stereo, 1.6 for 6 channels), but furnishes a THD+N measurement of -130dB on 32-bit samples 
          using only 2672 32-bit integer coefs instead of 20481 32-bit floating point coefs
    
            ------------------ useful formulas ---------------------------------
    	M is the oversampling ratio of the polyphase asrc sinc filter (15)
    	fsout=fsin*(1+ppm/1e6)
    	Tin=1/fsin  
    	Tout=1/fsout
    	dt=(M)*(Tout-Tin)/Tin
    	or dt=(M)*(fsin/fsout-1)
    	or dt=(M)*(nbconsumed/nbgen-1)
    	or dt=-(M)*ppm/(1e6+ppm)
    	fsout/fsin=M/(M+dt)
            -------------------------------------------------------------------*/
    
    
    #include "srconvfunctsT.h"
    
    void Fillinblock(int *inblock,int nbytes,FILE *inputfile); //fill SRC inblock with BLOCKLEN samples from spdif
    void FILLFIFOblock(int *outblock,int nbout,int *FIFO,int *FIFOlvl); //MT SRC outblock (BLOCKLEN+-N samples) to FIFO
    void MTFIFOblock(int *FIFO,int *FIFOlvl,int nbytes,FILE *outputfile,int *pos); //MT BLOCKLEN samples from FIFO to i2s
    
    #define FIFOSIZ 4*BLOCKLEN
    #define FIFOlvlMAX BLOCKLEN-8
    #define FIFOlvlMIN 3*BLOCKLEN+8
    #define XTRASIZ 22 //take into account for upsampling from 44.1->48kHz, need at least 12 XTRASIZ samples in the Srcblock outblock buffer
    
    //#define DEBUG
    
    int     main(int argc, char     *argv[])	
    {
    	double dt,DT,ddt,fsin,fsout,ppm,Tin,Tout,alpha=0.0,dalpha;
    	int j,nch,nbits,intfsin,intfsout,datavalid,pos=0,nbinsmpls,nbytes,nbytestot,nbgenerated,nbconsumed,doi2s=0;	
    #ifdef DEBUG
    	int totalnbconsumed=0,totalnbgenerated=0;
    	double time=0.;
    	FILE *fres;
    #endif
    	FILE *insig,*outsig; //insig contains master spdif input samples, outsig contains master i2s output samples
     	TeensySRCcontext *SRCctxt;//for Teensy Srcblock variables 
    	int outblock[BLOCKLEN+XTRASIZ],inblock[BLOCKLEN],blocksiz=BLOCKLEN;//Srcblock output & input buffers
    	int FIFO[FIFOSIZ]; //FIFO buffers storing the Srcblock output while samples are waiting for next i2s update
    	int FIFOlvl=(int)((double)FIFOSIZ/2.0); //FIFO avg target level 
    	for(j=0;j<FIFOSIZ;j++) FIFO[j]=0; //initialize to 0
    	if (argc<2)
    	{
    		printf("	use: srconv ppm  \n");
    		printf("        (ppm = -81250 for converting 48000 to 44100 Hz)\n");
    		printf("        input file: sinein48long.wav   output file: sineoutT.wav\n");
    		exit(1);
    	}
    	ppm=atof(argv[1]);//ppm=(fsout-fsin)/fsin this value will be determined by measurement of fs offset between input spdif sample frequency and Teensy sample frequency
    	if ((insig=fopen("sinein48long.wav","rb"))==0)
    	{
    		printf("\n\n********** BAD INPUT FILE NAME **********\n\n");
    		exit(1);
    	}
    	outsig=fopen("sineoutT.wav","wb"); //samples from spdif input
    #ifdef DEBUG
    	fres=fopen("res.txt","wt"); //current FIFOlvl containing resampled samples 
    #endif
    	if (readhead(insig,&nch,&nbits,&intfsin,&nbytestot,&datavalid)!=0) //getting input 48kHz wav file parameters
    	{
    		printf("not a wav file\n");
    		exit(1);
    	}
    	fsin=(double)intfsin;
    	fsout=fsin*(1.+ppm/1.e6); //ppm is -81250 to resample at 44100Hz
    	intfsout=(int)(fsout+.5);
    	Tin=1/fsin;
    	Tout=1/fsout;
    	dt=OVERSAMPLING*(Tout-Tin)/Tin; //dt determines the sinc filter group delay variation per output sample
    	ddt=.0001;//small adjustment DT=dt+-ddt to reach correct avg FIFOlvl at FIFOSIZ/2 and to compensate small errors in measured frequency offsets
    	DT=dt; //initial value
    	dalpha=(Tout-Tin)/Tin; //dalpha simulates ratio between spdif isr and i2s update events
    	inithead(nch,nbits,intfsout,outsig); //prepare output wav file
    	nbinsmpls=nbytestot/(nbits/8);
    	nbytes=nbits/8;
    	//----------------------------------------------for Teensy Srcblock variables -----------------------------------------------------
    	SRCctxt=malloc(sizeof(TeensySRCcontext));
    	SRCctxt->pint=malloc(((NTAPS*OVERSAMPLING+1)*(POLYORD+1)/2)*sizeof(int32_t)); //allocate sinc filter fixed point polynomial values
    	SRCctxt->tempbuf=malloc((WORKSIZ+2*OVERSAMPLING*NTAPS)*sizeof(double)); //allocate temporary buffer for calculating polynomial values
    	InitSrc(SRCctxt,fsin,fsout,1); //calculate Srcblock fixed variables according to initial fsin,fsout
    	free(SRCctxt->tempbuf);	//no longer needed unless relatively large modifications of fsin, fsout
    	//-------------------------------------------------------------------------------------------------------------
    
    	while(1) //infinite loop
    	{
    		if (alpha<-.5)
    		{
    			alpha+=1.0;
    			doi2s+=1; //another additional i2s update has occured (upsampling)
    		}
    		else if (alpha>.5)
    		{
    			alpha-=1.0;
    			doi2s-=1; //another additional spdif isr has occured (downsampling)
    		}
    		if (doi2s>0)
    		{
    			//MT BLOCKLEN FIFO samples, i2s output is ready
    			MTFIFOblock(FIFO,&FIFOlvl,nbytes,outsig,&pos);
    			alpha+=dalpha;
    			doi2s--;
    		}
    		else
    		{
    			//Spdif isr is ready. Src executes BLOCKLEN input samples and fills the FIFO with BLOCKLEN+-N samples
    			Fillinblock(inblock,nbytes,insig);
    			Srcblock(1,1,blocksiz,inblock,&nbconsumed,outblock,&nbgenerated,&DT,SRCctxt); //resample BLOCKLEN input samples to BLOCKLEN+-N output samples
    			FILLFIFOblock(outblock,nbgenerated,FIFO,&FIFOlvl); //get SRC nbout samples and put them in FIFO
    #ifdef DEBUG
    			totalnbconsumed+=BLOCKLEN;
    			totalnbgenerated+=nbgenerated;
    #endif
    			nbinsmpls-=BLOCKLEN;
    			doi2s++;
    		}
    		if (FIFOlvl<FIFOlvlMAX) DT=dt+ddt; //slowdown src, FIFO is too full
    		else if (FIFOlvl>FIFOlvlMIN) DT=dt-ddt; //speedup src, FIFO is too MT
    #ifdef DEBUG
    		fprintf(fres,"%d\n",FIFOlvl);
    #endif
    		if (nbinsmpls<BLOCKLEN) break; //end of input wav file
    	} //while(1)
    	fclose(insig);
    	closeheader(pos,outsig);
    	fclose(outsig);
    #ifdef DEBUG
    	fclose(fres);
    #endif
    	//----for Teensy-------
    	free(SRCctxt->pint);
    	free(SRCctxt);
    	//---------------------
    #ifdef DEBUG
    	time=(double)totalnbconsumed/fsin;
    	printf("totalconsumed: %d totalgenerated: %d approximatePPM: %f fsout: %f\n",totalnbconsumed,totalnbgenerated,1e6*(((double)totalnbgenerated/(double)totalnbconsumed)-1.0),(double)totalnbgenerated/time);
    	printf("memory SRCctxt: %d p: %d pint: %d tempbuf: %d\n",sizeof(TeensySRCcontext),((NTAPS*OVERSAMPLING+1)*(POLYORD+1)/2)*sizeof(double),((NTAPS*OVERSAMPLING+1)*(POLYORD+1)/2)*sizeof(int32_t),(WORKSIZ+2*OVERSAMPLING*NTAPS)*sizeof(double));
    #endif
    	return(0); 
    }
    
    void Fillinblock(int *inblock,int nbytes,FILE *inputfile) //fill SRC inblock with BLOCKLEN samples from spdif
    {
    	int i,j=0;
    	unsigned int itemp;
    	unsigned char cbuf;
    	while (j<BLOCKLEN) //fill the buffer with new samples
    	{
    		itemp=0;
    		for (i=0;i<nbytes;i++)
    		{
    			cbuf=fgetc(inputfile);
    			itemp+=cbuf<<((i+4-nbytes)*8);
    		}
    		inblock[j++]=(int)itemp;
    	}
    }
    void FILLFIFOblock(int *outblock,int nbout,int *FIFO,int *FIFOlvl) //MT SRC outblock (BLOCKLEN+-N samples) to FIFO
    {
    	int ibuf;
    	while (nbout>0)
    	{
    		ibuf=*outblock++;
    		FIFO[(*FIFOlvl)--]=ibuf;
    		nbout--;
    	}
    }
    void MTFIFOblock(int *FIFO,int *FIFOlvl,int nbytes,FILE *outputfile,int *pos) //MT BLOCKLEN samples from FIFO to i2s
    {
    	int outptr=FIFOSIZ-1;
    	int i,j,ibuf;
    	unsigned char cbuf;
    	//MT BLOCKLEN samples from FIFO to file
    	for (j=0;j<BLOCKLEN;j++)
    	{
    		(*FIFOlvl)++;
    		ibuf=FIFO[outptr--];
    		for (i=0;i<nbytes;i++)
    		{
    			cbuf=ibuf>>((i+4-nbytes)*8);
    			fputc(cbuf,outputfile);
    			(*pos)++;
    		}
    
    	}
    	//move down remains of the FIFO to the bottom
    	j=outptr;
    	outptr=FIFOSIZ-1;
    	while (j>0)
    	{	
    		FIFO[outptr--]=FIFO[j--];
    	}
    }

  21. #21
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    More specifically:

    I want to put the code I have just displayed in a Teensy sketch called asrc.ino.

    1. Executing asrc.ino, how can I get those 128 inblock samples when they arrive from the spdif3 input?
    2. THEN, how can I know when and how to send my 128 resampled samples from my FIFO to the i2s output?
    3. How can I obtain the frequency offset between the i2s and spdif objects? FreqCount()? ARM_DWT_CYCCNT? Others?

    Simple questions but difficult to answer?

  22. #22
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,748
    The whole audio library runs at one rate, determined by the first component initialized that uses clocking typically, such as i2s input or output.

    The interfaces for pulling samples in and out by code are AudioPlayQueue, AudioPlayMemory, AudioRecordQueue - all documented in the Audio
    library design tool.

    There's no way currently to split this as everything runs synchronously with the object that sets the timing and calls update() on the whole
    library.

  23. #23
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    Quote Originally Posted by MarkT View Post
    The whole audio library runs at one rate, determined by the first component initialized that uses clocking typically, such as i2s input or output.

    The interfaces for pulling samples in and out by code are AudioPlayQueue, AudioPlayMemory, AudioRecordQueue - all documented in the Audio
    library design tool.

    There's no way currently to split this as everything runs synchronously with the object that sets the timing and calls update() on the whole
    library.
    OK. What a pity. Thanks!

  24. #24
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    Quote Originally Posted by MarkT View Post
    The whole audio library runs at one rate, determined by the first component initialized that uses clocking typically, such as i2s input or output.

    The interfaces for pulling samples in and out by code are AudioPlayQueue, AudioPlayMemory, AudioRecordQueue - all documented in the Audio
    library design tool.

    There's no way currently to split this as everything runs synchronously with the object that sets the timing and calls update() on the whole
    library.
    OK so I qualified my asrc solution with the Teensy in real-time by doing varispeed. fsout=fsin, but I speed up and slow down the audio pitch by +- 1 half_tone every .4sec using a 4k software FIFO. Works great but THD -N is limited by the 16-bit sample size of the Teensy audio stream. This solution requires 4 convolutions/sample instead of only 1, but drastically reduces the required coefficients (2672 32-bit integers instead of 20481 32-bit floats). If I were more skilled in C++ and more knowlegable of the processor & environment, I would have tried to create a library. Anyone ever planning to create a simultaneous secondary 24-bit asynchronous audio stream, supported with the corresponding audio system design tool for novices?

  25. #25
    Senior Member
    Join Date
    Sep 2021
    Location
    American living in France
    Posts
    107
    Quote Originally Posted by alex6679 View Post
    I guess it doesn't make sense to connect AudioInputSPDIF3 to anything if it is not the master. But if it is in master mode, you could for example record audio data.

    If you want to resample data from the spdif input, then I think the best way to test your asrc code is to write an own audio library class like the AsyncAudioInputSPDIF3.

    You could basically copy AudioInputSPDIF3. The input samples arrive via dma transfer, that calls the 'isr' method. Just fill a buffer here instead of audio blocks.
    The objec that is the master, like the i2s output you proposed, will trigger calls of the 'update' method. Here you could take the samples of the buffer and resample them into audio blocks.
    So tell me if this is what you mean: (sorry about my awkwardness in using C++)

    Code:
     /* 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 Frank Bösing
     */
    
    #if defined(__IMXRT1052__) || defined(__IMXRT1062__)
    #include <Arduino.h>
    #include "myinput_spdif3.h"
    #include "myoutput_spdif3.h"
    #include "utility/imxrt_hw.h"
    
    DMAMEM __attribute__((aligned(32)))
    static uint32_t spdif_rx_buffer[AUDIO_BLOCK_SAMPLES * 4];
    static int32_t inblockL[AUDIO_BLOCK_SAMPLES];
    static int32_t inblockR[AUDIO_BLOCK_SAMPLES];
    int32_t *myAudioInputSPDIF3::inblkL=inblockL;
    int32_t *myAudioInputSPDIF3::inblkR=inblockR;
    audio_block_t * myAudioInputSPDIF3::block_left = NULL;
    audio_block_t * myAudioInputSPDIF3::block_right = NULL;
    uint16_t myAudioInputSPDIF3::block_offset = 0;
    bool myAudioInputSPDIF3::update_responsibility = false;
    DMAChannel myAudioInputSPDIF3::dma(false);
    
    FLASHMEM
    void myAudioInputSPDIF3::begin(void)
    {
    	dma.begin(true); // Allocate the DMA channel first
    
    	myAudioOutputSPDIF3::config_spdif3();
      Serial.println("begin");
    	const int nbytes_mlno = 2 * 4; // 8 Bytes per minor loop
    	dma.TCD->SADDR = &SPDIF_SRL;
    	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(nbytes_mlno) | DMA_TCD_NBYTES_SMLOE |
                             DMA_TCD_NBYTES_MLOFFYES_MLOFF(-8);
    	dma.TCD->SLAST = -8;
    	dma.TCD->DADDR = spdif_rx_buffer;
    	dma.TCD->DOFF = 4;
    	dma.TCD->DLASTSGA = -sizeof(spdif_rx_buffer);
    	dma.TCD->CITER_ELINKNO = sizeof(spdif_rx_buffer) / nbytes_mlno;
    	dma.TCD->BITER_ELINKNO = sizeof(spdif_rx_buffer) / nbytes_mlno;
    	dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
    	dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SPDIF_RX);
    	update_responsibility = update_setup();
    	dma.attachInterrupt(isr);
    	dma.enable();
    
    
    	SPDIF_SRCD = 0;
    	SPDIF_SCR |= SPDIF_SCR_DMA_RX_EN;
    
    
    
    	CORE_PIN15_CONFIG = 3;
    	IOMUXC_SPDIF_IN_SELECT_INPUT = 0; // GPIO_AD_B1_03_ALT3
      //myAudioInputSPDIF3::*inblkL=inblockL;
    	//pinMode(13, OUTPUT);
    }
    
    void myAudioInputSPDIF3::isr(void)
    {
    	uint32_t daddr/*, offset*/;
    	const int32_t *src, *end;
    	//int16_t *dest_left, *dest_right;
    	//audio_block_t *left, *right;
      int32_t *inL=(int32_t *)myAudioInputSPDIF3::inblkL;
      int32_t *inR=(int32_t *)myAudioInputSPDIF3::inblkR;
    
    	dma.clearInterrupt();
    	//digitalWriteFast(13, !digitalReadFast(13));
    	if (myAudioInputSPDIF3::update_responsibility) AudioStream::update_all();
    
    	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[AUDIO_BLOCK_SAMPLES * 2];
    		end = (int32_t *)&spdif_rx_buffer[AUDIO_BLOCK_SAMPLES * 4];
    	} 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[AUDIO_BLOCK_SAMPLES*2];
    	}
      *inL++=0x12;src++;
      *inR++=0xff34;src++;
    
      do 
      {
        *inL++=(*src++)>>8;
        *inR++=(*src++)>>8;
      } while (src < end);
    
    /*
    	left = myAudioInputSPDIF3::block_left;
    	right = myAudioInputSPDIF3::block_right;
    
    	if (left != NULL && right != NULL) {
    		offset = myAudioInputSPDIF3::block_offset;
    		if (offset <= AUDIO_BLOCK_SAMPLES*2) {
    			dest_left = &(left->data[offset]);
    			dest_right = &(right->data[offset]);
    			myAudioInputSPDIF3::block_offset = offset + AUDIO_BLOCK_SAMPLES*2;
    
    			do {
    				#if IMXRT_CACHE_ENABLED >=1
    				SCB_CACHE_DCIMVAC = (uintptr_t)src;
    				asm("dsb":::"memory");
    				#endif
    
    				*dest_left++ = (*src++) >> 8;
    				*dest_right++ = (*src++) >> 8;
    
    				*dest_left++ = (*src++) >> 8;
    				*dest_right++ = (*src++) >> 8;
    
    				*dest_left++ = (*src++) >> 8;
    				*dest_right++ = (*src++) >> 8;
    
    				*dest_left++ = (*src++) >> 8;
    				*dest_right++ = (*src++) >> 8;
    			} while (src < end);
    		}
    	}
    	else if (left != NULL) {
    		offset = myAudioInputSPDIF3::block_offset;
    		if (offset <= AUDIO_BLOCK_SAMPLES*2) {
    			dest_left = &(left->data[offset]);
    			myAudioInputSPDIF3::block_offset = offset + AUDIO_BLOCK_SAMPLES*2;
    
    			do {
    				#if IMXRT_CACHE_ENABLED >=1
    				SCB_CACHE_DCIMVAC = (uintptr_t)src;
    				asm("dsb":::"memory");
    				#endif
    
    				*dest_left++ = (*src++) >> 8;
    				src++;
    
    				*dest_left++ = (*src++) >> 8;
    				src++;
    
    				*dest_left++ = (*src++) >> 8;
    				src++;
    
    				*dest_left++ = (*src++) >> 8;
    				src++;
    
    			} while (src < end);
    		}		
    	}
    	else if (right != NULL) {
    		offset = myAudioInputSPDIF3::block_offset;
    		if (offset <= AUDIO_BLOCK_SAMPLES*2) {
    			dest_right = &(right->data[offset]);
    			myAudioInputSPDIF3::block_offset = offset + AUDIO_BLOCK_SAMPLES*2;
    
    			do {
    				#if IMXRT_CACHE_ENABLED >=1
    				SCB_CACHE_DCIMVAC = (uintptr_t)src;
    				asm("dsb":::"memory");
    				#endif
    
    				src++;
    				*dest_right++ = (*src++) >> 8;
    
    				src++;
    				*dest_right++ = (*src++) >> 8;
    
    				src++;
    				*dest_right++ = (*src++) >> 8;
    
    				src++;
    				*dest_right++ = (*src++) >> 8;
    
    			} while (src < end);
    		}		
    	}
    */
      //Serial.println("isr");
    }
    
    
    void myAudioInputSPDIF3::update(void)
    {
    /*
    	audio_block_t *new_left=NULL, *new_right=NULL, *out_left=NULL, *out_right=NULL;
    
    	// allocate 2 new blocks, but if one fails, allocate neither
    	new_left = allocate();
    	if (new_left != NULL) {
    		new_right = allocate();
    		if (new_right == NULL) {
    			release(new_left);
    			new_left = NULL;
    		}
    	}
    	__disable_irq();
    	if (block_offset >= AUDIO_BLOCK_SAMPLES) {
    		// the DMA filled 2 blocks, so grab them and get the
    		// 2 new blocks to the DMA, as quickly as possible
    		out_left = block_left;
    		block_left = new_left;
    		out_right = block_right;
    		block_right = new_right;
    		block_offset = 0;
    		__enable_irq();
    		// then transmit the DMA's former blocks
    		transmit(out_left, 0);
    		release(out_left);
    		transmit(out_right, 1);
    		release(out_right);
    		//Serial.print(".");
    	} else if (new_left != NULL) {
    		// the DMA didn't fill blocks, but we allocated blocks
    		if (block_left == NULL) {
    			// the DMA doesn't have any blocks to fill, so
    			// give it the ones we just allocated
    			block_left = new_left;
    			block_right = new_right;
    			block_offset = 0;
    			__enable_irq();
    		} else {
    			// the DMA already has blocks, doesn't need these
    			__enable_irq();
    			release(new_left);
    			release(new_right);
    		}
    	} else {
    		// The DMA didn't fill blocks, and we could not allocate
    		// memory... the system is likely starving for memory!
    		// Sadly, there's nothing we can do.
    		__enable_irq();
    	}
    */
    }
    
    bool myAudioInputSPDIF3::pllLocked(void)
    {
    	return (SPDIF_SRPC & SPDIF_SRPC_LOCK) == SPDIF_SRPC_LOCK ? true:false;
    }
    
    
    unsigned int myAudioInputSPDIF3::sampleRate(void) {
    	if (!pllLocked()) return 0;
    	return (float)((uint64_t)F_BUS_ACTUAL * SPDIF_SRFM) / (0x8000000ULL * myAudioOutputSPDIF3::dpll_Gain()) + 0.5F;
    }
    
    
    #endif
    and:

    Code:
     
    /*
     * A simple hardware test which receives audio from the audio shield
     * Line-In pins and send it to the Line-Out pins and headphone jack.
     *
     * This example code is in the public domain.
     */
    
    #include "myAudio.h"
    #include <Wire.h>
    #include <SPI.h>
    #include <SD.h>
    #include <SerialFlash.h>
    
    // GUItool: begin automatically generated code
    AudioInputI2S            i2s1;           //xy=200,69
    AudioOutputI2S           i2s2;           //xy=365,94
    AudioConnection          patchCord1(i2s1, 0, i2s2, 0);
    AudioConnection          patchCord2(i2s1, 1, i2s2, 1);
    AudioControlSGTL5000     sgtl5000_1;     //xy=302,184
    myAudioInputSPDIF3 spdif;
    // GUItool: end automatically generated code
    
    
    const int myInput = AUDIO_INPUT_LINEIN;
    //const int myInput = AUDIO_INPUT_MIC;
    
    
    
    void setup() {
      // Audio connections require memory to work.  For more
      // detailed information, see the MemoryAndCpuUsage example
      Serial.begin(115200);
      AudioMemory(12);
    
      // Enable the audio shield, select input, and enable output
      sgtl5000_1.enable();
      sgtl5000_1.inputSelect(myInput);
      sgtl5000_1.volume(0.75);
      spdif.begin();
    }
    
    elapsedMillis chk=0;
    
    void loop() {
      int32_t *inL=(int32_t *)myAudioInputSPDIF3::inblkL;
      int32_t *inR=(int32_t *)myAudioInputSPDIF3::inblkR;
    
      // every 1000 ms, chk SR
      if (chk > 1000) {
        Serial.print("spdifL: ");
        Serial.print(*inL,HEX);
        Serial.print("  spdifR: ");
        Serial.println(*inR,HEX);
        Serial.print("sampleRate: ");
        Serial.print(spdif.sampleRate());
        Serial.print(" pllLocked: ");
        Serial.println(spdif.pllLocked());
        chk = 0;               //     
      }
    }

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •