boblark 3 years ago
commit 8e470ff2ce
  1. 25
      AudioAlignLR_F32.cpp
  2. 2
      AudioConvert_F32.h
  3. 2
      AudioSettings_F32.cpp
  4. 4
      AudioSettings_F32.h
  5. 4
      OpenAudio_ArduinoLibrary.h
  6. 20
      RadioIQMixer_F32.cpp
  7. 2
      analyze_fft4096_iqem_F32.cpp
  8. 2
      analyze_tonedetect_F32.cpp
  9. 431
      async_input_spdif3_F32.cpp
  10. 91
      async_input_spdif3_F32.h
  11. 1
      examples/TestFFT4096iqEM/TestFFT4096iqEM.ino
  12. 54
      input_i2s_f32.cpp
  13. 260
      input_spdif3_f32.cpp
  14. 60
      input_spdif3_f32.h
  15. 52
      output_i2s_f32.cpp
  16. 308
      output_spdif3_f32.cpp
  17. 62
      output_spdif3_f32.h
  18. 3
      radioModulatedGenerator_F32.cpp
  19. 5
      readme.md

@ -29,7 +29,7 @@
#include "AudioAlignLR_F32.h" #include "AudioAlignLR_F32.h"
void AudioAlignLR_F32::update(void) { static uint32_t nnn = 0; void AudioAlignLR_F32::update(void) {
audio_block_f32_t *block_L,*block_R; audio_block_f32_t *block_L,*block_R;
audio_block_f32_t *block2_L,*block2_R; audio_block_f32_t *block2_L,*block2_R;
audio_block_f32_t *blockOutTestSignal; audio_block_f32_t *blockOutTestSignal;
@ -64,18 +64,15 @@ void AudioAlignLR_F32::update(void) { static uint32_t nnn = 0;
return; return;
} }
if(currentTPinfo.TPsignalHardware == TP_SIGNAL_CODEC) blockOutTestSignal = AudioStream_F32::allocate_f32();
{ if(currentTPinfo.TPsignalHardware==TP_SIGNAL_CODEC && !blockOutTestSignal)
blockOutTestSignal = AudioStream_F32::allocate_f32(); {
if (!blockOutTestSignal) AudioStream_F32::release(block_L);
{ AudioStream_F32::release(block_R);
AudioStream_F32::release(block_L); AudioStream_F32::release(block2_L);
AudioStream_F32::release(block_R); AudioStream_F32::release(block2_R);
AudioStream_F32::release(block2_L); return;
AudioStream_F32::release(block2_R); }
return;
}
}
// Input data is now in block_L and block_R. Filter from there to // Input data is now in block_L and block_R. Filter from there to
// block2_L and block2_R // block2_L and block2_R
@ -211,8 +208,8 @@ void AudioAlignLR_F32::update(void) { static uint32_t nnn = 0;
} }
} }
AudioStream_F32::transmit(blockOutTestSignal, 2); // NOTE: Goes to third output AudioStream_F32::transmit(blockOutTestSignal, 2); // NOTE: Goes to third output
AudioStream_F32::release(blockOutTestSignal);
} }
AudioStream_F32::release(blockOutTestSignal);
currentTPinfo.nMeas++; currentTPinfo.nMeas++;
// Serial.println(micros() - t0); // for timing // Serial.println(micros() - t0); // for timing

@ -36,7 +36,7 @@ class AudioConvert_I16toF32 : public AudioStream_F32 //receive Int and transmits
static void convertAudio_I16toF32(audio_block_t *in, audio_block_f32_t *out, int len) { static void convertAudio_I16toF32(audio_block_t *in, audio_block_f32_t *out, int len) {
//WEA Method. Should look at CMSIS arm_q15_to_float instead: https://www.keil.com/pack/doc/CMSIS/DSP/html/group__q15__to__x.html#gaf8b0d2324de273fc430b0e61ad4e9eb2 //WEA Method. Should look at CMSIS arm_q15_to_float instead: https://www.keil.com/pack/doc/CMSIS/DSP/html/group__q15__to__x.html#gaf8b0d2324de273fc430b0e61ad4e9eb2
const float MAX_INT = 32678.0; const float MAX_INT = 32768.0;
for (int i = 0; i < len; i++) out->data[i] = (float)(in->data[i]); for (int i = 0; i < len; i++) out->data[i] = (float)(in->data[i]);
arm_scale_f32(out->data, 1.0/MAX_INT, out->data, out->length); //divide by 32678 to get -1.0 to +1.0 arm_scale_f32(out->data, 1.0/MAX_INT, out->data, out->length); //divide by 32678 to get -1.0 to +1.0
} }

@ -1,5 +1,5 @@
#include <Arduino.h>
#include "AudioSettings_F32.h" #include "AudioSettings_F32.h"
#include "AudioStream_F32.h" #include "AudioStream_F32.h"

