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

Glad that you didn't give up. That is the approach I had in mind.
I haven't checked the details, but as far as I understand your code, you fill a buffer in the isr method. The buffer is only AUDIO_BLOCK_SAMPLES long and you would need to make it much larger. If the input frequency is for example 88.2 kHz you need a buffer with a length of at least 2*AUDIO_BLOCK_SAMPLES. Also, at each isr call you start filling the buffer at its beginning, but you would need to use it as a ring buffer.
You access the buffer in the loop of your main file (I guess only for testing purposes?). If you want to resample the data, it is better if you do the resampling in the update- method of your input class, because we know that this method is called every 128/44100 on average.
 
Glad that you didn't give up. That is the approach I had in mind.
I haven't checked the details, but as far as I understand your code, you fill a buffer in the isr method. The buffer is only AUDIO_BLOCK_SAMPLES long and you would need to make it much larger. If the input frequency is for example 88.2 kHz you need a buffer with a length of at least 2*AUDIO_BLOCK_SAMPLES. Also, at each isr call you start filling the buffer at its beginning, but you would need to use it as a ring buffer.
You access the buffer in the loop of your main file (I guess only for testing purposes?). If you want to resample the data, it is better if you do the resampling in the update- method of your input class, because we know that this method is called every 128/44100 on average.

Thank you for answering. The code that I showed you(long from being finished) directly copies 128x2 samples from the spdif input buffer to a new 128x2 buffer that I created. See it here:

Code:
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];
	}

  do 
  {
    *inL++=(*src++)>>8;
    *inR++=(*src++)>>8;
  } while (src < end);

I don't really understand how daddr is calculated. I was under the assumption that the left sample address is either &spdif_rx_buffer[0] or &spdif_rx_buffer[AUDIO_BLOCK_SAMPLES * 2] in a flip-flop double buffered manner. I don't quite understand the concept of a "ring buffer" (as a delay line I suppose?). Can you explain?

What I planned to do was immediatey execute my src during the isr process and put the src output samples (up to 256x2 samples) into a fifo. Then the update would grab 128x2 samples from this fifo but maybe up to x2 or x(1/2) times as often as the isr interrupt.
 
The data from spdif input is written into spdif_rx_buffer via dma transfer. daddr is just the address at which the dma transfer currently writes data. It is not calculated. The dma transfer calls the isr after the first half of spdif_rx_buffer is filled and after the complete array is filled. daddr is only used to find out which half of the buffer gets currently filled. The data of the other half is copied into your buffer, just as you suspected.

Regarding the ring buffer: I guess I used the wrong term and it is called circular buffer: https://en.wikipedia.org/wiki/Circular_buffer In my implementation, I write the data of spdif_rx_buffer into two circular buffers (one for the left and one for the right channel) during the isr calls. The data is then resampled in the update calls.

I would suggest that you also do the resampling in the update function. The update function is attached to a low priority interrupt and it's OK if more time is spend in this function,
because it can be interrupted by high priority tasks. Also the processorUsage-functions return the time that is spend in 'update'. So, if you do the resampling in update,
you can also use myAudioInputSPDIF3.processorUsage() to measure the performance of your resampling algorithm.
 
The data from spdif input is written into spdif_rx_buffer via dma transfer. daddr is just the address at which the dma transfer currently writes data. It is not calculated. The dma transfer calls the isr after the first half of spdif_rx_buffer is filled and after the complete array is filled. daddr is only used to find out which half of the buffer gets currently filled. The data of the other half is copied into your buffer, just as you suspected.

Regarding the ring buffer: I guess I used the wrong term and it is called circular buffer: https://en.wikipedia.org/wiki/Circular_buffer In my implementation, I write the data of spdif_rx_buffer into two circular buffers (one for the left and one for the right channel) during the isr calls. The data is then resampled in the update calls.

I would suggest that you also do the resampling in the update function. The update function is attached to a low priority interrupt and it's OK if more time is spend in this function,
because it can be interrupted by high priority tasks. Also the processorUsage-functions return the time that is spend in 'update'. So, if you do the resampling in update,
you can also use myAudioInputSPDIF3.processorUsage() to measure the performance of your resampling algorithm.

Thanks for all your answers!

My srcFIFOblock() function presently puts the resampled samples in the FIFO and was planned to be called during isr interrupt. My MTFIFOblock() gets these resampled samples out of the FIFO for the audio_block and was planned to be called during the update interrupt. My "circular buffer" is internal to the srcFIFOblock() and will be updated with new samples obtained during the isr interrupt. If I do as you suggest, I need to change the structure of my src by placing the FIFO at the input of the src. In your suggested configuration, the needed execution cycles for the src are lower when fsin>fsout and so less time required for the src during the update interrupt. But when fsout>fsin, more time is needed during the update.

