From 4fe37d5d958830d5e543c317d10719eaf3a9c306 Mon Sep 17 00:00:00 2001 From: joerg Date: Sun, 15 May 2022 11:55:33 +0200 Subject: [PATCH] Added float modules for 24 bit precision S/PDIF I/O. They are forked from current code of the "offical" (16 bit 44.1kHz only) Teensy audio library, but extended to 24 bit precision and configurable sample rate. --- OpenAudio_ArduinoLibrary.h | 3 + async_input_spdif3_F32.cpp | 431 +++++++++++++++++++++++++++++++++++++ async_input_spdif3_F32.h | 91 ++++++++ input_spdif3_f32.cpp | 260 ++++++++++++++++++++++ input_spdif3_f32.h | 60 ++++++ output_spdif3_f32.cpp | 308 ++++++++++++++++++++++++++ output_spdif3_f32.h | 62 ++++++ 7 files changed, 1215 insertions(+) create mode 100644 async_input_spdif3_F32.cpp create mode 100644 async_input_spdif3_F32.h create mode 100644 input_spdif3_f32.cpp create mode 100644 input_spdif3_f32.h create mode 100644 output_spdif3_f32.cpp create mode 100644 output_spdif3_f32.h diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index 29eceed..55d0393 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -22,7 +22,10 @@ #include "AudioMultiply_F32.h" #include "AudioSettings_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_spdif3_F32.h" #include "play_queue_f32.h" #include "record_queue_f32.h" #include "synth_pinknoise_f32.h" diff --git a/async_input_spdif3_F32.cpp b/async_input_spdif3_F32.cpp new file mode 100644 index 0000000..3558a76 --- /dev/null +++ b/async_input_spdif3_F32.cpp @@ -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 +//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(&_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 + diff --git a/async_input_spdif3_F32.h b/async_input_spdif3_F32.h new file mode 100644 index 0000000..b808a9a --- /dev/null +++ b/async_input_spdif3_F32.h @@ -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 + +//#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 \ No newline at end of file diff --git a/input_spdif3_f32.cpp b/input_spdif3_f32.cpp new file mode 100644 index 0000000..b516ab2 --- /dev/null +++ b/input_spdif3_f32.cpp @@ -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 +#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 diff --git a/input_spdif3_f32.h b/input_spdif3_f32.h new file mode 100644 index 0000000..ee1492b --- /dev/null +++ b/input_spdif3_f32.h @@ -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 diff --git a/output_spdif3_f32.cpp b/output_spdif3_f32.cpp new file mode 100644 index 0000000..76bf3dc --- /dev/null +++ b/output_spdif3_f32.cpp @@ -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 +#include "output_spdif3_f32.h" + +#if defined(__IMXRT1062__) + +#include "utility/imxrt_hw.h" +#include "memcpy_audio.h" +#include + +// 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 diff --git a/output_spdif3_f32.h b/output_spdif3_f32.h new file mode 100644 index 0000000..7821633 --- /dev/null +++ b/output_spdif3_f32.h @@ -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 +#include "AudioStream_F32.h" +//include "AudioStream.h" +#include + +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