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

D

DeletedUser

Guest
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: GUITool.jpg


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


sineout.jpg
 
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-Sync-to-I2S?highlight=word+start+interrupt
 
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-Sync-to-I2S?highlight=word+start+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
 
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.
 
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.
 
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);
}
 
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 ...
 
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
 
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
 
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.
 
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.
 
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:
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!
 
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--];
	}
}
 
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?
 
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.
 
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!
 
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?
 
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;               //     
  }
}
 
Back
Top