1. What is the predominant use case? fsin>fsout or fsout>fsin?
2. Will I do the update operations in my modified myinput_spdif3.cpp?
 
What is the predominant use case? fsin>fsout or fsout>fsin?
I think the most common sample rates are 44.1kHz and 48kHz and therefore fsin >= fsout most of the time.

Will I do the update operations in my modified myinput_spdif3.cpp?
Yes, you can call your src algorithm in 'update' of your class.
 
I think the most common sample rates are 44.1kHz and 48kHz and therefore fsin >= fsout most of the time.


Yes, you can call your src algorithm in 'update' of your class.

OK, I rewrote my code with the FIFO at the input. The FIFO can handle stereo input samples at fs=88200Hz. At 44100Hz, my fillFIFOstereo() + SrcblockStereoFIFO() functions consume 15.2% of the total available Teensy 4.0 cycles. This is because of the calculation of the on-the-fly 32-bit coefficients. I don't know if there are use-cases for multi-channel spdif, but the cycles/channel is lower for >2 channels because I use the same coefficients for all. Now, I will try to integrate the above-mentioned functions in input_spdif3 isr() and update() respectively. I may have a few questions as I continue.
 
I think the most common sample rates are 44.1kHz and 48kHz and therefore fsin >= fsout most of the time.


Yes, you can call your src algorithm in 'update' of your class.


If you could help me, it would be greatly appreciated. I would like to know what you think of my modified input_spdif3.cpp shown below. I have created 4 buffers int16_t inblockL,R and outblockL,R of AUDIO_BLOCK_SAMPLES long. I am a little bit confused as to where my SRC output samples should be put.

1. In isr, I load the inblocks from the dma.
2. I call fillFIFOstereo() using inblock samples. (actually called from my sketch for now).
3. In update, I copy previous SrcblockStereoFIFO() output samples in outblocks to block_left, block_right.
4. In update, I flag SrcblockStereoFIFO() generating new outblock samples (in sketch too).

For now, I haven't taken care of the sampling rate offset so there will be glitches(at least) in my audio.
Thanks!

input_spdif3:

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 int16_t inblockL[AUDIO_BLOCK_SAMPLES];
static int16_t inblockR[AUDIO_BLOCK_SAMPLES];
static int16_t Newsmpls;
int16_t *myAudioInputSPDIF3::inblkL=inblockL;
int16_t *myAudioInputSPDIF3::inblkR=inblockR;
int16_t *myAudioInputSPDIF3::newsmpls=&Newsmpls;

static int16_t outblockL[AUDIO_BLOCK_SAMPLES];
static int16_t outblockR[AUDIO_BLOCK_SAMPLES];
static int16_t Getsmpls;
int16_t *myAudioInputSPDIF3::outblkL=outblockL;
int16_t *myAudioInputSPDIF3::outblkR=outblockR;
int16_t *myAudioInputSPDIF3::getsmpls=&Getsmpls;

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;
  	int16_t *inL=(int16_t *)myAudioInputSPDIF3::inblkL;
  	int16_t *inR=(int16_t *)myAudioInputSPDIF3::inblkR;
  	Newsmpls=1;
	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++=int16_t((*src++)>>8);
    *inR++=int16_t((*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)
{
  int i;
/*
	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();
	}
*/

//	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=0;
//		resample(block_left->data, block_right->data,block_offset);
		for (i=0;i<AUDIO_BLOCK_SAMPLES;i++)
		{
			block_left->data[block_offset+i]=(int16_t)outblockL[i];
			block_right->data[block_offset+i]=(int16_t)outblockR[i];
		}
		Getsmpls=1;
//		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

}

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

sketch:

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 "Teensysrconvfuncts.h"
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

AudioOutputI2S           i2s2;           //xy=365,94
AudioControlSGTL5000     sgtl5000_1;     //xy=302,184
myAudioInputSPDIF3 spdif;

const int myInput = AUDIO_INPUT_LINEIN;

double dt=0.0,deltaT=0.0;
TeensySRCScontext  *SRCctxt;
TeensyFIFOstereocontext *FIFOctxt;
int fifosiz=2*BLOCKLEN+2*FIFOmargin+12; //+12 to accomodate upsampling ratio as high as 48/44.1
int *FIFOlvl,nbconsumed=0;

//isr stuff
int16_t *inL=(int16_t *)myAudioInputSPDIF3::inblkL;
int16_t *inR=(int16_t *)myAudioInputSPDIF3::inblkR;
int16_t *Newsmpls=(int16_t*)myAudioInputSPDIF3::newsmpls;

//update stuff
int16_t *outL=(int16_t *)myAudioInputSPDIF3::outblkL;
int16_t *outR=(int16_t *)myAudioInputSPDIF3::outblkR;
int16_t *Getsmpls=(int16_t*)myAudioInputSPDIF3::getsmpls;

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);

  SRCctxt=(TeensySRCScontext*)malloc(sizeof(TeensySRCScontext));
  SRCctxt->pint=(int32_t*)malloc(((NTAPS*OVERSAMPLING+1)*(POLYORD+1)/2)*sizeof(int32_t)); //for sinc filter polynomial coefs
  SRCctxt->tempbuf=(double*)malloc((WORKSIZ+2*OVERSAMPLING*NTAPS)*sizeof(double));
  InitSrcStereo(SRCctxt,44100.,44100.); 
  free(SRCctxt->tempbuf);

  FIFOctxt=(TeensyFIFOstereocontext*)malloc(sizeof(TeensyFIFOstereocontext));
  FIFOctxt->fifo0=(int32_t*)malloc(fifosiz*sizeof(int32_t));
  FIFOctxt->fifo1=(int32_t*)malloc(fifosiz*sizeof(int32_t));
  InitFIFOstereo(FIFOctxt,fifosiz,0);
  FIFOlvl=&FIFOctxt->fifolvl;

  memset(inL, 0,BLOCKLEN*sizeof(int16_t));
  memset(inR, 0,BLOCKLEN*sizeof(int16_t));
  *Newsmpls=0;
  memset(outL, 0,BLOCKLEN*sizeof(int16_t));
  memset(outR, 0,BLOCKLEN*sizeof(int16_t));
  *Getsmpls=0;
  
  spdif.begin();
}