@ -2,9 +2,11 @@
#ifndef _AudioSettings_F32_ #ifndef _AudioSettings_F32_
#define _AudioSettings_F32_ #define _AudioSettings_F32_
#include <AudioStream.h> // for AUDIO_SAMPLE_RATE_EXACT, AUDIO_BLOCK_SAMPLES
class AudioSettings_F32 { class AudioSettings_F32 {
public: public:
AudioSettings_F32(float fs_Hz, int block_size) : AudioSettings_F32(float fs_Hz=AUDIO_SAMPLE_RATE_EXACT, int block_size=AUDIO_BLOCK_SAMPLES) :
sample_rate_Hz(fs_Hz), audio_block_samples(block_size) {} sample_rate_Hz(fs_Hz), audio_block_samples(block_size) {}
const float sample_rate_Hz; const float sample_rate_Hz;
const int audio_block_samples; const int audio_block_samples;

@ -22,7 +22,10 @@
#include "AudioMultiply_F32.h" #include "AudioMultiply_F32.h"
#include "AudioSettings_F32.h" #include "AudioSettings_F32.h"
#include "input_i2s_f32.h" #include "input_i2s_f32.h"
#include "input_spdif3_F32.h"
#include "async_input_spdif3_F32.h"
#include "output_i2s_f32.h" #include "output_i2s_f32.h"
#include "output_spdif3_F32.h"
#include "play_queue_f32.h" #include "play_queue_f32.h"
#include "record_queue_f32.h" #include "record_queue_f32.h"
#include "synth_pinknoise_f32.h" #include "synth_pinknoise_f32.h"
@ -54,3 +57,4 @@
#include "RadioFMDetector_F32.h" #include "RadioFMDetector_F32.h"
#include "radioNoiseBlanker_F32.h" #include "radioNoiseBlanker_F32.h"
#include "synth_sin_cos_f32.h" #include "synth_sin_cos_f32.h"
// #include "USB_Audio_F32.h" Include this separately if needed. Then in IDE Tools>USB Type>Audio

@ -38,14 +38,18 @@ void RadioIQMixer_F32::update(void) {
if (!blockIn0) if (!blockIn0)
return; return;
if(twoChannel) { if(twoChannel)
blockIn1 = AudioStream_F32::receiveWritable_f32(1); {
if (!blockIn1) { blockIn1 = AudioStream_F32::receiveWritable_f32(1);
AudioStream_F32::release(blockIn0); if (!blockIn1)
if(errorPrintIQM) Serial.println("IQMIXER-ERR: No 1 input memory"); {
return; AudioStream_F32::release(blockIn0);
} if(errorPrintIQM) Serial.println("IQMIXER-ERR: No 1 input memory");
} return;
}
}
else
blockIn1 = NULL;
// Try to get a pair of blocks for the IQ output // Try to get a pair of blocks for the IQ output
blockOut_i = AudioStream_F32::allocate_f32(); blockOut_i = AudioStream_F32::allocate_f32();

@ -352,7 +352,7 @@ void AudioAnalyzeFFT4096_IQEM_F32::update(void) {
// Apply the window function, if any, to the time series. Half size window buffer. // Apply the window function, if any, to the time series. Half size window buffer.
if(wNum!=NULL && pWindow) if(wNum>NO_WINDOW && pWindow) // fixed syntax 14May2022 RSL
{ {
for (int i=0; i < 2048; i++) { for (int i=0; i < 2048; i++) {
*(pFFT_buffer + 2*i) *= *(pWindow + i); // real *(pFFT_buffer + 2*i) *= *(pWindow + i); // real

@ -112,7 +112,7 @@ AudioAnalyzeToneDetect_F32::operator bool() {
q2 = out2; q2 = out2;
len = length; len = length;
__enable_irq(); __enable_irq();
power = q1*q1 + q2*q2 - q1*q2*coef;
trigger = (float)len * thresh; trigger = (float)len * thresh;
trigger *= trigger; trigger *= trigger;
//Serial.println("bool: power, trigger = "); Serial.print(power, 6); Serial.print(", "); Serial.println(trigger, 6); //Serial.println("bool: power, trigger = "); Serial.print(power, 6); Serial.print(", "); Serial.println(trigger, 6);

@ -0,0 +1,431 @@
/* Audio Library for Teensy 3.X
* Copyright (c) 2019, Paul Stoffregen, paul@pjrc.com
*
* Development of this audio library was funded by PJRC.COM, LLC by sales of
* Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop
* open source software by purchasing Teensy or other PJRC products.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice, development funding notice, and this permission
* notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/*
by Alexander Walch
*/
#if defined(__IMXRT1062__)
#include "async_input_spdif3_F32.h"
#include "output_spdif3_F32.h"
#include "biquad.h"
#include <utility/imxrt_hw.h>
//Parameters
namespace {
#define SPDIF_RX_BUFFER_LENGTH AUDIO_BLOCK_SAMPLES
const int32_t bufferLength=8*AUDIO_BLOCK_SAMPLES;
const uint16_t noSamplerPerIsr=SPDIF_RX_BUFFER_LENGTH/4;
const float toFloatAudio= (float)(1./pow(2., 23.));
}
// dummy class, no quantization
class Scaler_F32 {
public:
Scaler_F32() {
_factor = 1.0;
};
void configure() {
};
void quantize(float* input, float32_t* output, uint16_t length) {
memcpy(output, input, length * sizeof(float));
/*
for (uint16_t i =0; i< length; i++){
*output++ = *input++ * _factor;
}
*/
};
private:
float _factor;
};
#ifdef DEBUG_SPDIF_IN
volatile bool AsyncAudioInputSPDIF3_F32::bufferOverflow=false;
#endif
volatile uint32_t AsyncAudioInputSPDIF3_F32::microsLast;
DMAMEM __attribute__((aligned(32)))
static int32_t spdif_rx_buffer[SPDIF_RX_BUFFER_LENGTH];
static float bufferR[bufferLength];
static float bufferL[bufferLength];
volatile int32_t AsyncAudioInputSPDIF3_F32::buffer_offset = 0; // read by resample/ written in spdif input isr -> copied at the beginning of 'resmaple' protected by __disable_irq() in resample
int32_t AsyncAudioInputSPDIF3_F32::resample_offset = 0; // read/written by resample/ read in spdif input isr -> no protection needed?
float AsyncAudioInputSPDIF3_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE_EXACT;
DMAChannel AsyncAudioInputSPDIF3_F32::dma(false);
AsyncAudioInputSPDIF3_F32::~AsyncAudioInputSPDIF3_F32(){
delete [] _bufferLPFilter.pCoeffs;
delete [] _bufferLPFilter.pState;
delete quantizer[0];
delete quantizer[1];
}
FLASHMEM
AsyncAudioInputSPDIF3_F32::AsyncAudioInputSPDIF3_F32(const AudioSettings_F32 &settings, float attenuation, int32_t minHalfFilterLength, int32_t maxHalfFilterLength):
AudioStream_F32(0, NULL),
_resampler(attenuation, minHalfFilterLength, maxHalfFilterLength)
{
sample_rate_Hz = settings.sample_rate_Hz;
quantizer[0]=new Scaler_F32();
quantizer[0]->configure();
quantizer[1]=new Scaler_F32();
quantizer[1]->configure();
begin();
}
FLASHMEM
void AsyncAudioInputSPDIF3_F32::begin()
{
AudioOutputSPDIF3_F32::config_spdif3(sample_rate_Hz);
dma.begin(true); // Allocate the DMA channel first
const uint32_t noByteMinorLoop=2*4;
dma.TCD->SOFF = 4;
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2);
dma.TCD->NBYTES_MLNO = DMA_TCD_NBYTES_MLOFFYES_NBYTES(noByteMinorLoop) | DMA_TCD_NBYTES_SMLOE |
DMA_TCD_NBYTES_MLOFFYES_MLOFF(-8);
dma.TCD->SLAST = -8;
dma.TCD->DOFF = 4;
dma.TCD->CITER_ELINKNO = sizeof(spdif_rx_buffer) / noByteMinorLoop;
dma.TCD->DLASTSGA = -sizeof(spdif_rx_buffer);
dma.TCD->BITER_ELINKNO = sizeof(spdif_rx_buffer) / noByteMinorLoop;
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
dma.TCD->SADDR = (void *)((uint32_t)&SPDIF_SRL);
dma.TCD->DADDR = spdif_rx_buffer;
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SPDIF_RX);
//SPDIF_SCR |=SPDIF_SCR_DMA_RX_EN; //DMA Receive Request Enable
dma.enable();
dma.attachInterrupt(isr);
#ifdef DEBUG_SPDIF_IN
while (!Serial);
#endif
_bufferLPFilter.pCoeffs=new float[5];
_bufferLPFilter.numStages=1;
_bufferLPFilter.pState=new float[2];
getCoefficients(_bufferLPFilter.pCoeffs, BiquadType::LOW_PASS, 0., 5., sample_rate_Hz/AUDIO_BLOCK_SAMPLES, 0.5);
SPDIF_SCR &=(~SPDIF_SCR_RXFIFO_OFF_ON); //receive fifo is turned on again
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
}
bool AsyncAudioInputSPDIF3_F32::isLocked() {
return (SPDIF_SRPC & SPDIF_SRPC_LOCK) == SPDIF_SRPC_LOCK;
}
void AsyncAudioInputSPDIF3_F32::resample(float32_t* data_left, float32_t* data_right, int32_t& block_offset){
block_offset=0;
if(!_resampler.initialized() || !isLocked()){
return;
}
int32_t bOffset=buffer_offset;
int32_t resOffset=resample_offset;
uint16_t inputBufferStop = bOffset >= resOffset ? bOffset-resOffset : bufferLength-resOffset;
if (inputBufferStop==0){
return;
}
uint16_t processedLength;
uint16_t outputCount=0;
uint16_t outputLength=AUDIO_BLOCK_SAMPLES;
float resampledBufferL[AUDIO_BLOCK_SAMPLES];
float resampledBufferR[AUDIO_BLOCK_SAMPLES];
_resampler.resample(&bufferL[resOffset],&bufferR[resOffset], inputBufferStop, processedLength, resampledBufferL, resampledBufferR, outputLength, outputCount);
resOffset=(resOffset+processedLength)%bufferLength;
block_offset=outputCount;
if (bOffset > resOffset && block_offset< AUDIO_BLOCK_SAMPLES){
inputBufferStop= bOffset-resOffset;
outputLength=AUDIO_BLOCK_SAMPLES-block_offset;
_resampler.resample(&bufferL[resOffset],&bufferR[resOffset], inputBufferStop, processedLength, resampledBufferL+block_offset, resampledBufferR+block_offset, outputLength, outputCount);
resOffset=(resOffset+processedLength)%bufferLength;
block_offset+=outputCount;
}
quantizer[0]->quantize(resampledBufferL, data_left, block_offset); // TODO: degenerated to a copy, perhaps directly resample into here?
quantizer[1]->quantize(resampledBufferR, data_right, block_offset);
__disable_irq();
resample_offset=resOffset;
__enable_irq();
}
void AsyncAudioInputSPDIF3_F32::isr(void)
{
dma.clearInterrupt();
microsLast=micros();
const int32_t *src, *end;
uint32_t daddr = (uint32_t)(dma.TCD->DADDR);
if (daddr < (uint32_t)spdif_rx_buffer + sizeof(spdif_rx_buffer) / 2) {
// DMA is receiving to the first half of the buffer
// need to remove data from the second half
src = (int32_t *)&spdif_rx_buffer[SPDIF_RX_BUFFER_LENGTH/2];
end = (int32_t *)&spdif_rx_buffer[SPDIF_RX_BUFFER_LENGTH];
//if (AsyncAudioInputSPDIF3_F32::update_responsibility) AudioStream::update_all();
} else {
// DMA is receiving to the second half of the buffer
// need to remove data from the first half
src = (int32_t *)&spdif_rx_buffer[0];
end = (int32_t *)&spdif_rx_buffer[SPDIF_RX_BUFFER_LENGTH/2];
}
if (buffer_offset >=resample_offset ||
(buffer_offset + SPDIF_RX_BUFFER_LENGTH/4) < resample_offset) {
#if IMXRT_CACHE_ENABLED >=1
arm_dcache_delete((void*)src, sizeof(spdif_rx_buffer) / 2);
#endif
float *destR = &(bufferR[buffer_offset]);
float *destL = &(bufferL[buffer_offset]);
do {
int32_t n=(*src) & 0x800000 ? (*src)|0xFF800000 : (*src) & 0xFFFFFF;
*destL++ = (float)(n)*toFloatAudio;
++src;
n=(*src) & 0x800000 ? (*src)|0xFF800000 : (*src) & 0xFFFFFF;
*destR++ = (float)(n)*toFloatAudio;
++src;
} while (src < end);
buffer_offset=(buffer_offset+SPDIF_RX_BUFFER_LENGTH/4)%bufferLength;
}
#ifdef DEBUG_SPDIF_IN
else {
bufferOverflow=true;
}
#endif
}
double AsyncAudioInputSPDIF3_F32::getNewValidInputFrequ(){
//page 2129: FrequMeas[23:0]=FreqMeas_CLK / BUS_CLK * 2^10 * GAIN
if (isLocked()){
const double f=(double)F_BUS_ACTUAL/(1048576.*(double)AudioOutputSPDIF3_F32::dpll_Gain()*128.);// bit clock = 128 * sampling frequency
const double freqMeas=(double)(SPDIF_SRFM & 0xFFFFFF)*f;
if (_lastValidInputFrequ != freqMeas){//frequency not stable yet;
_lastValidInputFrequ=freqMeas;
return -1.;
}
return _lastValidInputFrequ;
}
return -1.;
}
double AsyncAudioInputSPDIF3_F32::getBufferedTime() const{
__disable_irq();
double n=_bufferedTime;
__enable_irq();
return n;
}
void AsyncAudioInputSPDIF3_F32::configure(){
if(!isLocked()){
_resampler.reset();
return;
}
#ifdef DEBUG_SPDIF_IN
const bool bOverf=bufferOverflow;
bufferOverflow=false;
if (bOverf){
Serial.print("buffer overflow, buffer offset: ");
Serial.print(buffer_offset);
Serial.print(", resample_offset: ");
Serial.println(resample_offset);
if (!_resampler.initialized()){
Serial.println("_resampler not initialized. ");
}
}
#endif
const double inputF=getNewValidInputFrequ(); //returns: -1 ... invalid frequency
if (inputF > 0.){
//we got a valid sample frequency
const double frequDiff=inputF/_inputFrequency-1.;
if (abs(frequDiff) > 0.01 || !_resampler.initialized()){
//the new sample frequency differs from the last one -> configure the _resampler again
_inputFrequency=inputF;
_targetLatencyS=max(0.001,(noSamplerPerIsr*3./2./_inputFrequency));
_maxLatency=max(2.*_blockDuration, 2*noSamplerPerIsr/_inputFrequency);
const int32_t targetLatency=round(_targetLatencyS*inputF);
__disable_irq();
resample_offset = targetLatency <= buffer_offset ? buffer_offset - targetLatency : bufferLength -(targetLatency-buffer_offset);
__enable_irq();
_resampler.configure(inputF, sample_rate_Hz);
#ifdef DEBUG_SPDIF_IN
Serial.print("_maxLatency: ");
Serial.println(_maxLatency);
Serial.print("targetLatency: ");
Serial.println(targetLatency);
Serial.print("relative frequ diff: ");
Serial.println(frequDiff, 8);
Serial.print("configure _resampler with frequency ");
Serial.println(inputF,8);
#endif
}
}
}
void AsyncAudioInputSPDIF3_F32::monitorResampleBuffer(){
if(!_resampler.initialized()){
return;
}
__disable_irq();
const double dmaOffset=(micros()-microsLast)*1e-6; //[seconds]
double bTime = resample_offset <= buffer_offset ? (buffer_offset-resample_offset-_resampler.getXPos())/_lastValidInputFrequ+dmaOffset : (bufferLength-resample_offset +buffer_offset-_resampler.getXPos())/_lastValidInputFrequ+dmaOffset; //[seconds]
double diff = bTime- (_blockDuration+ _targetLatencyS); //seconds
biquad_cascade_df2T<double, arm_biquad_cascade_df2T_instance_f32, float>(&_bufferLPFilter, &diff, &diff, 1);
bool settled=_resampler.addToSampleDiff(diff);
if (bTime > _maxLatency || bTime-dmaOffset<= _blockDuration || settled) {
double distance=(_blockDuration+_targetLatencyS-dmaOffset)*_lastValidInputFrequ+_resampler.getXPos();
diff=0.;
if (distance > bufferLength-noSamplerPerIsr){
diff=bufferLength-noSamplerPerIsr-distance;
distance=bufferLength-noSamplerPerIsr;
}
if (distance < 0.){
distance=0.;
diff=- (_blockDuration+ _targetLatencyS);
}
double resample_offsetF=buffer_offset-distance;
resample_offset=(int32_t)floor(resample_offsetF);
_resampler.addToPos(resample_offsetF-resample_offset);
while (resample_offset<0){
resample_offset+=bufferLength;
}
#ifdef DEBUG_SPDIF_IN
double bTimeFixed = resample_offset <= buffer_offset ? (buffer_offset-resample_offset-_resampler.getXPos())/_lastValidInputFrequ+dmaOffset : (bufferLength-resample_offset +buffer_offset-_resampler.getXPos())/_lastValidInputFrequ+dmaOffset; //[seconds]
#endif
__enable_irq();
#ifdef DEBUG_SPDIF_IN
Serial.print("settled: ");
Serial.println(settled);
Serial.print("bTime: ");
Serial.println(bTime*1e6,3);
Serial.print("_maxLatency: ");
Serial.println(_maxLatency*1e6,3);
Serial.print("bTime-dmaOffset: ");
Serial.println((bTime-dmaOffset)*1e6,3);
Serial.print(", _blockDuration: ");
Serial.println(_blockDuration*1e6,3);
Serial.print("bTimeFixed: ");
Serial.println(bTimeFixed*1e6,3);
#endif
preload(&_bufferLPFilter, (float)diff);
_resampler.fixStep();
}
else {
__enable_irq();
}
_bufferedTime=_targetLatencyS+diff;
}
void AsyncAudioInputSPDIF3_F32::update(void)
{
configure();
monitorResampleBuffer(); //important first call 'monitorResampleBuffer' then 'resample'
audio_block_f32_t *block_left = allocate_f32();
audio_block_f32_t *block_right = nullptr;
if (block_left!= nullptr) {
block_right = allocate_f32();
if (block_right == nullptr) {
release(block_left);
block_left = nullptr;
}
}
if (block_left && block_right) {
int32_t block_offset;
resample(block_left->data, block_right->data,block_offset);
if(block_offset < AUDIO_BLOCK_SAMPLES){
memset(block_left->data+block_offset, 0, (AUDIO_BLOCK_SAMPLES-block_offset)*sizeof(float32_t));
memset(block_right->data+block_offset, 0, (AUDIO_BLOCK_SAMPLES-block_offset)*sizeof(float32_t));
#ifdef DEBUG_SPDIF_IN
Serial.print("filled only ");
Serial.print(block_offset);
Serial.println(" samples.");
#endif
}
transmit(block_left, 0);
release(block_left);
block_left=nullptr;
transmit(block_right, 1);
release(block_right);
block_right=nullptr;
}
#ifdef DEBUG_SPDIF_IN
else {
Serial.println("Not enough blocks available. Too few audio memory?");
}
#endif
}
double AsyncAudioInputSPDIF3_F32::getInputFrequency() const{
__disable_irq();
double f=_lastValidInputFrequ;
__enable_irq();
return isLocked() ? f : 0.;
}
double AsyncAudioInputSPDIF3_F32::getTargetLantency() const {
__disable_irq();
double l=_targetLatencyS;
__enable_irq();
return l ;
}
double AsyncAudioInputSPDIF3_F32::getAttenuation() const{
return _resampler.getAttenuation();
}
int32_t AsyncAudioInputSPDIF3_F32::getHalfFilterLength() const{
return _resampler.getHalfFilterLength();
}
#endif // __IMXRT1062__
#if defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MKL26Z64__)
// empty code to allow compile (but no sound input) on other Teensy models
#include "async_input_spdif3.h"
AsyncAudioInputSPDIF3_F32::AsyncAudioInputSPDIF3_F32(bool dither, bool noiseshaping,float attenuation, int32_t minHalfFilterLength, int32_t maxHalfFilterLength):
AudioStream(0, NULL), _resampler(attenuation, minHalfFilterLength, maxHalfFilterLength)
{ }
void AsyncAudioInputSPDIF3_F32::begin() { }
void AsyncAudioInputSPDIF3_F32::update(void) { }
double AsyncAudioInputSPDIF3_F32::getBufferedTime() const { return 0; }
double AsyncAudioInputSPDIF3_F32::getInputFrequency() const { return 0; }
bool AsyncAudioInputSPDIF3_F32::isLocked() { return false; }
double AsyncAudioInputSPDIF3_F32::getTargetLantency() const { return 0; }
double AsyncAudioInputSPDIF3_F32::getAttenuation() const { return 0; }
int32_t AsyncAudioInputSPDIF3_F32::getHalfFilterLength() const { return 0; }
AsyncAudioInputSPDIF3_F32::~AsyncAudioInputSPDIF3_F32() { }
#endif

@ -0,0 +1,91 @@
/* Audio Library for Teensy 3.X
* Copyright (c) 2019, Paul Stoffregen, paul@pjrc.com
*
* Development of this audio library was funded by PJRC.COM, LLC by sales of
* Teensy and Audio Adaptor boards. Please support PJRC's efforts to develop
* open source software by purchasing Teensy or other PJRC products.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice, development funding notice, and this permission
* notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/*
by Alexander Walch
*/
#ifndef async_input_spdif3_f32_h_
#define async_input_spdif3_f32_h_
#include "Resampler.h"
#include "Quantizer.h"
#include "Arduino.h"
#include "AudioStream_F32.h"
#include "DMAChannel.h"
#include <arm_math.h>
//#define DEBUG_SPDIF_IN //activates debug output
class Scaler_F32; // internal
class AsyncAudioInputSPDIF3_F32 : public AudioStream_F32
{
public:
///@param attenuation target attenuation [dB] of the anti-aliasing filter. Only used if AUDIO_SAMPLE_RATE_EXACT < input sample rate (input fs). The attenuation can't be reached if the needed filter length exceeds 2*MAX_FILTER_SAMPLES+1
///@param minHalfFilterLength If AUDIO_SAMPLE_RATE_EXACT >= input fs), the filter length of the resampling filter is 2*minHalfFilterLength+1. If AUDIO_SAMPLE_RATE_EXACT < input fs the filter is maybe longer to reach the desired attenuation
///@param maxHalfFilterLength Can be used to restrict the maximum filter length at the cost of a lower attenuation
AsyncAudioInputSPDIF3_F32(const AudioSettings_F32 &settings, float attenuation=100, int32_t minHalfFilterLength=20, int32_t maxHalfFilterLength=80);
~AsyncAudioInputSPDIF3_F32();
void begin();
virtual void update(void);
double getBufferedTime() const;
double getInputFrequency() const;
static bool isLocked();
double getTargetLantency() const;
double getAttenuation() const;
int32_t getHalfFilterLength() const;
protected:
static DMAChannel dma;
static void isr(void);
private:
void resample(float32_t* data_left, float32_t* data_right, int32_t& block_offset);
void monitorResampleBuffer();
void configure();
double getNewValidInputFrequ();
void config_spdifIn();
//accessed in isr ====
static volatile int32_t buffer_offset;
static int32_t resample_offset;
static volatile uint32_t microsLast;
//====================
Resampler _resampler;
Scaler_F32* quantizer[2];
arm_biquad_cascade_df2T_instance_f32 _bufferLPFilter;
volatile double _bufferedTime;
volatile double _lastValidInputFrequ;
double _inputFrequency=0.;
double _targetLatencyS; //target latency [seconds]
const double _blockDuration=AUDIO_BLOCK_SAMPLES/AUDIO_SAMPLE_RATE_EXACT; //[seconds]
double _maxLatency=2.*_blockDuration;
static float sample_rate_Hz; // configured output sample rate
#ifdef DEBUG_SPDIF_IN
static volatile bool bufferOverflow;
#endif
};
#endif

@ -76,7 +76,6 @@ void setup(void) {
void loop(void) { void loop(void) {
static bool doPrint=true; static bool doPrint=true;
float *pPwr;
// Print output, once // Print output, once
if( FFT4096iqEM1.available() && jj++>2 && doPrint ) { if( FFT4096iqEM1.available() && jj++>2 && doPrint ) {

@ -40,7 +40,7 @@
#include <arm_math.h> #include <arm_math.h>
//DMAMEM __attribute__((aligned(32))) //DMAMEM __attribute__((aligned(32)))
static uint32_t i2s_rx_buffer[AUDIO_BLOCK_SAMPLES]; //good for 16-bit audio samples coming in from teh AIC. 32-bit transfers will need this to be bigger. static uint64_t i2s_rx_buffer[AUDIO_BLOCK_SAMPLES]; // Two 32-bit transfers per sample.
audio_block_f32_t * AudioInputI2S_F32::block_left_f32 = NULL; audio_block_f32_t * AudioInputI2S_F32::block_left_f32 = NULL;
audio_block_f32_t * AudioInputI2S_F32::block_right_f32 = NULL; audio_block_f32_t * AudioInputI2S_F32::block_right_f32 = NULL;
uint16_t AudioInputI2S_F32::block_offset = 0; uint16_t AudioInputI2S_F32::block_offset = 0;
@ -60,7 +60,7 @@ int AudioInputI2S_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES;
//#define I2S_BUFFER_TO_USE_BYTES (AudioOutputI2S_F32::audio_block_samples*2*sizeof(i2s_rx_buffer[0])) //#define I2S_BUFFER_TO_USE_BYTES (AudioOutputI2S_F32::audio_block_samples*2*sizeof(i2s_rx_buffer[0]))
void AudioInputI2S_F32::begin(void) { void AudioInputI2S_F32::begin(void) {
bool transferUsing32bit = false; bool transferUsing32bit = true; // be official, although this is not cared about
begin(transferUsing32bit); begin(transferUsing32bit);
} }
@ -78,19 +78,19 @@ void AudioInputI2S_F32::begin(bool transferUsing32bit) {
#if defined(KINETISK) #if defined(KINETISK)
CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0 CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0
dma.TCD->SADDR = (void *)((uint32_t)&I2S0_RDR0 + 2); //From Teensy Audio Library...but why "+ 2" (Chip 2020-10-31) dma.TCD->SADDR = (void *)((uint32_t)&I2S0_RDR0 + 0); // take the full 32 bit (not just upper half)
dma.TCD->SOFF = 0; dma.TCD->SOFF = 0;
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2);
dma.TCD->NBYTES_MLNO = 2; dma.TCD->NBYTES_MLNO = 4;
dma.TCD->SLAST = 0; dma.TCD->SLAST = 0;
dma.TCD->DADDR = i2s_rx_buffer; dma.TCD->DADDR = i2s_rx_buffer;
dma.TCD->DOFF = 2; dma.TCD->DOFF = 4;
//dma.TCD->CITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; //original from Teensy Audio Library //dma.TCD->CITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; //original from Teensy Audio Library
dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 2; dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4;
//dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer); //original from Teensy Audio Library //dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer); //original from Teensy Audio Library
dma.TCD->DLASTSGA = -I2S_BUFFER_TO_USE_BYTES; dma.TCD->DLASTSGA = -I2S_BUFFER_TO_USE_BYTES;
//dma.TCD->BITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; //original from Teensy Audio Library //dma.TCD->BITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; //original from Teensy Audio Library
dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 2; dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4;
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_RX); dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_RX);
@ -101,19 +101,19 @@ void AudioInputI2S_F32::begin(bool transferUsing32bit) {
CORE_PIN8_CONFIG = 3; //1:RX_DATA0 CORE_PIN8_CONFIG = 3; //1:RX_DATA0
IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2; IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2;
dma.TCD->SADDR = (void *)((uint32_t)&I2S1_RDR0 + 2); dma.TCD->SADDR = (void *)((uint32_t)&I2S1_RDR0 + 0);
dma.TCD->SOFF = 0; dma.TCD->SOFF = 0;
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2);
dma.TCD->NBYTES_MLNO = 2; dma.TCD->NBYTES_MLNO = 4;
dma.TCD->SLAST = 0; dma.TCD->SLAST = 0;
dma.TCD->DADDR = i2s_rx_buffer; dma.TCD->DADDR = i2s_rx_buffer;
dma.TCD->DOFF = 2; dma.TCD->DOFF = 4;
//dma.TCD->CITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; //original from Teensy Audio Library //dma.TCD->CITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; //original from Teensy Audio Library
dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 2; dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4;
//dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer); //original from Teensy Audio Library //dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer); //original from Teensy Audio Library
dma.TCD->DLASTSGA = -I2S_BUFFER_TO_USE_BYTES; dma.TCD->DLASTSGA = -I2S_BUFFER_TO_USE_BYTES;
//dma.TCD->BITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; //original from Teensy Audio Library //dma.TCD->BITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; //original from Teensy Audio Library
dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 2; dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4;
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_RX); dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_RX);
@ -265,7 +265,7 @@ void AudioInputI2S_F32::begin(bool transferUsing32bit) {
void AudioInputI2S_F32::isr(void) void AudioInputI2S_F32::isr(void)
{ {
uint32_t daddr, offset; uint32_t daddr, offset;
const int16_t *src, *end; const int32_t *src, *end;
//int16_t *dest_left, *dest_right; //int16_t *dest_left, *dest_right;
//audio_block_t *left, *right; //audio_block_t *left, *right;
float32_t *dest_left_f32, *dest_right_f32; float32_t *dest_left_f32, *dest_right_f32;
@ -283,16 +283,16 @@ void AudioInputI2S_F32::isr(void)
// need to remove data from the second half // need to remove data from the second half
//src = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original Teensy Audio Library //src = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original Teensy Audio Library
//end = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES]; //original Teensy Audio Library //end = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES]; //original Teensy Audio Library
src = (int16_t *)&i2s_rx_buffer[audio_block_samples/2]; src = (int32_t *)&i2s_rx_buffer[audio_block_samples/2];
end = (int16_t *)&i2s_rx_buffer[audio_block_samples]; end = (int32_t *)&i2s_rx_buffer[audio_block_samples];
update_counter++; //let's increment the counter here to ensure that we get every ISR resulting in audio update_counter++; //let's increment the counter here to ensure that we get every ISR resulting in audio
if (AudioInputI2S_F32::update_responsibility) AudioStream_F32::update_all(); if (AudioInputI2S_F32::update_responsibility) AudioStream_F32::update_all();
} else { } else {
// DMA is receiving to the second half of the buffer // DMA is receiving to the second half of the buffer
// need to remove data from the first half // need to remove data from the first half
src = (int16_t *)&i2s_rx_buffer[0]; src = (int32_t *)&i2s_rx_buffer[0];
//end = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original Teensy Audio Library //end = (int16_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original Teensy Audio Library
end = (int16_t *)&i2s_rx_buffer[audio_block_samples/2]; end = (int32_t *)&i2s_rx_buffer[audio_block_samples/2];
} }
left_f32 = AudioInputI2S_F32::block_left_f32; left_f32 = AudioInputI2S_F32::block_left_f32;
right_f32 = AudioInputI2S_F32::block_right_f32; right_f32 = AudioInputI2S_F32::block_right_f32;
@ -480,8 +480,8 @@ void AudioInputI2S_F32::scale_i32_to_f32( float32_t *p_i32, float32_t *p_f32, in
if (!out_f32) return; if (!out_f32) return;
//scale the float values so that the maximum possible audio values span -1.0 to + 1.0 //scale the float values so that the maximum possible audio values span -1.0 to + 1.0
//scale_i32_to_f32(out_f32->data, out_f32->data, audio_block_samples); scale_i32_to_f32(out_f32->data, out_f32->data, audio_block_samples);
scale_i16_to_f32(out_f32->data, out_f32->data, audio_block_samples); //scale_i16_to_f32(out_f32->data, out_f32->data, audio_block_samples);
//prepare to transmit by setting the update_counter (which helps tell if data is skipped or out-of-order) //prepare to transmit by setting the update_counter (which helps tell if data is skipped or out-of-order)
out_f32->id = update_counter; out_f32->id = update_counter;
@ -564,16 +564,16 @@ void AudioInputI2Sslave_F32::begin(void)
#if defined(KINETISK) #if defined(KINETISK)
CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0 CORE_PIN13_CONFIG = PORT_PCR_MUX(4); // pin 13, PTC5, I2S0_RXD0
dma.TCD->SADDR = (void *)((uint32_t)&I2S0_RDR0 + 2); dma.TCD->SADDR = (void *)((uint32_t)&I2S0_RDR0 + 0);
dma.TCD->SOFF = 0; dma.TCD->SOFF = 0;
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2);
dma.TCD->NBYTES_MLNO = 2; dma.TCD->NBYTES_MLNO = 4;
dma.TCD->SLAST = 0; dma.TCD->SLAST = 0;
dma.TCD->DADDR = i2s_rx_buffer; dma.TCD->DADDR = i2s_rx_buffer;
dma.TCD->DOFF = 2; dma.TCD->DOFF = 4;
dma.TCD->CITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; dma.TCD->CITER_ELINKNO = sizeof(i2s_rx_buffer) / 4;
dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer); dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer);
dma.TCD->BITER_ELINKNO = sizeof(i2s_rx_buffer) / 2; dma.TCD->BITER_ELINKNO = sizeof(i2s_rx_buffer) / 4;
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_RX); dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_RX);

@ -0,0 +1,260 @@
/* 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 "input_spdif3_F32.h"
#include "output_spdif3_F32.h"
#include "utility/imxrt_hw.h"
// sign extend and scale
static inline float32_t i24_to_f32(int32_t n) {
const float32_t scale = 1.0 / (1LL << 31);
int32_t leftaligned = (uint32_t)n << 8; // to avoid manual sign extension
return scale * leftaligned;
}
DMAMEM __attribute__((aligned(32)))
static uint32_t spdif_rx_buffer[AUDIO_BLOCK_SAMPLES * 4];
audio_block_f32_t * AudioInputSPDIF3_F32::block_left = NULL;
audio_block_f32_t * AudioInputSPDIF3_F32::block_right = NULL;
uint16_t AudioInputSPDIF3_F32::block_offset = 0;
bool AudioInputSPDIF3_F32::update_responsibility = false;
DMAChannel AudioInputSPDIF3_F32::dma(false);
FLASHMEM
void AudioInputSPDIF3_F32::begin(void)
{
dma.begin(true); // Allocate the DMA channel first
AudioOutputSPDIF3_F32::config_spdif3(sample_rate_Hz);
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
//pinMode(13, OUTPUT);
}
void AudioInputSPDIF3_F32::isr(void)
{
uint32_t daddr, offset;
const int32_t *src, *end;
float32_t *dest_left, *dest_right;
audio_block_f32_t *left, *right;
dma.clearInterrupt();
//digitalWriteFast(13, !digitalReadFast(13));
if (AudioInputSPDIF3_F32::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];
}
left = AudioInputSPDIF3_F32::block_left;
right = AudioInputSPDIF3_F32::block_right;
if (left != NULL && right != NULL) {
offset = AudioInputSPDIF3_F32::block_offset;
if (offset <= AUDIO_BLOCK_SAMPLES*2) {
dest_left = &(left->data[offset]);
dest_right = &(right->data[offset]);
AudioInputSPDIF3_F32::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++ = i24_to_f32(*src++);
*dest_right++ = i24_to_f32(*src++);
*dest_left++ = i24_to_f32(*src++);
*dest_right++ = i24_to_f32(*src++);
*dest_left++ = i24_to_f32(*src++);
*dest_right++ = i24_to_f32(*src++);
*dest_left++ = i24_to_f32(*src++);
*dest_right++ = i24_to_f32(*src++);
} while (src < end);
}
}
else if (left != NULL) {
offset = AudioInputSPDIF3_F32::block_offset;
if (offset <= AUDIO_BLOCK_SAMPLES*2) {
dest_left = &(left->data[offset]);
AudioInputSPDIF3_F32::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++ = i24_to_f32(*src++);
src++;
*dest_left++ = i24_to_f32(*src++);
src++;
*dest_left++ = i24_to_f32(*src++);
src++;
*dest_left++ = i24_to_f32(*src++);
src++;
} while (src < end);
}
}
else if (right != NULL) {
offset = AudioInputSPDIF3_F32::block_offset;
if (offset <= AUDIO_BLOCK_SAMPLES*2) {
dest_right = &(right->data[offset]);
AudioInputSPDIF3_F32::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++ = i24_to_f32(*src++);
src++;
*dest_right++ = i24_to_f32(*src++);
src++;
*dest_right++ = i24_to_f32(*src++);
src++;
*dest_right++ = i24_to_f32(*src++);
} while (src < end);
}
}
}
void AudioInputSPDIF3_F32::update(void)
{
audio_block_f32_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_f32();
if (new_left != NULL) {
new_right = allocate_f32();
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 AudioInputSPDIF3_F32::pllLocked(void)
{
return (SPDIF_SRPC & SPDIF_SRPC_LOCK) == SPDIF_SRPC_LOCK ? true:false;
}
unsigned int AudioInputSPDIF3_F32::sampleRate(void) {
if (!pllLocked()) return 0;
return (float)((uint64_t)F_BUS_ACTUAL * SPDIF_SRFM) / (0x8000000ULL * AudioOutputSPDIF3_F32::dpll_Gain()) + 0.5F;
}
#endif

@ -0,0 +1,60 @@
/* 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.
*/
// Frank Bösing
#if defined(__IMXRT1052__) || defined(__IMXRT1062__)
#ifndef _input_spdif3_f32_h_
#define _input_spdif3_f32_h_
#include "Arduino.h"
#include "AudioStream_F32.h"
#include "DMAChannel.h"
class AudioInputSPDIF3_F32 : public AudioStream_F32
{
public:
AudioInputSPDIF3_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) {
sample_rate_Hz = settings.sample_rate_Hz;
begin();
}
virtual void update(void);
void begin(void);
static bool pllLocked(void);
static unsigned int sampleRate(void);
protected:
//AudioInputSPDIF3_F32(int dummy): AudioStream_F32(0, NULL) {} // to be used only inside AudioInputSPDIF3slave !!
static bool update_responsibility;
static DMAChannel dma;
static void isr(void);
private:
static audio_block_f32_t *block_left;
static audio_block_f32_t *block_right;
static uint16_t block_offset;
static float sample_rate_Hz;
};
#endif
#endif

@ -164,7 +164,7 @@ uint16_t AudioOutputI2S_F32::block_left_offset = 0;
uint16_t AudioOutputI2S_F32::block_right_offset = 0; uint16_t AudioOutputI2S_F32::block_right_offset = 0;
bool AudioOutputI2S_F32::update_responsibility = false; bool AudioOutputI2S_F32::update_responsibility = false;
DMAChannel AudioOutputI2S_F32::dma(false); DMAChannel AudioOutputI2S_F32::dma(false);
DMAMEM __attribute__((aligned(32))) static uint32_t i2s_tx_buffer[AUDIO_BLOCK_SAMPLES]; DMAMEM __attribute__((aligned(32))) static uint64_t i2s_tx_buffer[AUDIO_BLOCK_SAMPLES];
//DMAMEM static int32_t i2s_tx_buffer[2*AUDIO_BLOCK_SAMPLES]; //2 channels at 32-bits per sample. Local "audio_block_samples" should be no larger than global "AUDIO_BLOCK_SAMPLES" //DMAMEM static int32_t i2s_tx_buffer[2*AUDIO_BLOCK_SAMPLES]; //2 channels at 32-bits per sample. Local "audio_block_samples" should be no larger than global "AUDIO_BLOCK_SAMPLES"
float AudioOutputI2S_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; float AudioOutputI2S_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE;
@ -199,18 +199,18 @@ void AudioOutputI2S_F32::begin(bool transferUsing32bit) {
CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0
dma.TCD->SADDR = i2s_tx_buffer; dma.TCD->SADDR = i2s_tx_buffer;
dma.TCD->SOFF = 2; dma.TCD->SOFF = 4;
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2);
dma.TCD->NBYTES_MLNO = 2; dma.TCD->NBYTES_MLNO = 4;
//dma.TCD->SLAST = -sizeof(i2s_tx_buffer);//orig from Teensy Audio Library 2020-10-31 //dma.TCD->SLAST = -sizeof(i2s_tx_buffer);//orig from Teensy Audio Library 2020-10-31
dma.TCD->SLAST = -I2S_BUFFER_TO_USE_BYTES; dma.TCD->SLAST = -I2S_BUFFER_TO_USE_BYTES;
dma.TCD->DADDR = (void *)((uint32_t)&I2S0_TDR0 + 2); dma.TCD->DADDR = (void *)((uint32_t)&I2S0_TDR0 + 0);
dma.TCD->DOFF = 0; dma.TCD->DOFF = 0;
//dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; //orig from Teensy Audio Library 2020-10-31 //dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; //orig from Teensy Audio Library 2020-10-31
dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 2; dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4;
dma.TCD->DLASTSGA = 0; dma.TCD->DLASTSGA = 0;
//dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;//orig from Teensy Audio Library 2020-10-31 //dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;//orig from Teensy Audio Library 2020-10-31
dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 2; dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4;
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX);
dma.enable(); //newer location of this line in Teensy Audio library dma.enable(); //newer location of this line in Teensy Audio library
@ -222,19 +222,19 @@ void AudioOutputI2S_F32::begin(bool transferUsing32bit) {
CORE_PIN7_CONFIG = 3; //1:TX_DATA0 CORE_PIN7_CONFIG = 3; //1:TX_DATA0
dma.TCD->SADDR = i2s_tx_buffer; dma.TCD->SADDR = i2s_tx_buffer;
dma.TCD->SOFF = 2; dma.TCD->SOFF = 4;
dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2);
dma.TCD->NBYTES_MLNO = 2; dma.TCD->NBYTES_MLNO = 4;
//dma.TCD->SLAST = -sizeof(i2s_tx_buffer);//orig from Teensy Audio Library 2020-10-31 //dma.TCD->SLAST = -sizeof(i2s_tx_buffer);//orig from Teensy Audio Library 2020-10-31
dma.TCD->SLAST = -I2S_BUFFER_TO_USE_BYTES; dma.TCD->SLAST = -I2S_BUFFER_TO_USE_BYTES;
dma.TCD->DOFF = 0; dma.TCD->DOFF = 0;
//dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; //orig from Teensy Audio Library 2020-10-31 //dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; //orig from Teensy Audio Library 2020-10-31
dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 2; dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4;
dma.TCD->DLASTSGA = 0; dma.TCD->DLASTSGA = 0;
//dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;//orig from Teensy Audio Library 2020-10-31 //dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2;//orig from Teensy Audio Library 2020-10-31
dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 2; dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4;
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 0);
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX);
dma.enable(); //newer location of this line in Teensy Audio library dma.enable(); //newer location of this line in Teensy Audio library
@ -253,7 +253,7 @@ void AudioOutputI2S_F32::begin(bool transferUsing32bit) {
void AudioOutputI2S_F32::isr(void) void AudioOutputI2S_F32::isr(void)
{ {
#if defined(KINETISK) || defined(__IMXRT1062__) #if defined(KINETISK) || defined(__IMXRT1062__)
int16_t *dest; int32_t *dest;
audio_block_f32_t *blockL, *blockR; audio_block_f32_t *blockL, *blockR;
uint32_t saddr, offsetL, offsetR; uint32_t saddr, offsetL, offsetR;
@ -264,12 +264,12 @@ void AudioOutputI2S_F32::isr(void)
// DMA is transmitting the first half of the buffer // DMA is transmitting the first half of the buffer
// so we must fill the second half // so we must fill the second half
//dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original Teensy Audio //dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original Teensy Audio
dest = (int16_t *)&i2s_tx_buffer[audio_block_samples/2]; //this will be diff if we were to do 32-bit samples dest = (int32_t *)&i2s_tx_buffer[audio_block_samples/2]; //this will be diff if we were to do 32-bit samples
if (AudioOutputI2S_F32::update_responsibility) AudioStream_F32::update_all(); if (AudioOutputI2S_F32::update_responsibility) AudioStream_F32::update_all();
} else { } else {
// DMA is transmitting the second half of the buffer // DMA is transmitting the second half of the buffer
// so we must fill the first half // so we must fill the first half
dest = (int16_t *)i2s_tx_buffer; dest = (int32_t *)i2s_tx_buffer;
} }
blockL = AudioOutputI2S_F32::block_left_1st; blockL = AudioOutputI2S_F32::block_left_1st;
@ -277,15 +277,15 @@ void AudioOutputI2S_F32::isr(void)
offsetL = AudioOutputI2S_F32::block_left_offset; offsetL = AudioOutputI2S_F32::block_left_offset;
offsetR = AudioOutputI2S_F32::block_right_offset; offsetR = AudioOutputI2S_F32::block_right_offset;
int16_t *d = dest; int32_t *d = dest;
if (blockL && blockR) { if (blockL && blockR) {
//memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR); //memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR);
//memcpy_tointerleaveLRwLen(dest, blockL->data + offsetL, blockR->data + offsetR, audio_block_samples/2); //memcpy_tointerleaveLRwLen(dest, blockL->data + offsetL, blockR->data + offsetR, audio_block_samples/2);
float32_t *pL = blockL->data + offsetL; float32_t *pL = blockL->data + offsetL;
float32_t *pR = blockR->data + offsetR; float32_t *pR = blockR->data + offsetR;
for (int i=0; i < audio_block_samples/2; i++) { for (int i=0; i < audio_block_samples/2; i++) {
*d++ = (int16_t) *pL++; *d++ = (int32_t) *pL++;
*d++ = (int16_t) *pR++; //interleave *d++ = (int32_t) *pR++; //interleave
//*d++ = 0; //*d++ = 0;
//*d++ = 0; //*d++ = 0;
} }
@ -294,15 +294,15 @@ void AudioOutputI2S_F32::isr(void)
} else if (blockL) { } else if (blockL) {
//memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR); //memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR);
float32_t *pL = blockL->data + offsetL; float32_t *pL = blockL->data + offsetL;
for (int i=0; i < audio_block_samples / 2 * 2; i+=2) { *(d+i) = (int16_t) *pL++; } //interleave for (int i=0; i < audio_block_samples / 2 * 2; i+=2) { *(d+i) = (int32_t) *pL++; } //interleave
offsetL += audio_block_samples / 2; offsetL += audio_block_samples / 2;
} else if (blockR) { } else if (blockR) {
float32_t *pR = blockR->data + offsetR; float32_t *pR = blockR->data + offsetR;
for (int i=0; i < audio_block_samples /2 * 2; i+=2) { *(d+i) = (int16_t) *pR++; } //interleave for (int i=0; i < audio_block_samples /2 * 2; i+=2) { *(d+i) = (int32_t) *pR++; } //interleave
offsetR += audio_block_samples / 2; offsetR += audio_block_samples / 2;
} else { } else {
//memset(dest,0,AUDIO_BLOCK_SAMPLES * 2); //memset(dest,0,AUDIO_BLOCK_SAMPLES * 2);
memset(dest,0,audio_block_samples * 2); memset(dest,0,audio_block_samples * 4);
return; return;
} }
@ -664,8 +664,8 @@ void AudioOutputI2S_F32::update(void)
//scale F32 to Int32 //scale F32 to Int32
//block_f32_scaled = AudioStream_F32::allocate_f32(); //block_f32_scaled = AudioStream_F32::allocate_f32();
//scale_f32_to_i32(block_f32->data, block_f32_scaled->data, audio_block_samples); scale_f32_to_i32(block_f32->data, block_f32_scaled->data, audio_block_samples);
scale_f32_to_i16(block_f32->data, block_f32_scaled->data, audio_block_samples); //scale_f32_to_i16(block_f32->data, block_f32_scaled->data, audio_block_samples);
//count++; //count++;
//if (count > 100) { //if (count > 100) {
@ -709,8 +709,8 @@ void AudioOutputI2S_F32::update(void)
//scale F32 to Int32 //scale F32 to Int32
//block_f32_scaled = AudioStream_F32::allocate_f32(); //block_f32_scaled = AudioStream_F32::allocate_f32();
//scale_f32_to_i32(block_f32->data, block_f32_scaled->data, audio_block_samples); scale_f32_to_i32(block_f32->data, block_f32_scaled->data, audio_block_samples);
scale_f32_to_i16(block_f32->data, block_f32_scaled->data, audio_block_samples); //scale_f32_to_i16(block_f32->data, block_f32_scaled->data, audio_block_samples);
__disable_irq(); __disable_irq();
if (block_right_1st == NULL) { if (block_right_1st == NULL) {

@ -0,0 +1,308 @@
/* Hardware-SPDIF for Teensy 4
* Copyright (c) 2019, Frank Bösing, f.boesing@gmx.de
*
* 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.
*/
/*
http://www.hardwarebook.info/S/PDIF
https://www.mikrocontroller.net/articles/S/PDIF
https://en.wikipedia.org/wiki/S/PDIF
*/
#include <Arduino.h>
#include "output_spdif3_f32.h"
#if defined(__IMXRT1062__)
#include "utility/imxrt_hw.h"
#include "memcpy_audio.h"
#include <math.h>
// TODO: convert within update() instead of isr(), into buffer
static inline int32_t f32_to_i24(float32_t f) {
const float32_t fullscale = (1LL << 23) - 1;
if (f > 1.0) return fullscale;
if (f < -1.0) return -fullscale;
return (int32_t)(f * fullscale);
}
audio_block_f32_t * AudioOutputSPDIF3_F32::block_left_1st = nullptr;
audio_block_f32_t * AudioOutputSPDIF3_F32::block_right_1st = nullptr;
audio_block_f32_t * AudioOutputSPDIF3_F32::block_left_2nd = nullptr;
audio_block_f32_t * AudioOutputSPDIF3_F32::block_right_2nd = nullptr;
bool AudioOutputSPDIF3_F32::update_responsibility = false;
float AudioOutputSPDIF3_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE_EXACT;
DMAChannel AudioOutputSPDIF3_F32::dma(false);
DMAMEM __attribute__((aligned(32)))
static int32_t SPDIF_tx_buffer[AUDIO_BLOCK_SAMPLES * 4];
DMAMEM __attribute__((aligned(32)))
audio_block_f32_t AudioOutputSPDIF3_F32::block_silent;
#define SPDIF_DPLL_GAIN24 0
#define SPDIF_DPLL_GAIN16 1
#define SPDIF_DPLL_GAIN12 2
#define SPDIF_DPLL_GAIN8 3
#define SPDIF_DPLL_GAIN6 4
#define SPDIF_DPLL_GAIN4 5
#define SPDIF_DPLL_GAIN3 6
#define SPDIF_DPLL_GAIN1 7
#define SPDIF_DPLL_GAIN SPDIF_DPLL_GAIN8 //Actual Gain
static const uint8_t spdif_gain[8] = {24, 16, 12, 8, 6, 4, 3, 1};
FLASHMEM
void AudioOutputSPDIF3_F32::begin(void)
{
dma.begin(true); // Allocate the DMA channel first
block_left_1st = nullptr;
block_right_1st = nullptr;
memset(&block_silent, 0, sizeof(block_silent));
config_spdif3(sample_rate_Hz);
const int nbytes_mlno = 2 * 4; // 8 Bytes per minor loop
dma.TCD->SADDR = SPDIF_tx_buffer;
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_DMLOE |
DMA_TCD_NBYTES_MLOFFYES_MLOFF(-8);
dma.TCD->SLAST = -sizeof(SPDIF_tx_buffer);
dma.TCD->DADDR = &SPDIF_STL;
dma.TCD->DOFF = 4;
dma.TCD->DLASTSGA = -8;
//dma.TCD->ATTR_DST = ((31 - __builtin_clz(8)) << 3);
dma.TCD->CITER_ELINKNO = sizeof(SPDIF_tx_buffer) / nbytes_mlno;
dma.TCD->BITER_ELINKNO = sizeof(SPDIF_tx_buffer) / nbytes_mlno;
dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR;
dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SPDIF_TX);
update_responsibility = update_setup();
dma.enable();
dma.attachInterrupt(isr);
CORE_PIN14_CONFIG = 3; //3:SPDIF_OUT
SPDIF_SCR |= SPDIF_SCR_DMA_TX_EN;
SPDIF_STC |= SPDIF_STC_TX_ALL_CLK_EN;
// pinMode(13, OUTPUT);
}
void AudioOutputSPDIF3_F32::isr(void)
{
const float32_t *src_left, *src_right;
const int32_t *end;
int32_t *dest;
audio_block_f32_t *block_left, *block_right;
uint32_t saddr;
saddr = (uint32_t)(dma.TCD->SADDR);
dma.clearInterrupt();
if (saddr < (uint32_t)SPDIF_tx_buffer + sizeof(SPDIF_tx_buffer) / 2) {
// DMA is transmitting the first half of the buffer
// so we must fill the second half
dest = SPDIF_tx_buffer + AUDIO_BLOCK_SAMPLES*2;
end = SPDIF_tx_buffer + AUDIO_BLOCK_SAMPLES*4;
} else {
// DMA is transmitting the second half of the buffer
// so we must fill the first half
dest = SPDIF_tx_buffer;
end = SPDIF_tx_buffer + AUDIO_BLOCK_SAMPLES*2;
}
block_left = block_left_1st;
if (!block_left) block_left = &block_silent;
block_right = block_right_1st;
if (!block_right) block_right = &block_silent;
src_left = (const float32_t *)(block_left->data);
src_right = (const float32_t *)(block_right->data);
do {
#if IMXRT_CACHE_ENABLED >= 2
SCB_CACHE_DCCIMVAC = (uintptr_t) dest;
asm volatile("dsb");
#endif
*dest++ = f32_to_i24(*src_left++);
*dest++ = f32_to_i24(*src_right++);
*dest++ = f32_to_i24(*src_left++);
*dest++ = f32_to_i24(*src_right++);
*dest++ = f32_to_i24(*src_left++);
*dest++ = f32_to_i24(*src_right++);
*dest++ = f32_to_i24(*src_left++);
*dest++ = f32_to_i24(*src_right++);
} while (dest < end);
if (block_left != &block_silent) {
release(block_left);
block_left_1st = block_left_2nd;
block_left_2nd = nullptr;
}
if (block_right != &block_silent) {
release(block_right);
block_right_1st = block_right_2nd;
block_right_2nd = nullptr;
}
if (update_responsibility) update_all();
//digitalWriteFast(13,!digitalReadFast(13));
}
void AudioOutputSPDIF3_F32::update(void)
{
audio_block_f32_t *block_left, *block_right;
block_left = receiveReadOnly_f32(0); // input 0
block_right = receiveReadOnly_f32(1); // input 1
__disable_irq();
if (block_left) {
if (block_left_1st == nullptr) {
block_left_1st = block_left;
block_left = nullptr;
} else if (block_left_2nd == nullptr) {
block_left_2nd = block_left;
block_left = nullptr;
} else {
audio_block_f32_t *tmp = block_left_1st;
block_left_1st = block_left_2nd;
block_left_2nd = block_left;
block_left = tmp;
}
}
if (block_right) {
if (block_right_1st == nullptr) {
block_right_1st = block_right;
block_right = nullptr;
} else if (block_right_2nd == nullptr) {
block_right_2nd = block_right;
block_right = nullptr;
} else {
audio_block_f32_t *tmp = block_right_1st;
block_right_1st = block_right_2nd;
block_right_2nd = block_right;
block_right = tmp;
}
}
__enable_irq();
if (block_left) {
release(block_left);
}
if (block_right) {
release(block_right);
}
}
void AudioOutputSPDIF3_F32::mute_PCM(const bool mute)
{
if (mute)
SPDIF_SCR |= SPDIF_SCR_VALCTRL;
else
SPDIF_SCR &= ~SPDIF_SCR_VALCTRL;
}
uint32_t AudioOutputSPDIF3_F32::dpll_Gain(void)
{
return spdif_gain[SPDIF_DPLL_GAIN];
}
FLASHMEM
void AudioOutputSPDIF3_F32::config_spdif3(float fs_Hz)
{
delay(1); //WHY IS THIS NEEDED?
uint32_t fs = fs_Hz;
// PLL between 27*24 = 648MHz und 54*24=1296MHz
// n1, n2 choosen for compatibility with I2S (same PLL frequency) :
int n1 = 4; //SAI prescaler 4 => (n1*n2) = multiple of 4
int n2 = 1 + (24000000 * 27) / (fs * 256 * n1);
double C = ((double)fs * 256 * n1 * n2) / 24000000;
int c0 = C;
int c2 = 10000;
int c1 = C * c2 - (c0 * c2);
set_audioClock(c0, c1, c2);
//use new pred/podf values
n1 = 7; //0: divide by 1 (do not use with high input frequencies), 1:/2, 2: /3, 7:/8
n2 = 0; //0: divide by 1, 7: divide by 8
CCM_CCGR5 &= ~CCM_CCGR5_SPDIF(CCM_CCGR_ON); //Clock gate off
CCM_CDCDR = (CCM_CDCDR & ~(CCM_CDCDR_SPDIF0_CLK_SEL_MASK | CCM_CDCDR_SPDIF0_CLK_PRED_MASK | CCM_CDCDR_SPDIF0_CLK_PODF_MASK))
| CCM_CDCDR_SPDIF0_CLK_SEL(0) // 0 PLL4, 1 PLL3 PFD2, 2 PLL5, 3 pll3_sw_clk
| CCM_CDCDR_SPDIF0_CLK_PRED(n1)
| CCM_CDCDR_SPDIF0_CLK_PODF(n2);
CCM_CCGR5 |= CCM_CCGR5_SPDIF(CCM_CCGR_ON); //Clock gate on
if (!(SPDIF_SCR & (SPDIF_SCR_DMA_RX_EN | SPDIF_SCR_DMA_TX_EN))) {
//Serial.print("Reset SPDIF3");
SPDIF_SCR = SPDIF_SCR_SOFT_RESET; //Reset SPDIF
while (SPDIF_SCR & SPDIF_SCR_SOFT_RESET) {;} //Wait for Reset (takes 8 cycles)
} else return;
SPDIF_SCR =
SPDIF_SCR_RXFIFOFULL_SEL(0) | // Full interrupt if at least 1 sample in Rx left and right FIFOs
SPDIF_SCR_RXAUTOSYNC |
SPDIF_SCR_TXAUTOSYNC |
SPDIF_SCR_TXFIFOEMPTY_SEL(2) | // Empty interrupt if at most 8 samples in Tx left and right FIFOs
SPDIF_SCR_TXFIFO_CTRL(1) | // 0:Send zeros 1: normal operation
SPDIF_SCR_VALCTRL | // Outgoing Validity always clear
SPDIF_SCR_TXSEL(5) | // 0:off and output 0, 1:Feed-though SPDIFIN, 5:Tx Normal operation
SPDIF_SCR_USRC_SEL(3);
SPDIF_SRPC =
SPDIF_SRPC_CLKSRC_SEL(1) | //if (DPLL Locked) SPDIF_RxClk else tx_clk (SPDIF0_CLK_ROOT)
SPDIF_SRPC_GAINSEL(SPDIF_DPLL_GAIN);
uint32_t pllclock = (c0 + (float)c1 / c2) * 24000000ULL; //677376000 Hz
uint32_t clock = pllclock / (1 + n1) / (1 + n2);
uint32_t clkdiv = clock / (fs * 64); // 1 .. 128
uint32_t mod = clock % (fs * 64);
if (mod > ((fs * 64) / 2)) clkdiv += 1; //nearest divider
#if 0
Serial.printf("PLL: %d\n", pllclock);
Serial.printf("clock: %d\n", clock);
Serial.printf("clkdiv: %d\n", clkdiv);
#endif
SPDIF_STC =
SPDIF_STC_TXCLK_SOURCE(1) | //tx_clk input (from SPDIF0_CLK_ROOT)
SPDIF_STC_TXCLK_DF(clkdiv - 1);
}
#endif // __IMXRT1062__
#if defined(__MK66FX1M0__) || defined(__MK64FX512__) || defined(__MK20DX256__) || defined(__MKL26Z64__)
// empty code to allow compile (but no sound output) on other Teensy models
void AudioOutputSPDIF3_F32::update(void) { }
void AudioOutputSPDIF3_F32::begin(void) { }
void AudioOutputSPDIF3_F32::mute_PCM(const bool mute) { }
bool AudioOutputSPDIF3_F32::pll_locked(void) { return false; }
#endif

@ -0,0 +1,62 @@
/* Hardware-SPDIF for Teensy 4
* Copyright (c) 2019, Frank Bösing, f.boesing@gmx.de
*
* 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.
*/
#ifndef output_SPDIF3_f32_h_
#define output_SPDIF3_f32_h_
#include <Arduino.h>
#include "AudioStream_F32.h"
//include "AudioStream.h"
#include <DMAChannel.h>
class AudioOutputSPDIF3_F32 : public AudioStream_F32
{
public:
AudioOutputSPDIF3_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray) {
sample_rate_Hz = settings.sample_rate_Hz;
begin();
}
virtual void update(void);
void begin(void);
friend class AudioInputSPDIF3_F32;
friend class AsyncAudioInputSPDIF3_F32;
static void mute_PCM(const bool mute);
static bool pll_locked(void);
protected:
//AudioOutputSPDIF3_F32(int dummy): AudioStream(2, inputQueueArray) {}
static void config_spdif3(float fs_Hz);
static audio_block_f32_t *block_left_1st;
static audio_block_f32_t *block_right_1st;
static bool update_responsibility;
static DMAChannel dma;
static void isr(void);
private:
static uint32_t dpll_Gain() __attribute__ ((const));
static audio_block_f32_t *block_left_2nd;
static audio_block_f32_t *block_right_2nd;
static audio_block_f32_t block_silent;
audio_block_f32_t *inputQueueArray[2];
static float sample_rate_Hz;
};
#endif

@ -34,12 +34,14 @@ void radioModulatedGenerator_F32::update(void) {
float32_t a, b, deltaPhase, phaseC, amSig; float32_t a, b, deltaPhase, phaseC, amSig;
// Input 0 is for amplitude modulation. // Input 0 is for amplitude modulation.
inAmpl = NULL;
if(doAM) { if(doAM) {
inAmpl = AudioStream_F32::receiveReadOnly_f32(0); inAmpl = AudioStream_F32::receiveReadOnly_f32(0);
if (!inAmpl) return; if (!inAmpl) return;
} }
// Input 1 is for phase or frequency modulation. // Input 1 is for phase or frequency modulation.
inPhaseFreq = NULL;
if(doPM || doFM) { if(doPM || doFM) {
inPhaseFreq = AudioStream_F32::receiveReadOnly_f32(1); inPhaseFreq = AudioStream_F32::receiveReadOnly_f32(1);
if (!inPhaseFreq) { if (!inPhaseFreq) {
@ -55,6 +57,7 @@ void radioModulatedGenerator_F32::update(void) {
return; return;
} }
outBlockQ = NULL;
if(bothIQ) { if(bothIQ) {
outBlockQ = AudioStream_F32::allocate_f32(); outBlockQ = AudioStream_F32::allocate_f32();
if (!outBlockQ) { if (!outBlockQ) {

@ -22,6 +22,11 @@ ready to be used for T3.x and T4.x. There are some restrictions, particularly t
**Tympan Project** Many of the classes in this library were put together as part of the [Tympan Project.](https://github.com/Tympan) That is oriented towards open-source hearing aid and hearing aid development tools. It has its own [Tympan Design Tool](https://tympan.github.io/Tympan_Audio_Design_Tool/) as well as some custom Teensy-based hardware. Additionally, there are a few classes in this library that use terminology and variables that are specific to audiology. It is intended that these, in time, be replaced by similar classes with more conventional descriptors. And, of course, if your interest is in hearing aids, you should spend time at the Tympan project! **Tympan Project** Many of the classes in this library were put together as part of the [Tympan Project.](https://github.com/Tympan) That is oriented towards open-source hearing aid and hearing aid development tools. It has its own [Tympan Design Tool](https://tympan.github.io/Tympan_Audio_Design_Tool/) as well as some custom Teensy-based hardware. Additionally, there are a few classes in this library that use terminology and variables that are specific to audiology. It is intended that these, in time, be replaced by similar classes with more conventional descriptors. And, of course, if your interest is in hearing aids, you should spend time at the Tympan project!
Notes
-----
1 - The USB_Audio_F32.h includes all the functions needed to use USB audio for input and output. However, it is not in the OpenAudio_ArduinoLibrary.h as would be expected. To use this class, add "#include USB_Audio_F32.h" to the INO file and before compiling, go to the IDE Tools>USB Type and set the radio button to "Audio." This should then compile without error. Also, using this class requires some amount of I16 audio memory, such as a line in the top of the INO, "AudioMemory(10);"
Installation Installation
------------ ------------

Loading…
Cancel
Save