elapsedMillis chk=0;

void loop() 
{
  if (*Newsmpls)
  {
    fillFIFOstereo(inL,inR,&deltaT,FIFOctxt);
    *Newsmpls=0;
  }
  if (*Getsmpls)
  {
     SrcblockStereoFIFO(&nbconsumed,outL,outR,&dt,&deltaT,SRCctxt,FIFOctxt);
     *Getsmpls=0;
  } 

  // every 1000 ms, chk SR
  if (chk > 1000) {
    Serial.print("sampleRate: ");
    Serial.print(spdif.sampleRate());
    Serial.print(" pllLocked: ");
    Serial.println(spdif.pllLocked());
    chk = 0;         
  }
}
 
I am sorry, but at the moment I don't have time to dive deep into your code. However, I had a brief look at your code. So let's see I a can help you. I assume you need to call 'fillFIFOstereo' and 'SrcblockStereoFIFO' if you want to resample data and currently you are polling in 'loop' if there is new data to resample.
I would suggest to move this functions to 'update'.
'inblockL' and 'inblockL' need to be increased. Even if the input sampling rate is 44100 Hz, you can not assume that 'isr' is always called exactly once in between two 'update' calls.
Things like 'TeensySRCScontext *SRCctxt' and 'TeensyFIFOstereocontext *FIFOctxt' could become private members of the spdif input class. Then you can access them in 'update'. If you need to initalize them, you could do that in the constructor of the spdif input.
'InitSrcStereo(SRCctxt,44100.,44100.)' can't be put into the constructor (setup is also not a good place), since the input sample rate is not known in the beginning. In my spdif input class, I set the input sample rate in 'update'. Not each time 'update' is called, but when the input frequency significantly changed.

I am a little bit confused as to where my SRC output samples should be put

If you resample in 'update', then your output samples can be directly put into 'block_left->data' and 'block_right->data'

For now, I haven't taken care of the sampling rate offset so there will be glitches(at least) in my audio.

Good idea. You can take care of that if the input basically works. Also, you should think about how to prevent 'isr' to change 'inblockL' and 'inblockL' as you access them at the resampling.
 
1. 'inblockL' and 'inblockL' need to be increased. Even if the input sampling rate is 44100 Hz, you can not assume that 'isr' is always called exactly once in between two 'update' calls.

If there are 2 isr calls then before an update then I put 2 inblocks into the FIFO. The FIFO takes care of the extra blocks.

2. InitSrcStereo(SRCctxt,44100.,44100.)' can't be put into the constructor (setup is also not a good place)

This is a temporary configuration. the I/O sample frequencies are used to obtain the global cutoff frequencies for my polynomials (eg. fin=48000, fout=44100, so fc=.9*44100/2*(OVERSAMPLINGRATIO)

3. Things like 'TeensySRCScontext *SRCctxt' and 'TeensyFIFOstereocontext *FIFOctxt' could become private members of the spdif input class.

Yes, I will move most of this stuff to the library. But, I will do this as I progress. fillFIFOstereo() and SrcBlockStereoFIFO() were firstly put in the sketch using 0 frequency offet and record and play queues. Ihis was done to easily qualify their functionalities and evaluate their cycles.
 
Hello

I have finished qualifying my multiphase polynomial approximation algorithm for a software asrc. Using C simulations, I obtain a THD+N of -130dB on a 24-bit 44100Hz wav file shifted by 250 ppm. I have then (temporarly) integrated this code in the Teensy 4.0 environment by modifying input_spdif3.cpp. Below are the Teensy resource requirements:

method nch %of total available cycles number of 32-bit coefficients
---------------------------------------------------------------------------------------------------------------
mysrc 2 15.8% 2672
mysrc 6 24.8% 2672
async_input_spdif3(actual) 2 7.2% 20481

My conclusion:

The current async_input_spdif3 is the best solution for spdif slave-mode stereo audio. It requires minimal CPU cycles if there is no need for additional large coefficient memory requirements for other tasks.

My questions:

1. Is there a required use case for a multi-channel asrc? At each sample time, the polynomial approx. method calculates ONLY ONE set of on-the-fly async coefs, whether it be for 1,2, or N channels.
2. Below are portions of my code for the modification to isr & update tasks after modification to input_spdif3.cpp. I find that there is very little code here compared to the original. It works fine but I must have missed something or maybe I'm listening to the wrong music ("Tower of Power", "Chicago", "Stevie Wonder"). Maybe one of you library specialists can enlighten me on my potential lack of understanding of the mysterious Teensy audio stream.

Code:
void myAudioInputSPDIF3::isr(void)
{
	uint32_t daddr; 
	const int32_t *src, *end;
  int16_t *inL=(int16_t *)myAudioInputSPDIF3::inblkL;
  int16_t *inR=(int16_t *)myAudioInputSPDIF3::inblkR;
  Newsmpls=1;
	dma.clearInterrupt();
	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];
	}
	do
	{
    #if IMXRT_CACHE_ENABLED >=1
    SCB_CACHE_DCIMVAC = (uintptr_t)src;
    asm("dsb":::"memory");
    #endif
		*inL++=(*src++)>>8;
		*inR++=(*src++)>>8;
    *inL++=(*src++)>>8;
    *inR++=(*src++)>>8;
    *inL++=(*src++)>>8;
    *inR++=(*src++)>>8;
    *inL++=(*src++)>>8;
    *inR++=(*src++)>>8;
	} while (src < end);
}


void myAudioInputSPDIF3::update(void)
{
  audio_block_t *out_left=NULL, *out_right=NULL;
  out_left=allocate();
  out_right=allocate();
  for (int i=0;i<AUDIO_BLOCK_SAMPLES;i++)
  {
    out_left->data[i]=outblockL[i];
    out_right->data[i]=outblockR[i];
  }
  transmit(out_left, 0);
  release(out_left);
  transmit(out_right, 1);
  release(out_right);
  Getsmpls=1;
}
 
Hi,

1. Is there a required use case for a multi-channel asrc?
Not for the spdif input. But it can be useful for the multichannel i2s slave inputs. I implemented a template class that makes it relatively easy to add the resampling to an arbitrary input. It also works for the multichannel inputs:
https://github.com/alex6679/teensy-4-async-inputs
The coefficients of the resampling filter are also in implementation only computed once and then used for all channels. The processor usage does therefore not increase linearly with the number of channels.

Regarding missing code:
I think the only thing you could add is to check if the allocation of the audio blocks in 'update' was successful or if 'out_left' and 'out_right' are null pointers. Just to make sure that your program doesn't crash if there is not enough audio memory.
 
Hi,

The coefficients of the resampling filter are also in implementation only computed once and then used for all channels. The processor usage does therefore not increase linearly with the number of channels.

Thanx. Was curious if you had a % of total available cycles for 6 channel @ 44100Hz. It's to compare with my value of 24.8% for 6 channels. I'm asking to know if it's interesting for me to work on the i2s slave library for multichannels. Thanx.
 
Hi, I finally found some time to have a look at the processor usage. The problem is that the processor usage depends on input frequency. A high input frequency needs a longer filter to achieve the requested anti-aliasing attenuation. Therefore I tested different input frequencies. Then there are the constructor parameters of the spdif input, that also impact the processor usage:
Code:
AsyncAudioInputSPDIF3(bool dither=false, bool noiseshaping=false,float attenuation=100, int32_t minHalfFilterLength=20, int32_t maxHalfFilterLength=80
I sticked to the default parameters, except 'minHalfFilterLength'. I used minHalfFilterLength=25 instead of 20, because I also used 25 when I estimated THD+N of the algorithm. I have to admit that I used the most pragmatic way to make the test and used my surround processsor in which 4 T4.0 are working. It uses a 48kHz version of the audio library instead of the original 44.1kHz.

target frequency 48kHz
input frequency - usage
Channels=2:
44.1 - 5.3%
48 - 5.3%
88.2 - 7%
96 - 7.5%
176.4 - 13.2%
192 - 14.8%

Channels=8:
44.1 - 16%
48 - 16%
88.2 - 21.7%
96 - 23.5%
176.4 - 43.5%
192 - 47%

At the 2 channel test the setup was: notebook -> hdmi -> hdmi to spdif converter -> spdif input of the teensy.
At the 8 channel test the setup was: notebook -> MiniDsp UsbStreamer -> 8 channel i2s ->i2s input of the teensy.
I used the notebook as signal source, because then the input frequency can easily be changed in the Windows audio settings.
If you want to know the processor usage at 6 channels, you could probably interpolate between the 2-channel and 8-channel result to get an estimation.

Is the processor load independent of the input frequency at your algorithm?
 
Hi, I finally found some time to have a look at the processor usage. The problem is that the processor usage depends on input frequency. A high input frequency needs a longer filter to achieve the requested anti-aliasing attenuation. Therefore I tested different input frequencies. Then there are the constructor parameters of the spdif input, that also impact the processor usage:
Code:
AsyncAudioInputSPDIF3(bool dither=false, bool noiseshaping=false,float attenuation=100, int32_t minHalfFilterLength=20, int32_t maxHalfFilterLength=80
I sticked to the default parameters, except 'minHalfFilterLength'. I used minHalfFilterLength=25 instead of 20, because I also used 25 when I estimated THD+N of the algorithm. I have to admit that I used the most pragmatic way to make the test and used my surround processsor in which 4 T4.0 are working. It uses a 48kHz version of the audio library instead of the original 44.1kHz.

target frequency 48kHz
input frequency - usage
Channels=2:
44.1 - 5.3%
48 - 5.3%
88.2 - 7%
96 - 7.5%
176.4 - 13.2%
192 - 14.8%

Channels=8:
44.1 - 16%
48 - 16%
88.2 - 21.7%
96 - 23.5%
176.4 - 43.5%
192 - 47%

At the 2 channel test the setup was: notebook -> hdmi -> hdmi to spdif converter -> spdif input of the teensy.
At the 8 channel test the setup was: notebook -> MiniDsp UsbStreamer -> 8 channel i2s ->i2s input of the teensy.
I used the notebook as signal source, because then the input frequency can easily be changed in the Windows audio settings.
If you want to know the processor usage at 6 channels, you could probably interpolate between the 2-channel and 8-channel result to get an estimation.

Is the processor load independent of the input frequency at your algorithm?

Thank you very much. Very impressive. My conclusion is that, for any number of channels, your solution uses quite a lot less cycles than mine. I did not study the processor load function of input frequency but I imagine it would be quite constant. At fsin=88200, I'm just putting x2 more input samples in the FIFO but only convolving(where I need all those cycles) half of them and discarding the others. The only advantage I see for using my oversampling polynomial interpolation algorithm is the requirement for only 2672 32-bit coefficients instead of 20481.

It's been interesting to study this subject with you!
 
OOPS! Sorry, not correct about "discarding the others". Effectively, I should have a higher cycle count for 88->44kHz use case.
 
Right, the large look up table that contains the filter coefficients, is probably the largest drawback of my implementation.
Anyway, good that you were abe to perform all your evaluations in the end.
 
Hi, I finally found some time to have a look at the processor usage. The problem is that the processor usage depends on input frequency. A high input frequency needs a longer filter to achieve the requested anti-aliasing attenuation.
..
..
Is the processor load independent of the input frequency at your algorithm?

A check yesterday. I tested the use case fsin=88200, fsout=44100. Of course, I need to change my coefficients to avoid aliasing.

Test1: If I change the values of the same number N of coefficients, I have aliasing of about -110 dBm between 20000 and 22050 Hz.
Test2: If I change the values on 1.5*N coefficients (delay line 89->137), there is about -130 dBm of aliasing between 21000 and 22050 Hz.

I may calculate too many coefficients for use cases of fsin=44100, fsout=44100+-250ppm but I will still use too many cycles to calculate them.
 
Back
Top