diff --git a/AudioEffectDelay_OA_F32.cpp b/AudioEffectDelay_OA_F32.cpp index 3c0e048..36dc606 100644 --- a/AudioEffectDelay_OA_F32.cpp +++ b/AudioEffectDelay_OA_F32.cpp @@ -59,9 +59,9 @@ void AudioEffectDelay_OA_F32::receiveIncomingData(void) { int dest_ind = writeposition; //inclusive if (dest_ind >= (queue[head]->full_length)) { head++; dest_ind = 0; - if (head >= DELAY_QUEUE_SIZE) head = 0; + if (head >= DELAY_QUEUE_SIZE_OA) head = 0; if (queue[head] != NULL) { - if (head==tail) {tail++; if (tail >= DELAY_QUEUE_SIZE) tail = 0; } + if (head==tail) {tail++; if (tail >= DELAY_QUEUE_SIZE_OA) tail = 0; } AudioStream_F32::release(queue[head]); queue[head]=NULL; } @@ -94,9 +94,9 @@ void AudioEffectDelay_OA_F32::receiveIncomingData(void) { //finish writing taking data from the next queue buffer if (src_count < n_copy) { // there's still more input data to copy...but we need to roll-over to a new input queue head++; dest_ind = 0; - if (head >= DELAY_QUEUE_SIZE) head = 0; + if (head >= DELAY_QUEUE_SIZE_OA) head = 0; if (queue[head] != NULL) { - if (head==tail) {tail++; if (tail >= DELAY_QUEUE_SIZE) tail = 0; } + if (head==tail) {tail++; if (tail >= DELAY_QUEUE_SIZE_OA) tail = 0; } AudioStream_F32::release(queue[head]); queue[head]=NULL; } @@ -132,15 +132,15 @@ void AudioEffectDelay_OA_F32::discardUnneededBlocksFromQueue(void) { if (head >= tail) { count = head - tail; } else { - count = DELAY_QUEUE_SIZE + head - tail; + count = DELAY_QUEUE_SIZE_OA + head - tail; } /* if (head>0) { - Serial.print("AudioEffectDelay_OA_F32::discardUnneededBlocksFromQueue: head, tail, count, maxblocks, DELAY_QUEUE_SIZE: "); + Serial.print("AudioEffectDelay_OA_F32::discardUnneededBlocksFromQueue: head, tail, count, maxblocks, DELAY_QUEUE_SIZE_OA: "); Serial.print(head); Serial.print(", "); Serial.print(tail); Serial.print(", "); Serial.print(count); Serial.print(", "); Serial.print(maxblocks); Serial.print(", "); - Serial.print(DELAY_QUEUE_SIZE); Serial.print(", "); + Serial.print(DELAY_QUEUE_SIZE_OA); Serial.print(", "); Serial.println(); } */ if (count > maxblocks) { @@ -150,7 +150,7 @@ void AudioEffectDelay_OA_F32::discardUnneededBlocksFromQueue(void) { AudioStream_F32::release(queue[tail]); queue[tail] = NULL; } - if (++tail >= DELAY_QUEUE_SIZE) tail = 0; + if (++tail >= DELAY_QUEUE_SIZE_OA) tail = 0; } while (--count > 0); } tailindex = tail; @@ -176,7 +176,7 @@ void AudioEffectDelay_OA_F32::transmitOutgoingData(void) { //receiveIncomingData method. We'll adjust it next uint32_t offset_samp = delay_samps[channel]+output->length; if (ref_samp_long < offset_samp) { //when (ref_samp_long - offset_samp) goes negative, the uint32_t will fail, so we do this logic check - ref_samp_long = ref_samp_long + (((uint32_t)(DELAY_QUEUE_SIZE))*((uint32_t)AUDIO_BLOCK_SIZE_F32)); + ref_samp_long = ref_samp_long + (((uint32_t)(DELAY_QUEUE_SIZE_OA))*((uint32_t)AUDIO_BLOCK_SIZE_F32)); } ref_samp_long = ref_samp_long - offset_samp; uint16_t source_queue_ind = (uint16_t)(ref_samp_long / ((uint32_t)AUDIO_BLOCK_SIZE_F32)); @@ -207,7 +207,7 @@ void AudioEffectDelay_OA_F32::transmitOutgoingData(void) { //yes, we need to keep filling the output int Iend = n_output - dest_counter; //how many more data points do we need source_queue_ind++; source_samp = 0; //which source block will we draw from (and reset the source sample counter) - if (source_queue_ind >= DELAY_QUEUE_SIZE) source_queue_ind = 0; //wrap around on our source black. + if (source_queue_ind >= DELAY_QUEUE_SIZE_OA) source_queue_ind = 0; //wrap around on our source black. source_block = queue[source_queue_ind]; //get the source block if (source_block == NULL) { //does it have data? //no, it doesn't have data. fill destination with zeros diff --git a/AudioEffectDelay_OA_F32.h b/AudioEffectDelay_OA_F32.h index 8dc8f7d..cde6281 100644 --- a/AudioEffectDelay_OA_F32.h +++ b/AudioEffectDelay_OA_F32.h @@ -41,19 +41,19 @@ // Are these too big??? I think the same as I16---half as much? <<<<<<<<<<<<<<<< #if defined(__IMXRT1062__) // 4.00 second maximum on Teensy 4.0 - #define DELAY_QUEUE_SIZE (176512 / AUDIO_BLOCK_SAMPLES) + #define DELAY_QUEUE_SIZE_OA (176512 / AUDIO_BLOCK_SAMPLES) #elif defined(__MK66FX1M0__) // 2.41 second maximum on Teensy 3.6 - #define DELAY_QUEUE_SIZE (106496 / AUDIO_BLOCK_SIZE_F32) + #define DELAY_QUEUE_SIZE_OA (106496 / AUDIO_BLOCK_SIZE_F32) #elif defined(__MK64FX512__) // 1.67 second maximum on Teensy 3.5 - #define DELAY_QUEUE_SIZE (73728 / AUDIO_BLOCK_SIZE_F32) + #define DELAY_QUEUE_SIZE_OA (73728 / AUDIO_BLOCK_SIZE_F32) #elif defined(__MK20DX256__) // 0.45 second maximum on Teensy 3.1 & 3.2 - #define DELAY_QUEUE_SIZE (19826 / AUDIO_BLOCK_SIZE_F32) + #define DELAY_QUEUE_SIZE_OA (19826 / AUDIO_BLOCK_SIZE_F32) #else // 0.14 second maximum on Teensy 3.0 - #define DELAY_QUEUE_SIZE (6144 / AUDIO_BLOCK_SIZE_F32) + #define DELAY_QUEUE_SIZE_OA (6144 / AUDIO_BLOCK_SIZE_F32) #endif class AudioEffectDelay_OA_F32 : public AudioStream_F32 @@ -87,7 +87,7 @@ public: if (channel >= 8) return; if (milliseconds < 0.0) milliseconds = 0.0; uint32_t n = (milliseconds*(sampleRate_Hz/1000.0))+0.5; - uint32_t nmax = AUDIO_BLOCK_SIZE_F32 * (DELAY_QUEUE_SIZE-1); + uint32_t nmax = AUDIO_BLOCK_SIZE_F32 * (DELAY_QUEUE_SIZE_OA-1); if (n > nmax) n = nmax; uint32_t blks = (n + (AUDIO_BLOCK_SIZE_F32-1)) / AUDIO_BLOCK_SIZE_F32 + 1; if (!(activemask & (1< +#include + + +#define USE_F32_IO 1 + +AudioControlSGTL5000 sgtl5000_1; + +#if USE_F32_IO + + +#if USE_CODEC_IN + +#else // Use Sine Input +AudioSynthWaveformSine_F32 sine1; +AudioConvert_F32toI16 float2Int1; //, float2Int2; +AudioOutputI2S i2sOut; +//AudioOutputI2S_OA_F32 i2sOut; + +/* +//Make all of the audio connections +AudioConnection patchCord1(i2s_in, 0, int2Float1, 0); +AudioConnection patchCord2(i2s_in, 1, int2Float2, 0); +AudioConnection_F32 patchCord10(int2Float1, 0, gain1, 0); +AudioConnection_F32 patchCord11(int2Float2, 0, gain2, 0); +* */ + + +AudioConnection_F32 pc1(sine1, 0, float2Int1, 0); + + +AudioConnection_F32 patchCord12(float2Int1, 0, i2sOut, 0); +//AudioConnection_F32 patchCord13(gain2, 0, float2Int2, 0); +//AudioConnection_F32 patchCord20(sine1, 0, i2sOut, 0); +//AudioConnection_F32 patchCord21(sine1, 0, i2sOut, 1); + +//AudioConnection_F32 patchCord12(gain1, 0, i2sOut, 0); +//AudioConnection_F32 patchCord13(gain2, 0, i2sOut, 1); +/*AudioInputI2S i2s_in; +AudioConvert_I16toF32 int2Float1, int2Float2; +AudioEffectGain_F32 gain1, gain2; +* */ +void setup(void) { + Serial.begin(1); delay(1000); + Serial.println("Open Audio Test Input and Output"); + + AudioMemory(10); + AudioMemory_F32(10); + + sgtl5000_1.enable(); + sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN); + + sine1.frequency(300.0); + sine1.amplitude(0.01f); + sine1.begin(); + + +/* + //sgtl5000_1.adcHighPassFilterEnable(); //LOW OUTPUT NO WHINEY NOISE, gain has no effect---Direct path?? + //reduces noise. https://forum.pjrc.com/threads/27215-24-bit-audio-boards?p=78831&viewfull=1#post78831 + sgtl5000_1.adcHighPassFilterDisable(); gain1.setGain_dB(40); // NOISE WITH WHINEY PITCH + + gain2.setGain_dB(40); + */ + + +} + +void loop() { +} diff --git a/examples/TestLoader/TestLoader.ino b/examples/TestLoader/TestLoader.ino new file mode 100644 index 0000000..9befc3d --- /dev/null +++ b/examples/TestLoader/TestLoader.ino @@ -0,0 +1,11 @@ +/* TestLoader.ino Bob Larkin 3 July 2020 + */ + +void setup(void) { + Serial.begin(9600); + while(!Serial) ; + Serial.println("Test Loader"); +} + +void loop() { +} diff --git a/examples/TestLoader2/TestLoader2.ino b/examples/TestLoader2/TestLoader2.ino new file mode 100644 index 0000000..293988d --- /dev/null +++ b/examples/TestLoader2/TestLoader2.ino @@ -0,0 +1,20 @@ +#include + +AudioSynthWaveformSine sine1; +AudioOutputI2S i2sOut; +AudioConnection patchCord1(sine1, 0, i2sOut, 0); +AudioConnection patchCord2(sine1, 0, i2sOut, 1); +AudioControlSGTL5000 sgtl5000_1; + +void setup(void) { + Serial.begin(1); delay(1000); + Serial.println("Teensy Audio, Test Loader"); + + AudioMemory(10); + sgtl5000_1.enable(); + sine1.frequency(300.0); + sine1.amplitude(0.005f); +} + +void loop() { +} diff --git a/examples/TestOutput_float/TestOutput_float.ino b/examples/TestOutput_float/TestOutput_float.ino new file mode 100644 index 0000000..644ca07 --- /dev/null +++ b/examples/TestOutput_float/TestOutput_float.ino @@ -0,0 +1,77 @@ +/* TestOutput_float.ino Bob Larkin 3 July 2020 + * + * Test for #define USE_F32_IO either 0 or 1, all OK on T3.6 + * + */ +#include +#include + +// NOT WORKING for USE_F32_IO 1 <<<<<<<<<<<<<<< +#define ALL_TEENSY_AUDIO 0 +#define USE_F32_IO 1 + +#if ALL_TEENSY_AUDIO + +AudioSynthWaveformSine sine1; +AudioOutputI2S i2sOut; +AudioConnection patchCord1(sine1, 0, i2sOut, 0); +AudioConnection patchCord2(sine1, 0, i2sOut, 1); +AudioControlSGTL5000 sgtl5000_1; + +void setup(void) { + Serial.begin(1); delay(1000); + Serial.println("Teensy Audio, No F32"); + + AudioMemory(10); + sgtl5000_1.enable(); + sine1.frequency(300.0); + sine1.amplitude(0.005f); +} + +void loop() { +} +// ================================================ + +#else // OpenAudio F32 +#if USE_F32_IO +AudioSynthWaveformSine_F32 sine1; +AudioOutputI2S_OA_F32 i2sOut; +AudioConnection_F32 patchCord1(sine1, 0, i2sOut, 0); +AudioConnection_F32 patchCord2(sine1, 0, i2sOut, 1); + +#else // Use F32toI16 convert and I16 out +AudioSynthWaveformSine_F32 sine1; +AudioConvert_F32toI16 float2Int1, float2Int2; +AudioOutputI2S i2sOut; +AudioConnection_F32 patchCord5(sine1, 0, float2Int1, 0); +AudioConnection_F32 patchCord6(sine1, 0, float2Int2, 0); +AudioConnection patchCord7(float2Int1, 0, i2sOut, 0); +AudioConnection patchCord8(float2Int2, 0, i2sOut, 1); +#endif +AudioControlSGTL5000 sgtl5000_1; + +void setup(void) { + Serial.begin(1); delay(1000); +#if USE_F32_IO + Serial.println("Open Audio: Test direct F32 Output"); +#else + Serial.println("Open Audio: Test Convert to Teensy Audio I16 Output"); +#endif + AudioMemory(10); + AudioMemory_F32(10); + +//delay(1); Serial.println("Start i2s_f32 out"); +//i2sOut.begin(); +//delay(1); Serial.println("Start codec"); + + sgtl5000_1.enable(); + + sine1.frequency(300.0); + sine1.amplitude(0.005f); + sine1.begin(); +} + +void loop() { +} + +#endif // ALL_TEENSY_AUDIO diff --git a/memcpy_audio.h b/memcpy_audio.h new file mode 100644 index 0000000..e07b7a2 --- /dev/null +++ b/memcpy_audio.h @@ -0,0 +1,46 @@ +/* Teensyduino Audio Memcpy + * Copyright (c) 2016 Frank Bösing + * + * 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: + * + * 1. The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * 2. If the Software is incorporated into a build system that allows + * selection among a list of target devices, then similar target + * devices manufactured by PJRC.COM must be included in the list of + * target devices and selectable in the same manner. + * + * 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 memcpy_audio_h_ +#define memcpy_audio_h_ + +#ifdef __cplusplus +extern "C" { +#endif +void memcpy_tointerleaveLR(int16_t *dst, const int16_t *srcL, const int16_t *srcR); +void memcpy_tointerleaveL(int16_t *dst, const int16_t *srcL); +void memcpy_tointerleaveR(int16_t *dst, const int16_t *srcR); +void memcpy_tointerleaveQuad(int16_t *dst, const int16_t *src1, const int16_t *src2, + const int16_t *src3, const int16_t *src4); +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/output_i2s_OA_F32.cpp.xxx b/output_i2s_OA_F32.cpp.xxx new file mode 100644 index 0000000..2a240d6 --- /dev/null +++ b/output_i2s_OA_F32.cpp.xxx @@ -0,0 +1,581 @@ +/* output_i2s_OA_f32.cpp + * + * This is Teensy Audio output_i2s.h altered to support OpenAudio float (F32) + * to allow direct output of F32 blocks to the codec. It is the Teensy Audio output + * class with a conversion of float to int16 at the beginning. Bob Larkin + * June 2020 + * + * Additions under MIT license, and all the original is: + * Copyright (c) 2014, 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. + */ + +#include +#include "output_i2s_OA_f32.h" +#include "memcpy_audio.h" + +audio_block_t * AudioOutputI2S_OA_F32::block_left_1st = NULL; +audio_block_t * AudioOutputI2S_OA_F32::block_right_1st = NULL; +audio_block_t * AudioOutputI2S_OA_F32::block_left_2nd = NULL; +audio_block_t * AudioOutputI2S_OA_F32::block_right_2nd = NULL; +uint16_t AudioOutputI2S_OA_F32::block_left_offset = 0; +uint16_t AudioOutputI2S_OA_F32::block_right_offset = 0; +bool AudioOutputI2S_OA_F32::update_responsibility = false; +DMAChannel AudioOutputI2S_OA_F32::dma(false); +DMAMEM __attribute__((aligned(32))) static uint32_t i2s_tx_buffer[AUDIO_BLOCK_SAMPLES]; + +#if defined(__IMXRT1062__) +#include "utility/imxrt_hw.h" +#endif + +void AudioOutputI2S_OA_F32::begin(void) +{ + dma.begin(true); // Allocate the DMA channel first + block_left_1st = NULL; + block_right_1st = NULL; + config_i2s(); + +#if defined(KINETISK) + CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 + + dma.TCD->SADDR = i2s_tx_buffer; + dma.TCD->SOFF = 2; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); + dma.TCD->NBYTES_MLNO = 2; + dma.TCD->SLAST = -sizeof(i2s_tx_buffer); + dma.TCD->DADDR = (void *)((uint32_t)&I2S0_TDR0 + 2); + dma.TCD->DOFF = 0; + dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); + dma.enable(); + + I2S0_TCSR = I2S_TCSR_SR; + I2S0_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; + +#elif defined(__IMXRT1062__) + CORE_PIN7_CONFIG = 3; //1:TX_DATA0 + dma.TCD->SADDR = i2s_tx_buffer; + dma.TCD->SOFF = 2; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); + dma.TCD->NBYTES_MLNO = 2; + dma.TCD->SLAST = -sizeof(i2s_tx_buffer); + dma.TCD->DOFF = 0; + dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); + dma.enable(); + + I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; + I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; +#endif + update_responsibility = update_setup(); + dma.attachInterrupt(isr); +} // end begin() + +void AudioOutputI2S_OA_F32::isr(void) +{ +#if defined(KINETISK) || defined(__IMXRT1062__) + int16_t *dest; + audio_block_t *blockL, *blockR; + uint32_t saddr, offsetL, offsetR; + + saddr = (uint32_t)(dma.TCD->SADDR); + dma.clearInterrupt(); + if (saddr < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2) { + // DMA is transmitting the first half of the buffer + // so we must fill the second half + dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; + if (AudioOutputI2S_OA_F32::update_responsibility) AudioStream_F32::update_all(); + } else { + // DMA is transmitting the second half of the buffer + // so we must fill the first half + dest = (int16_t *)i2s_tx_buffer; + } + + blockL = AudioOutputI2S_OA_F32::block_left_1st; // These 2 are I16* + blockR = AudioOutputI2S_OA_F32::block_right_1st; + offsetL = AudioOutputI2S_OA_F32::block_left_offset; + offsetR = AudioOutputI2S_OA_F32::block_right_offset; + + if (blockL && blockR) { + memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR); + offsetL += AUDIO_BLOCK_SAMPLES / 2; + offsetR += AUDIO_BLOCK_SAMPLES / 2; + } else if (blockL) { + memcpy_tointerleaveL(dest, blockL->data + offsetL); + offsetL += AUDIO_BLOCK_SAMPLES / 2; + } else if (blockR) { + memcpy_tointerleaveR(dest, blockR->data + offsetR); + offsetR += AUDIO_BLOCK_SAMPLES / 2; + } else { + memset(dest,0,AUDIO_BLOCK_SAMPLES * 2); + } + + arm_dcache_flush_delete(dest, sizeof(i2s_tx_buffer) / 2 ); + + if (offsetL < AUDIO_BLOCK_SAMPLES) { + AudioOutputI2S_OA_F32::block_left_offset = offsetL; + } else { + AudioOutputI2S_OA_F32::block_left_offset = 0; + AudioStream::release(blockL); + AudioOutputI2S_OA_F32::block_left_1st = AudioOutputI2S_OA_F32::block_left_2nd; + AudioOutputI2S_OA_F32::block_left_2nd = NULL; + } + if (offsetR < AUDIO_BLOCK_SAMPLES) { + AudioOutputI2S_OA_F32::block_right_offset = offsetR; + } else { + AudioOutputI2S_OA_F32::block_right_offset = 0; + AudioStream::release(blockR); + AudioOutputI2S_OA_F32::block_right_1st = AudioOutputI2S_OA_F32::block_right_2nd; + AudioOutputI2S_OA_F32::block_right_2nd = NULL; + } +#else + // This is T3.x, x<5. Those would not seem to be candidates for F32 audio processing? + const int16_t *src, *end; + int16_t *dest; + audio_block_t *block; + uint32_t saddr, offset; + + saddr = (uint32_t)(dma.CFG->SAR); + dma.clearInterrupt(); + if (saddr < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2) { + // DMA is transmitting the first half of the buffer + // so we must fill the second half + dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; + end = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES]; + if (AudioOutputI2S_OA_F32::update_responsibility) AudioStream::update_all(); + } else { + // DMA is transmitting the second half of the buffer + // so we must fill the first half + dest = (int16_t *)i2s_tx_buffer; + end = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; + } + + block = AudioOutputI2S_OA_F32::block_left_1st; + if (block) { + offset = AudioOutputI2S_OA_F32::block_left_offset; + src = &block->data[offset]; + do { + *dest = *src++; + dest += 2; + } while (dest < end); + offset += AUDIO_BLOCK_SAMPLES/2; + if (offset < AUDIO_BLOCK_SAMPLES) { + AudioOutputI2S_OA_F32::block_left_offset = offset; + } else { + AudioOutputI2S_OA_F32::block_left_offset = 0; + AudioStream::release(block); + AudioOutputI2S_OA_F32::block_left_1st = AudioOutputI2S_OA_F32::block_left_2nd; + AudioOutputI2S_OA_F32::block_left_2nd = NULL; + } + } else { + do { + *dest = 0; + dest += 2; + } while (dest < end); + } + dest -= AUDIO_BLOCK_SAMPLES - 1; + block = AudioOutputI2S_OA_F32::block_right_1st; + if (block) { + offset = AudioOutputI2S_OA_F32::block_right_offset; + src = &block->data[offset]; + do { + *dest = *src++; + dest += 2; + } while (dest < end); + offset += AUDIO_BLOCK_SAMPLES/2; + if (offset < AUDIO_BLOCK_SAMPLES) { + AudioOutputI2S_OA_F32::block_right_offset = offset; + } else { + AudioOutputI2S_OA_F32::block_right_offset = 0; + AudioStream::release(block); + AudioOutputI2S_OA_F32::block_right_1st = AudioOutputI2S_OA_F32::block_right_2nd; + AudioOutputI2S_OA_F32::block_right_2nd = NULL; + } + } else { + do { + *dest = 0; + dest += 2; + } while (dest < end); + } +#endif +} + +void AudioOutputI2S_OA_F32::update(void) +{ + audio_block_f32_t *blockF32; + audio_block_t *blockI16; + + blockF32 = AudioStream_F32::receiveReadOnly_f32(0); // input 0 = left + blockI16 = AudioStream::allocate(); + if (blockF32) { + // Change F32 to I16 + for(int i=0; i<128; i++) { + blockI16->data[i] = (int16_t)(32768.0f*blockF32->data[i]); + } + AudioStream_F32::release(blockF32); // End of F32 activity + + // From here down to end of left channel is the same as for I16 Teensy Audio + // Now I16, so sort out the DMA data + __disable_irq(); + if (block_left_1st == NULL) { + block_left_1st = blockI16; + block_left_offset = 0; + __enable_irq(); + } else if (block_left_2nd == NULL) { + block_left_2nd = blockI16; + __enable_irq(); + } else { + audio_block_t *tmp = block_left_1st; + block_left_1st = block_left_2nd; + block_left_2nd = blockI16; + block_left_offset = 0; + __enable_irq(); + AudioStream::release(tmp); + } + } + + blockF32 = AudioStream_F32::receiveReadOnly_f32(1); // input 1 = right + if (blockF32) { + for(int i=0; i<128; i++) { + blockI16->data[i] = (int16_t)(32768.0f*blockF32->data[i]); + } + AudioStream_F32::release(blockF32); // Second end of F32 activity + + __disable_irq(); + if (block_right_1st == NULL) { + block_right_1st = blockI16; + block_right_offset = 0; + __enable_irq(); + } else if (block_right_2nd == NULL) { + block_right_2nd = blockI16; + __enable_irq(); + } else { + audio_block_t *tmp = block_right_1st; + block_right_1st = block_right_2nd; + block_right_2nd = blockI16; + block_right_offset = 0; + __enable_irq(); + AudioStream::release(tmp); + } + } + AudioStream::release(blockI16); +} // end update() + +#if defined(KINETISK) || defined(KINETISL) +// MCLK needs to be 48e6 / 1088 * 256 = 11.29411765 MHz -> 44.117647 kHz sample rate +// +#if F_CPU == 96000000 || F_CPU == 48000000 || F_CPU == 24000000 + // PLL is at 96 MHz in these modes + #define MCLK_MULT 2 + #define MCLK_DIV 17 +#elif F_CPU == 72000000 + #define MCLK_MULT 8 + #define MCLK_DIV 51 +#elif F_CPU == 120000000 + #define MCLK_MULT 8 + #define MCLK_DIV 85 +#elif F_CPU == 144000000 + #define MCLK_MULT 4 + #define MCLK_DIV 51 +#elif F_CPU == 168000000 + #define MCLK_MULT 8 + #define MCLK_DIV 119 +#elif F_CPU == 180000000 + #define MCLK_MULT 16 + #define MCLK_DIV 255 + #define MCLK_SRC 0 +#elif F_CPU == 192000000 + #define MCLK_MULT 1 + #define MCLK_DIV 17 +#elif F_CPU == 216000000 + #define MCLK_MULT 12 + #define MCLK_DIV 17 + #define MCLK_SRC 1 +#elif F_CPU == 240000000 + #define MCLK_MULT 2 + #define MCLK_DIV 85 + #define MCLK_SRC 0 +#elif F_CPU == 256000000 + #define MCLK_MULT 12 + #define MCLK_DIV 17 + #define MCLK_SRC 1 +#elif F_CPU == 16000000 + #define MCLK_MULT 12 + #define MCLK_DIV 17 +#else + #error "This CPU Clock Speed is not supported by the Audio library"; +#endif + +#ifndef MCLK_SRC +#if F_CPU >= 20000000 + #define MCLK_SRC 3 // the PLL +#else + #define MCLK_SRC 0 // system clock +#endif +#endif +#endif + +void AudioOutputI2S_OA_F32::config_i2s(void) +{ +#if defined(KINETISK) || defined(KINETISL) + SIM_SCGC6 |= SIM_SCGC6_I2S; + SIM_SCGC7 |= SIM_SCGC7_DMA; + SIM_SCGC6 |= SIM_SCGC6_DMAMUX; + + // if either transmitter or receiver is enabled, do nothing + if (I2S0_TCSR & I2S_TCSR_TE) return; + if (I2S0_RCSR & I2S_RCSR_RE) return; + + // enable MCLK output + I2S0_MCR = I2S_MCR_MICS(MCLK_SRC) | I2S_MCR_MOE; + while (I2S0_MCR & I2S_MCR_DUF) ; + I2S0_MDR = I2S_MDR_FRACT((MCLK_MULT-1)) | I2S_MDR_DIVIDE((MCLK_DIV-1)); + + // configure transmitter + I2S0_TMR = 0; + I2S0_TCR1 = I2S_TCR1_TFW(1); // watermark at half fifo size + I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1) + | I2S_TCR2_BCD | I2S_TCR2_DIV(1); + I2S0_TCR3 = I2S_TCR3_TCE; + I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF + | I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_TCR4_FSD; + I2S0_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); + + // configure receiver (sync'd to transmitter clocks) + I2S0_RMR = 0; + I2S0_RCR1 = I2S_RCR1_RFW(1); + I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1) + | I2S_RCR2_BCD | I2S_RCR2_DIV(1); + I2S0_RCR3 = I2S_RCR3_RCE; + I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; + I2S0_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); + + // configure pin mux for 3 clock signals + CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK) + CORE_PIN9_CONFIG = PORT_PCR_MUX(6); // pin 9, PTC3, I2S0_TX_BCLK + CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK + +#elif defined(__IMXRT1062__) + + CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON); + + // if either transmitter or receiver is enabled, do nothing + if (I2S1_TCSR & I2S_TCSR_TE) return; + if (I2S1_RCSR & I2S_RCSR_RE) return; +//PLL: + int fs = AUDIO_SAMPLE_RATE_EXACT; + // PLL between 27*24 = 648MHz und 54*24=1296MHz + 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); + + // clear SAI1_CLK register locations + CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI1_CLK_SEL_MASK)) + | CCM_CSCMR1_SAI1_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4 + CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK)) + | CCM_CS1CDR_SAI1_CLK_PRED(n1-1) // &0x07 + | CCM_CS1CDR_SAI1_CLK_PODF(n2-1); // &0x3f + + // Select MCLK + IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 + & ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK)) + | (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0)); + + CORE_PIN23_CONFIG = 3; //1:MCLK + CORE_PIN21_CONFIG = 3; //1:RX_BCLK + CORE_PIN20_CONFIG = 3; //1:RX_SYNC + + int rsync = 0; + int tsync = 1; + + I2S1_TMR = 0; + //I2S1_TCSR = (1<<25); //Reset + I2S1_TCR1 = I2S_TCR1_RFW(1); + I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP // sync=0; tx is async; + | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1)); + I2S1_TCR3 = I2S_TCR3_TCE; + I2S1_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((32-1)) | I2S_TCR4_MF + | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP; + I2S1_TCR5 = I2S_TCR5_WNW((32-1)) | I2S_TCR5_W0W((32-1)) | I2S_TCR5_FBT((32-1)); + + I2S1_RMR = 0; + //I2S1_RCSR = (1<<25); //Reset + I2S1_RCR1 = I2S_RCR1_RFW(1); + I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP // sync=0; rx is async; + | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1)); + I2S1_RCR3 = I2S_RCR3_RCE; + I2S1_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((32-1)) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; + I2S1_RCR5 = I2S_RCR5_WNW((32-1)) | I2S_RCR5_W0W((32-1)) | I2S_RCR5_FBT((32-1)); + +#endif +} + + +/******************************************************************/ + +void AudioOutputI2Sslave_OA_F32::begin(void) +{ + + dma.begin(true); // Allocate the DMA channel first + + block_left_1st = NULL; + block_right_1st = NULL; + + AudioOutputI2Sslave_OA_F32::config_i2s(); + +#if defined(KINETISK) + CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 + dma.TCD->SADDR = i2s_tx_buffer; + dma.TCD->SOFF = 2; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); + dma.TCD->NBYTES_MLNO = 2; + dma.TCD->SLAST = -sizeof(i2s_tx_buffer); + dma.TCD->DADDR = (void *)((uint32_t)&I2S0_TDR0 + 2); + dma.TCD->DOFF = 0; + dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); + dma.enable(); + + I2S0_TCSR = I2S_TCSR_SR; + I2S0_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; + +#elif defined(__IMXRT1062__) + CORE_PIN7_CONFIG = 3; //1:TX_DATA0 + dma.TCD->SADDR = i2s_tx_buffer; + dma.TCD->SOFF = 2; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); + dma.TCD->NBYTES_MLNO = 2; + dma.TCD->SLAST = -sizeof(i2s_tx_buffer); + dma.TCD->DOFF = 0; + dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); + dma.enable(); + + I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; + I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; +#endif + + update_responsibility = update_setup(); + dma.attachInterrupt(isr); +} + +void AudioOutputI2Sslave_OA_F32::config_i2s(void) +{ +#if defined(KINETISK) + SIM_SCGC6 |= SIM_SCGC6_I2S; + SIM_SCGC7 |= SIM_SCGC7_DMA; + SIM_SCGC6 |= SIM_SCGC6_DMAMUX; + + // if either transmitter or receiver is enabled, do nothing + if (I2S0_TCSR & I2S_TCSR_TE) return; + if (I2S0_RCSR & I2S_RCSR_RE) return; + + // Select input clock 0 + // Configure to input the bit-clock from pin, bypasses the MCLK divider + I2S0_MCR = I2S_MCR_MICS(0); + I2S0_MDR = 0; + + // configure transmitter + I2S0_TMR = 0; + I2S0_TCR1 = I2S_TCR1_TFW(1); // watermark at half fifo size + I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP; + + I2S0_TCR3 = I2S_TCR3_TCE; + I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF + | I2S_TCR4_FSE | I2S_TCR4_FSP; + + I2S0_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); + + // configure receiver (sync'd to transmitter clocks) + I2S0_RMR = 0; + I2S0_RCR1 = I2S_RCR1_RFW(1); + I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP; + + I2S0_RCR3 = I2S_RCR3_RCE; + I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; + + I2S0_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); + + // configure pin mux for 3 clock signals + CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK) + CORE_PIN9_CONFIG = PORT_PCR_MUX(6); // pin 9, PTC3, I2S0_TX_BCLK + CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK + +#elif defined(__IMXRT1062__) + + CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON); + + // if either transmitter or receiver is enabled, do nothing + if (I2S1_TCSR & I2S_TCSR_TE) return; + if (I2S1_RCSR & I2S_RCSR_RE) return; + + // not using MCLK in slave mode - hope that's ok? + //CORE_PIN23_CONFIG = 3; // AD_B1_09 ALT3=SAI1_MCLK + CORE_PIN21_CONFIG = 3; // AD_B1_11 ALT3=SAI1_RX_BCLK + CORE_PIN20_CONFIG = 3; // AD_B1_10 ALT3=SAI1_RX_SYNC + IOMUXC_SAI1_RX_BCLK_SELECT_INPUT = 1; // 1=GPIO_AD_B1_11_ALT3, page 868 + IOMUXC_SAI1_RX_SYNC_SELECT_INPUT = 1; // 1=GPIO_AD_B1_10_ALT3, page 872 + + // configure transmitter + I2S1_TMR = 0; + I2S1_TCR1 = I2S_TCR1_RFW(1); // watermark at half fifo size + I2S1_TCR2 = I2S_TCR2_SYNC(1) | I2S_TCR2_BCP; + I2S1_TCR3 = I2S_TCR3_TCE; + I2S1_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF + | I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_RCR4_FSD; + I2S1_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); + + // configure receiver + I2S1_RMR = 0; + I2S1_RCR1 = I2S_RCR1_RFW(1); + I2S1_RCR2 = I2S_RCR2_SYNC(0) | I2S_TCR2_BCP; + I2S1_RCR3 = I2S_RCR3_RCE; + I2S1_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSP; + I2S1_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); + +#endif +} diff --git a/output_i2s_OA_f32.h.xxx b/output_i2s_OA_f32.h.xxx new file mode 100644 index 0000000..8cf3cb9 --- /dev/null +++ b/output_i2s_OA_f32.h.xxx @@ -0,0 +1,111 @@ +/* output_i2s_OA_f32.h + * + * This is Teensy Audio output_i2s.h altered to support OpenAudio float (F32) + * to allow direct output of F32 blocks to the codec. It is the Teensy Audio output + * class with a conversion of float to int16 at the beginning. Bob Larkin + * June 2020 + * + * This is basic: 128 word blocks, 16-bit integer word to the codec. It + * needs to be revisited for variable word block. + * + * Tested: Using TestOutput_float.ino, Runs T3.6 and T4.0 w/o error. + * + * Additions under MIT license, and all the original is: + * Copyright (c) 2014, 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. + */ + +#ifndef output_i2s_OA_F32_h_ +#define output_i2s_OA_F32_h_ + +#include "Arduino.h" +#include "AudioStream.h" +#include "AudioStream_F32.h" +#include "DMAChannel.h" + +class AudioOutputI2S_OA_F32 : public AudioStream_F32 +{ +//GUI: inputs:2, outputs:0 //this line used for automatic generation of GUI node +public: + AudioOutputI2S_OA_F32(void) : AudioStream_F32(2, inputQueueArray) { + // Add 2 delays as experimental fix for possible Teensy Loader issue. + uint32_t ii; + uint32_t xx = 0; + for (ii=0; ii<1000; ii++) xx += ii; + begin(); + for (ii=0; ii<1000; ii++) xx += ii; + } + +/* This long form needs to be added <<<<<<<<<<<<<<<< + AudioOutputI2S_OA_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray) + { + sample_rate_Hz = settings.sample_rate_Hz; + audio_block_samples = settings.audio_block_samples; + begin(); + } + */ + + virtual void update(void); + void begin(void); + friend class AudioInputI2S_OA_F32; + +/* These are 4, 6, and 8 channel options in I16 T4, not yet supported in F32 +#if defined(__IMXRT1062__) + friend class AudioOutputI2SQuad; + friend class AudioInputI2SQuad; + friend class AudioOutputI2SHex; + friend class AudioInputI2SHex; + friend class AudioOutputI2SOct; + friend class AudioInputI2SOct; +#endif + */ +protected: + // Next fcn to be used only inside AudioOutputI2S_OA_F32slave !! + AudioOutputI2S_OA_F32(int dummy): AudioStream_F32(2, inputQueueArray) {} + static void config_i2s(void); + static audio_block_t *block_left_1st; + static audio_block_t *block_right_1st; + static bool update_responsibility; + static DMAChannel dma; + static void isr(void); +private: + static audio_block_t *block_left_2nd; + static audio_block_t *block_right_2nd; + static uint16_t block_left_offset; + static uint16_t block_right_offset; + audio_block_f32_t *inputQueueArray[2]; +}; + +class AudioOutputI2Sslave_OA_F32 : public AudioOutputI2S_OA_F32 +{ +public: + AudioOutputI2Sslave_OA_F32(void) : AudioOutputI2S_OA_F32(0) { begin(); } ; + void begin(void); + friend class AudioInputI2Sslave_OA_F32; + friend void dma_ch0_isr(void); +protected: + static void config_i2s(void); +}; + +#endif diff --git a/output_i2s_f32.cpp.xxx b/output_i2s_f32.cpp.xxx index da57e6b..b052022 100644 --- a/output_i2s_f32.cpp.xxx +++ b/output_i2s_f32.cpp.xxx @@ -1,4 +1,9 @@ -/* Audio Library for Teensy 3.X +/* output_i2s_f32.h - Input block of float samples from I2S + * + * Adapted to F32 output and Open Audio AudioSettings_F32 by Chip Audette + * Modified for Teensy 4.x Bob Larkin June 2020 + * + * Direct from: Audio Library for Teensy 3.X * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com * * Development of this audio library was funded by PJRC.COM, LLC by sales of @@ -23,6 +28,765 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +/* Extended by Chip Audette, OpenAudio, May 2019 + * Converted to F32 and to variable audio block length + * The F32 conversion is under the MIT License. Use at your own risk. + */ + +#include "output_i2s_f32.h" +//#include "input_i2s_f32.h" +//include "memcpy_audio.h" +//#include "memcpy_interleave.h" +#include + +//////////// +// +// Changing the sample rate based on changing the I2S bus freuqency +// +//Here's the function to change the sample rate of the system (via changing the clocking of the I2S bus) +//https://forum.pjrc.com/threads/38753-Discussion-about-a-simple-way-to-change-the-sample-rate?p=121365&viewfull=1#post121365 +// +//And, a post on how to compute the frac and div portions? I haven't checked the code presented in this post: +//https://forum.pjrc.com/threads/38753-Discussion-about-a-simple-way-to-change-the-sample-rate?p=188812&viewfull=1#post188812 +// +//Finally, here is my own Matlab code for computing the mult and div values... +/* + %choose the sample rates that you are hoping to hit + targ_fs_Hz = [2000, 8000, 11025, 16000, 22050, 24000, 32000, 44100, floor(44117.64706) , ... + 48000, 88200, floor(44117.64706 * 2), (37000/256*662), 96000, 176400, floor(44117.64706 * 4), 192000]; + F_PLL = 180e6; %choose the clock rate used for this calculation + PLL_div = 256; + all_n=[];all_d=[]; + for Itarg=1:length(targ_fs_Hz) + if (0) + [best_d,best_n]=rat((F_PLL/PLL_div)/targ_fs_Hz(Itarg)); + else + best_n = 1; best_d = 1; best_err = 1e10; + for n=1:255 + d = [1:4095]; + act_fs_Hz = F_PLL / PLL_div * n ./ d; + [err,I] = min(abs(act_fs_Hz - targ_fs_Hz(Itarg))); + if err < best_err + best_n = n; best_d = d(I); + best_err = err; + end + end + end + all_n(Itarg) = best_n; + all_d(Itarg) = best_d; + disp(['fs = ' num2str(targ_fs_Hz(Itarg)) ', n = ' num2str(best_n) ', d = ' num2str(best_d) ', true = ' num2str(F_PLL/PLL_div * best_n / best_d)]) + end +*/ +float AudioOutputI2S_F32::setI2SFreq(const float freq_Hz) { + //***T4X*** no rate change yet for T4 +#if defined(KINETISK) + int freq = (int)(freq_Hz+0.5); + typedef struct { + uint8_t mult; + uint16_t div; + } __attribute__((__packed__)) tmclk; + + const int numfreqs = 17; + const int samplefreqs[numfreqs] = { 2000, 8000, 11025, 16000, 22050, 24000, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)(44117.64706 * 2), (int)(95679.69+0.5), 96000, 176400, (int)(44117.64706 * 4), 192000}; + +#if (F_PLL==16000000) + const tmclk clkArr[numfreqs] = {{4, 125}, {16, 125}, {148, 839}, {32, 125}, {145, 411}, {48, 125}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {124,81}, {192, 125}, {127, 45}, {48, 17}, {255, 83} }; +#elif (F_PLL==72000000) + const tmclk clkArr[numfreqs] = {{832, 1125}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {32, 375}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {248,729}, {128, 375}, {249, 397}, {32, 51}, {185, 271} }; +#elif (F_PLL==96000000) + const tmclk clkArr[numfreqs] = {{2, 375},{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {8, 125}, {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {62,243},{32, 125}, {151, 321}, {8, 17}, {64, 125} }; +#elif (F_PLL==120000000) + const tmclk clkArr[numfreqs] = {{8, 1875},{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {32, 625}, {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {119,583}, {128, 625}, {178, 473}, {32, 85}, {145, 354} }; +#elif (F_PLL==144000000) + const tmclk clkArr[numfreqs] = {{4, 1125},{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {16, 375}, {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {157,923}, {64, 375}, {196, 625}, {16, 51}, {128, 375} }; +#elif (F_PLL==180000000) + const tmclk clkArr[numfreqs] = {{9, 3164}, {46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {64, 1875}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {238,1749}, {219, 1604}, {214, 853}, {64, 255}, {219, 802} }; +#elif (F_PLL==192000000) + const tmclk clkArr[numfreqs] = {{1, 375}, {4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {4, 125}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {31,243}, {16, 125}, {147, 625}, {4, 17}, {32, 125} }; +#elif (F_PLL==216000000) + const tmclk clkArr[numfreqs] = {{8, 3375}, {32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {32, 1125}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125}, {196, 1875}, {16, 153}, {248,2187}, {128, 1125}, {226, 1081}, {32, 153}, {147, 646} }; +#elif (F_PLL==240000000) + const tmclk clkArr[numfreqs] = {{4, 1875}, {16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {16, 625}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625}, {205, 2179}, {8, 85}, {119,1166}, {64, 625}, {89, 473}, {16, 85}, {128, 625} }; +#endif + + for (int f = 0; f < numfreqs; f++) { + if ( freq == samplefreqs[f] ) { + while (I2S0_MCR & I2S_MCR_DUF) ; + I2S0_MDR = I2S_MDR_FRACT((clkArr[f].mult - 1)) | I2S_MDR_DIVIDE((clkArr[f].div - 1)); + return (float)(F_PLL / 256 * clkArr[f].mult / clkArr[f].div); + } + } + return 0.0f; +#elif defined(__IMXRT1062__) + // Needs some meat.....otherwise just 44100 +#endif +} + + +audio_block_f32_t * AudioOutputI2S_F32::block_left_1st = NULL; +audio_block_f32_t * AudioOutputI2S_F32::block_right_1st = NULL; +audio_block_f32_t * AudioOutputI2S_F32::block_left_2nd = NULL; +audio_block_f32_t * AudioOutputI2S_F32::block_right_2nd = NULL; +uint16_t AudioOutputI2S_F32::block_left_offset = 0; +uint16_t AudioOutputI2S_F32::block_right_offset = 0; +bool AudioOutputI2S_F32::update_responsibility = false; +//DMAMEM static uint32_t i2s_tx_buffer[AUDIO_BLOCK_SAMPLES]; //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" +DMAChannel AudioOutputI2S_F32::dma(false); + +float AudioOutputI2S_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; +int AudioOutputI2S_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; + +//#for 16-bit transfers +//#define I2S_BUFFER_TO_USE_BYTES (AudioOutputI2S_F32::audio_block_samples*sizeof(i2s_tx_buffer[0])) + +//#for 32-bit transfers +#define I2S_BUFFER_TO_USE_BYTES (AudioOutputI2S_F32::audio_block_samples*2*sizeof(i2s_tx_buffer[0])) + +//***T4X*** +#if defined(__IMXRT1062__) +#include "utility/imxrt_hw.h" +#endif + + +void AudioOutputI2S_F32::begin(void) +{ + bool transferUsing32bit = true; + begin(transferUsing32bit); +} +void AudioOutputI2S_F32::begin(bool transferUsing32bit) { + + dma.begin(true); // Allocate the DMA channel first + + block_left_1st = NULL; + block_right_1st = NULL; + + // TODO: should we set & clear the I2S_TCSR_SR bit here? + config_i2s(transferUsing32bit); + + CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 + + //setup DMA parameters + //if (transferUsing32bit) { + sub_begin_i32(); + //} else { + // sub_begin_i16(); + //} + + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); + update_responsibility = update_setup(); + dma.enable(); + + I2S0_TCSR = I2S_TCSR_SR; + I2S0_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; + dma.attachInterrupt(isr_32); + + // change the I2S frequencies to make the requested sample rate + setI2SFreq(AudioOutputI2S_F32::sample_rate_Hz); + + enabled = 1; + + //AudioInputI2S_F32::begin_guts(); +} + +void AudioOutputI2S_F32::sub_begin_i16(void) { + dma.TCD->SADDR = i2s_tx_buffer; + dma.TCD->SOFF = 2; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); + dma.TCD->NBYTES_MLNO = 2; + //dma.TCD->SLAST = -sizeof(i2s_tx_buffer); //original + dma.TCD->SLAST = -I2S_BUFFER_TO_USE_BYTES; + dma.TCD->DADDR = &I2S0_TDR0; + dma.TCD->DOFF = 0; + //dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; //original + dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 2; + dma.TCD->DLASTSGA = 0; + //dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; //original + dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 2; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; +} + +void AudioOutputI2S_F32::sub_begin_i32(void) { + dma.TCD->SADDR = i2s_tx_buffer; //here's where to get the data from + + //let's assume that we'll transfer each sample (left or right) independently. So 4-byte (32bit) transfers. + dma.TCD->SOFF = 4; //step forward pointer for source data by 4 bytes (ie, 32 bits) after each read + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(DMA_TCD_ATTR_SIZE_32BIT) | DMA_TCD_ATTR_DSIZE(DMA_TCD_ATTR_SIZE_32BIT); //each read is 32 bits + dma.TCD->NBYTES_MLNO = 4; //how many bytes to send per minor loop. Do each sample (left or right) independently. So, 4 bytes? Should be 4 or 8? + + //dma.TCD->SLAST = -sizeof(i2s_tx_buffer); //original + dma.TCD->SLAST = -I2S_BUFFER_TO_USE_BYTES; //jump back to beginning of source data when hit the end + dma.TCD->DADDR = &I2S0_TDR0; //destination of DMA transfers + dma.TCD->DOFF = 0; //do not increment the destination pointer + //dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; //original + dma.TCD->CITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4; //number of minor loops in a major loop. I2S_BUFFER_TO_USE_BYTES/NBYTES_MLNO? Should be 4 or 8? + dma.TCD->DLASTSGA = 0; + //dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; //original + dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 4; //number of minor loops in a major loop. I2S_BUFFER_TO_USE_BYTES/NBYTES_MLNO? should be 4 or 8? + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; +} + + +/* void AudioOutputI2S_F32::isr_16(void) +{ +#if defined(KINETISK) + int16_t *dest; + audio_block_t *blockL, *blockR; + uint32_t saddr, offsetL, offsetR; + saddr = (uint32_t)(dma.TCD->SADDR); + dma.clearInterrupt(); + //if (saddr < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2) { //original + if (saddr < (uint32_t)i2s_tx_buffer + I2S_BUFFER_TO_USE_BYTES / 2) { + // DMA is transmitting the first half of the buffer + // so we must fill the second half + //dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original + dest = (int16_t *)&i2s_tx_buffer[audio_block_samples/2]; + if (AudioOutputI2S_F32::update_responsibility) AudioStream_F32::update_all(); + } else { + // DMA is transmitting the second half of the buffer + // so we must fill the first half + dest = (int16_t *)i2s_tx_buffer; + } + blockL = AudioOutputI2S_F32::block_left_1st; + blockR = AudioOutputI2S_F32::block_right_1st; + offsetL = AudioOutputI2S_F32::block_left_offset; + offsetR = AudioOutputI2S_F32::block_right_offset; + + int16_t *d = dest; + if (blockL && blockR) { + //memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR); + //memcpy_tointerleaveLRwLen(dest, blockL->data + offsetL, blockR->data + offsetR, audio_block_samples/2); + int16_t *pL = blockL->data + offsetL; + int16_t *pR = blockR->data + offsetR; + for (int i=0; i < audio_block_samples/2; i++) { *d++ = *pL++; *d++ = *pR++; } //interleave + offsetL += audio_block_samples / 2; + offsetR += audio_block_samples / 2; + } else if (blockL) { + //memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR); + int16_t *pL = blockL->data + offsetL; + for (int i=0; i < audio_block_samples / 2 * 2; i+=2) { *(d+i) = *pL++; } //interleave + offsetL += audio_block_samples / 2; + } else if (blockR) { + int16_t *pR = blockR->data + offsetR; + for (int i=0; i < audio_block_samples /2 * 2; i+=2) { *(d+i) = *pR++; } //interleave + offsetR += audio_block_samples / 2; + } else { + //memset(dest,0,AUDIO_BLOCK_SAMPLES * 2); + memset(dest,0,audio_block_samples * 2); + return; + } + //if (offsetL < AUDIO_BLOCK_SAMPLES) { //original + if (offsetL < (uint16_t)audio_block_samples) { + AudioOutputI2S_F32::block_left_offset = offsetL; + } else { + AudioOutputI2S_F32::block_left_offset = 0; + AudioStream::release(blockL); + AudioOutputI2S_F32::block_left_1st = AudioOutputI2S_F32::block_left_2nd; + AudioOutputI2S_F32::block_left_2nd = NULL; + } + //if (offsetR < AUDIO_BLOCK_SAMPLES) { + if (offsetR < (uint16_t)audio_block_samples) { + AudioOutputI2S_F32::block_right_offset = offsetR; + } else { + AudioOutputI2S_F32::block_right_offset = 0; + AudioStream::release(blockR); + AudioOutputI2S_F32::block_right_1st = AudioOutputI2S_F32::block_right_2nd; + AudioOutputI2S_F32::block_right_2nd = NULL; + } +#else + const int16_t *src, *end; + int16_t *dest; + audio_block_t *block; + uint32_t saddr, offset; + saddr = (uint32_t)(dma.CFG->SAR); + dma.clearInterrupt(); + if (saddr < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2) { + // DMA is transmitting the first half of the buffer + // so we must fill the second half + dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; + end = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES]; + if (AudioOutputI2S_F32::update_responsibility) AudioStream_F32::update_all(); + } else { + // DMA is transmitting the second half of the buffer + // so we must fill the first half + dest = (int16_t *)i2s_tx_buffer; + end = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; + } + block = AudioOutputI2S_F32::block_left_1st; + if (block) { + offset = AudioOutputI2S_F32::block_left_offset; + src = &block->data[offset]; + do { + *dest = *src++; + dest += 2; + } while (dest < end); + offset += AUDIO_BLOCK_SAMPLES/2; + if (offset < AUDIO_BLOCK_SAMPLES) { + AudioOutputI2S_F32::block_left_offset = offset; + } else { + AudioOutputI2S_F32::block_left_offset = 0; + AudioStream::release(block); + AudioOutputI2S_F32::block_left_1st = AudioOutputI2S_F32::block_left_2nd; + AudioOutputI2S_F32::block_left_2nd = NULL; + } + } else { + do { + *dest = 0; + dest += 2; + } while (dest < end); + } + dest -= AUDIO_BLOCK_SAMPLES - 1; + block = AudioOutputI2S_F32::block_right_1st; + if (block) { + offset = AudioOutputI2S_F32::block_right_offset; + src = &block->data[offset]; + do { + *dest = *src++; + dest += 2; + } while (dest < end); + offset += AUDIO_BLOCK_SAMPLES/2; + if (offset < AUDIO_BLOCK_SAMPLES) { + AudioOutputI2S_F32::block_right_offset = offset; + } else { + AudioOutputI2S_F32::block_right_offset = 0; + AudioStream::release(block); + AudioOutputI2S_F32::block_right_1st = AudioOutputI2S_F32::block_right_2nd; + AudioOutputI2S_F32::block_right_2nd = NULL; + } + } else { + do { + *dest = 0; + dest += 2; + } while (dest < end); + } +#endif +} */ + +void AudioOutputI2S_F32::isr_32(void) //should be called every half of an audio block +{ + int32_t *dest; //int32 is the data type being sent to the audio codec + audio_block_f32_t *blockL, *blockR; + uint32_t saddr; + uint32_t offsetL, offsetR; + + saddr = (uint32_t)(dma.TCD->SADDR); + dma.clearInterrupt(); + //if (saddr < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2) { //original 16-bit + if (saddr < (uint32_t)i2s_tx_buffer + I2S_BUFFER_TO_USE_BYTES / 2) { //are we transmitting the first half or second half of the buffer? + // DMA is transmitting the first half of the buffer + // so we must fill the second half + //dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original, half-way through buffer (buffer is 32-bit elements filled with 16-bit stereo samples) + dest = (int32_t *)&i2s_tx_buffer[2*(audio_block_samples/2)]; //half-way through the buffer..remember, buffer is 32-bit elements filled with 32-bit stereo samples) + if (AudioOutputI2S_F32::update_responsibility) AudioStream_F32::update_all(); + } else { + // DMA is transmitting the second half of the buffer so we must fill the first half + dest = (int32_t *)i2s_tx_buffer; //beginning of the buffer + } + + blockL = AudioOutputI2S_F32::block_left_1st; + blockR = AudioOutputI2S_F32::block_right_1st; + offsetL = AudioOutputI2S_F32::block_left_offset; + offsetR = AudioOutputI2S_F32::block_right_offset; + + + int32_t *d = dest; + if (blockL && blockR) { + //memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR); + //memcpy_tointerleaveLRwLen(dest, blockL->data + offsetL, blockR->data + offsetR, audio_block_samples/2); + float32_t *pL = blockL->data + offsetL; + float32_t *pR = blockR->data + offsetR; + for (int i=0; i < audio_block_samples/2; i++) { //loop over half of the audio block (this routine gets called every half an audio block) + *d++ = (int32_t) (*pL++); + *d++ = (int32_t) (*pR++); //cast and interleave + } + offsetL += (audio_block_samples / 2); + offsetR += (audio_block_samples / 2); + } else if (blockL) { + //memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR); + float32_t *pL = blockL->data + offsetL; + for (int i=0; i < audio_block_samples /2; i++) { + *d++ = (int32_t) *pL++; //cast and interleave + *d++ = 0; + } + offsetL += (audio_block_samples / 2); + } else if (blockR) { + float32_t *pR = blockR->data + offsetR; + for (int i=0; i < audio_block_samples /2; i++) { + *d++ = 0; + *d++ = (int32_t) *pR++; //cast and interleave + } + offsetR += (audio_block_samples / 2); + } else { + //memset(dest,0,AUDIO_BLOCK_SAMPLES * 2); //half buffer (AUDIO_BLOCK_SAMPLES/2), 16-bits per sample (AUDIO_BLOCK_SAMPLES/2*2), stereo (AUDIO_BLOCK_SAMPLES/2*2*2) + //memset(dest,0,audio_block_samples * 2 * 4 / 2);//half buffer (AUDIO_BLOCK_SAMPLES/2), 32-bits per sample (AUDIO_BLOCK_SAMPLES/2*4), stereo (AUDIO_BLOCK_SAMPLES/2*4*2) + for (int i=0; i < audio_block_samples/2; i++) { //loop over half of the audio block (this routine gets called every half an audio block) + *d++ = (int32_t) 0; + *d++ = (int32_t) 0; + //*d++ = (int32_t) (-200000000L); + } + return; + } + + //if (offsetL < AUDIO_BLOCK_SAMPLES) { //original + if (offsetL < (uint16_t)audio_block_samples) { + AudioOutputI2S_F32::block_left_offset = offsetL; + } else { + AudioOutputI2S_F32::block_left_offset = 0; + AudioStream_F32::release(blockL); + AudioOutputI2S_F32::block_left_1st = AudioOutputI2S_F32::block_left_2nd; + AudioOutputI2S_F32::block_left_2nd = NULL; + } + //if (offsetR < AUDIO_BLOCK_SAMPLES) { + if (offsetR < (uint16_t)audio_block_samples) { + AudioOutputI2S_F32::block_right_offset = offsetR; + } else { + AudioOutputI2S_F32::block_right_offset = 0; + AudioStream_F32::release(blockR); + AudioOutputI2S_F32::block_right_1st = AudioOutputI2S_F32::block_right_2nd; + AudioOutputI2S_F32::block_right_2nd = NULL; + } + +} + +void AudioOutputI2S_F32::scale_f32_to_i16(float32_t *p_f32, float32_t *p_i16, int len) { + for (int i=0; ilength != audio_block_samples) { + Serial.print("AudioOutputI2S_F32: *** WARNING ***: audio_block says len = "); + Serial.print(block_f32->length); + Serial.print(", but I2S settings want it to be = "); + Serial.println(audio_block_samples); + } + //Serial.print("AudioOutputI2S_F32: audio_block_samples = "); + //Serial.println(audio_block_samples); + + //scale F32 to Int32 + //block_f32_scaled = AudioStream_F32::allocate_f32(); + scale_f32_to_i32(block_f32->data, block_f32_scaled->data, audio_block_samples); + + //now process the data blocks + __disable_irq(); + if (block_left_1st == NULL) { + block_left_1st = block_f32_scaled; + block_left_offset = 0; + __enable_irq(); + } else if (block_left_2nd == NULL) { + block_left_2nd = block_f32_scaled; + __enable_irq(); + } else { + audio_block_f32_t *tmp = block_left_1st; + block_left_1st = block_left_2nd; + block_left_2nd = block_f32_scaled; + block_left_offset = 0; + __enable_irq(); + AudioStream_F32::release(tmp); + } + AudioStream_F32::transmit(block_f32,0); AudioStream_F32::release(block_f32); //echo the incoming audio out the outputs + } else { + //this branch should never get called, but if it does, let's release the buffer that was never used + AudioStream_F32::release(block_f32_scaled); + } + + block_f32_scaled = block2_f32_scaled; //this is simply renaming the pre-allocated buffer + block_f32 = receiveReadOnly_f32(1); // input 1 = right channel + if (block_f32) { + //scale F32 to Int32 + //block_f32_scaled = AudioStream_F32::allocate_f32(); + scale_f32_to_i32(block_f32->data, block_f32_scaled->data, audio_block_samples); + + __disable_irq(); + if (block_right_1st == NULL) { + block_right_1st = block_f32_scaled; + block_right_offset = 0; + __enable_irq(); + } else if (block_right_2nd == NULL) { + block_right_2nd = block_f32_scaled; + __enable_irq(); + } else { + audio_block_f32_t *tmp = block_right_1st; + block_right_1st = block_right_2nd; + block_right_2nd = block_f32_scaled; + block_right_offset = 0; + __enable_irq(); + AudioStream_F32::release(tmp); + } + AudioStream_F32::transmit(block_f32,1); AudioStream_F32::release(block_f32); //echo the incoming audio out the outputs + } else { + //this branch should never get called, but if it does, let's release the buffer that was never used + AudioStream_F32::release(block_f32_scaled); + } +} + + +// MCLK needs to be 48e6 / 1088 * 256 = 11.29411765 MHz -> 44.117647 kHz sample rate +// +#if F_CPU == 96000000 || F_CPU == 48000000 || F_CPU == 24000000 + // PLL is at 96 MHz in these modes + #define MCLK_MULT 2 + #define MCLK_DIV 17 +#elif F_CPU == 72000000 + #define MCLK_MULT 8 + #define MCLK_DIV 51 +#elif F_CPU == 120000000 + #define MCLK_MULT 8 + #define MCLK_DIV 85 +#elif F_CPU == 144000000 + #define MCLK_MULT 4 + #define MCLK_DIV 51 +#elif F_CPU == 168000000 + #define MCLK_MULT 8 + #define MCLK_DIV 119 +#elif F_CPU == 180000000 + #define MCLK_MULT 16 + #define MCLK_DIV 255 + #define MCLK_SRC 0 +#elif F_CPU == 192000000 + #define MCLK_MULT 1 + #define MCLK_DIV 17 +#elif F_CPU == 216000000 + #define MCLK_MULT 8 + #define MCLK_DIV 153 + #define MCLK_SRC 0 +#elif F_CPU == 240000000 + #define MCLK_MULT 4 + #define MCLK_DIV 85 +#elif F_CPU == 16000000 + #define MCLK_MULT 12 + #define MCLK_DIV 17 +#else + #error "This CPU Clock Speed is not supported by the Audio library"; +#endif + +#ifndef MCLK_SRC + #if (F_CPU >= 20000000) + #define MCLK_SRC 3 // the PLL + #else + #define MCLK_SRC 0 // system clock + #endif +#endif + +void AudioOutputI2S_F32::config_i2s(void) { config_i2s(false); } +void AudioOutputI2S_F32::config_i2s(bool transferUsing32bit) { + SIM_SCGC6 |= SIM_SCGC6_I2S; + SIM_SCGC7 |= SIM_SCGC7_DMA; + SIM_SCGC6 |= SIM_SCGC6_DMAMUX; + + // if either transmitter or receiver is enabled, do nothing + if (I2S0_TCSR & I2S_TCSR_TE) return; + if (I2S0_RCSR & I2S_RCSR_RE) return; + + //if (transferUsing32bit) { + config_i2s_i32(); + //} else { + // config_i2s_i16(); + //} + + // configure pin mux for 3 clock signals + CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK) + CORE_PIN9_CONFIG = PORT_PCR_MUX(6); // pin 9, PTC3, I2S0_TX_BCLK + CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK +} + +void AudioOutputI2S_F32::config_i2s_i16(void) +{ + + // enable MCLK output + I2S0_MCR = I2S_MCR_MICS(MCLK_SRC) | I2S_MCR_MOE; + while (I2S0_MCR & I2S_MCR_DUF) ; + I2S0_MDR = I2S_MDR_FRACT((MCLK_MULT-1)) | I2S_MDR_DIVIDE((MCLK_DIV-1)); + + // configure transmitter + I2S0_TMR = 0; + I2S0_TCR1 = I2S_TCR1_TFW(1); // watermark at half fifo size + I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1) + | I2S_TCR2_BCD | I2S_TCR2_DIV(3); //for 32-bit, use I2S_TCR2_DIV(1) + I2S0_TCR3 = I2S_TCR3_TCE; + I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(15) | I2S_TCR4_MF + | I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_TCR4_FSD; //for 32-bit use I2S_TCR4_SYWD(31) + I2S0_TCR5 = I2S_TCR5_WNW(15) | I2S_TCR5_W0W(15) | I2S_TCR5_FBT(15); //for 32-bit, change all 15 to 31 + + // configure receiver (sync'd to transmitter clocks) + I2S0_RMR = 0; + I2S0_RCR1 = I2S_RCR1_RFW(1); + I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1) + | I2S_RCR2_BCD | I2S_RCR2_DIV(3); //for 32-bit, change I2S_RCR2_DIV(3) to I2S_RCR2_DIV(1) + I2S0_RCR3 = I2S_RCR3_RCE; + I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(15) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; //for 32-bit, change I2S_RCR4_SYWD(15) to I2S_RCR4_SYWD(31) + I2S0_RCR5 = I2S_RCR5_WNW(15) | I2S_RCR5_W0W(15) | I2S_RCR5_FBT(15); //for 32-bit, change all 15 to 31 + + +} + +//32-bit transfers. Taken from: https://github.com/WMXZ-EU/BasicAudioLogger/blob/master/I2S_32.h +void AudioOutputI2S_F32::config_i2s_i32(void) +{ + + // enable MCLK output + I2S0_MCR = I2S_MCR_MICS(MCLK_SRC) | I2S_MCR_MOE; + while (I2S0_MCR & I2S_MCR_DUF) ; + I2S0_MDR = I2S_MDR_FRACT((MCLK_MULT-1)) | I2S_MDR_DIVIDE((MCLK_DIV-1)); + //I2S0_MDR = I2S_MDR_FRACT((MCLK_MULT-1)) | I2S_MDR_DIVIDE((MCLK_DIV/2-1)); //For 32-bit? + + // configure transmitter + I2S0_TMR = 0; + I2S0_TCR1 = I2S_TCR1_TFW(1); // watermark at half fifo size. should be 1 or 2? + I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1) + | I2S_TCR2_BCD | I2S_TCR2_DIV(1); //transmitter must be set to asynchronous mode, + I2S0_TCR3 = I2S_TCR3_TCE; + I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF + | I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_TCR4_FSD; + I2S0_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); + + // configure receiver (sync'd to transmitter clocks) + I2S0_RMR = 0; + I2S0_RCR1 = I2S_RCR1_RFW(1); // watermark at half fifo size. should be 1 or 2? + I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1) + | I2S_RCR2_BCD | I2S_RCR2_DIV(1); //receiver set to syncrhonous + I2S0_RCR3 = I2S_RCR3_RCE; + I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; + I2S0_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); + +} +/******************************************************************/ + +/* +void AudioOutputI2Sslave::begin(void) +{ + dma.begin(true); // Allocate the DMA channel first + //pinMode(2, OUTPUT); + block_left_1st = NULL; + block_right_1st = NULL; + AudioOutputI2Sslave::config_i2s(); + CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 +#if defined(KINETISK) + dma.TCD->SADDR = i2s_tx_buffer; + dma.TCD->SOFF = 2; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); + dma.TCD->NBYTES_MLNO = 2; + dma.TCD->SLAST = -sizeof(i2s_tx_buffer); + dma.TCD->DADDR = &I2S0_TDR0; + dma.TCD->DOFF = 0; + dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; +#endif + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); + update_responsibility = update_setup(); + dma.enable(); + I2S0_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE | I2S_TCSR_FR; + dma.attachInterrupt(isr); +} +void AudioOutputI2Sslave::config_i2s(void) +{ + SIM_SCGC6 |= SIM_SCGC6_I2S; + SIM_SCGC7 |= SIM_SCGC7_DMA; + SIM_SCGC6 |= SIM_SCGC6_DMAMUX; + // if either transmitter or receiver is enabled, do nothing + if (I2S0_TCSR & I2S_TCSR_TE) return; + if (I2S0_RCSR & I2S_RCSR_RE) return; + // Select input clock 0 + // Configure to input the bit-clock from pin, bypasses the MCLK divider + I2S0_MCR = I2S_MCR_MICS(0); + I2S0_MDR = 0; + // configure transmitter + I2S0_TMR = 0; + I2S0_TCR1 = I2S_TCR1_TFW(1); // watermark at half fifo size + I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP; + I2S0_TCR3 = I2S_TCR3_TCE; + I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(15) | I2S_TCR4_MF + | I2S_TCR4_FSE | I2S_TCR4_FSP; + I2S0_TCR5 = I2S_TCR5_WNW(15) | I2S_TCR5_W0W(15) | I2S_TCR5_FBT(15); + // configure receiver (sync'd to transmitter clocks) + I2S0_RMR = 0; + I2S0_RCR1 = I2S_RCR1_RFW(1); + I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP; + I2S0_RCR3 = I2S_RCR3_RCE; + I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(15) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; + I2S0_RCR5 = I2S_RCR5_WNW(15) | I2S_RCR5_W0W(15) | I2S_RCR5_FBT(15); + // configure pin mux for 3 clock signals + CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK) + CORE_PIN9_CONFIG = PORT_PCR_MUX(6); // pin 9, PTC3, I2S0_TX_BCLK + CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK +} +*/ + + +// ///////////////////////////////////////////// +// //////////////////////////////////////////////// +///////////////////////////////////////////// +#if 0 +// +++++++++++++++ SAVE ++++++++++++++++++++++++ +/* + * output_i2s_f32.cpp - Input block of float samples from I2S + * + * Adapted to F32 output and Open Audio AudioSettings_F32 by Chip Audette + * Modified for Teensy 4.x Bob Larkin June 2020 + * + * Direct from: + * Audio Library for Teensy 3.X + * Copyright (c) 2014, 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. + */ + +// Changes to support Teensy 4.x marked //***T4X*** Bob, June 2020 #include "output_i2s_f32.h" //#include "input_i2s_f32.h" @@ -30,16 +794,16 @@ //#include "memcpy_interleave.h" #include - //Here's the function to change the sample rate of the system (via changing the clocking of the I2S bus) //https://forum.pjrc.com/threads/38753-Discussion-about-a-simple-way-to-change-the-sample-rate?p=121365&viewfull=1#post121365 float setI2SFreq(const float freq_Hz) { - int freq = (int)freq_Hz; +//***T4X*** no rate change yet for T4 +#if defined(KINETISK) + int freq = (int)freq_Hz; typedef struct { - uint8_t mult; - uint16_t div; + uint8_t mult; + uint16_t div; } __attribute__((__packed__)) tmclk; - const int numfreqs = 16; const int samplefreqs[numfreqs] = { 2000, 8000, 11025, 16000, 22050, 24000, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)(44117.64706 * 2), 96000, 176400, (int)(44117.64706 * 4), 192000}; @@ -71,12 +835,15 @@ float setI2SFreq(const float freq_Hz) { } } return 0.0f; +#elif defined(__IMXRT1062__) +// Needs some meat.....otherwise just 44100 +#endif } -audio_block_t * AudioOutputI2S_F32::block_left_1st = NULL; -audio_block_t * AudioOutputI2S_F32::block_right_1st = NULL; -audio_block_t * AudioOutputI2S_F32::block_left_2nd = NULL; -audio_block_t * AudioOutputI2S_F32::block_right_2nd = NULL; +audio_block_f32_t * AudioOutputI2S_F32::block_left_1st = NULL; +audio_block_f32_t * AudioOutputI2S_F32::block_right_1st = NULL; +audio_block_f32_t * AudioOutputI2S_F32::block_left_2nd = NULL; +audio_block_f32_t * AudioOutputI2S_F32::block_right_2nd = NULL; uint16_t AudioOutputI2S_F32::block_left_offset = 0; uint16_t AudioOutputI2S_F32::block_right_offset = 0; bool AudioOutputI2S_F32::update_responsibility = false; @@ -88,20 +855,22 @@ int AudioOutputI2S_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; #define I2S_BUFFER_TO_USE_BYTES (AudioOutputI2S_F32::audio_block_samples*sizeof(i2s_tx_buffer[0])) +//***T4X*** +#if defined(__IMXRT1062__) +#include "utility/imxrt_hw.h" +#endif void AudioOutputI2S_F32::begin(void) { dma.begin(true); // Allocate the DMA channel first - block_left_1st = NULL; block_right_1st = NULL; - // TODO: should we set & clear the I2S_TCSR_SR bit here? config_i2s(); +#if defined(KINETISK) CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 -#if defined(KINETISK) dma.TCD->SADDR = i2s_tx_buffer; dma.TCD->SOFF = 2; dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); @@ -116,29 +885,49 @@ void AudioOutputI2S_F32::begin(void) //dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; //original dma.TCD->BITER_ELINKNO = I2S_BUFFER_TO_USE_BYTES / 2; dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; -#endif dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); update_responsibility = update_setup(); dma.enable(); I2S0_TCSR = I2S_TCSR_SR; I2S0_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; - dma.attachInterrupt(isr); +//***T4X*** +#elif defined(__IMXRT1062__) + CORE_PIN7_CONFIG = 3; //1:TX_DATA0 + dma.TCD->SADDR = i2s_tx_buffer; + dma.TCD->SOFF = 2; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); + dma.TCD->NBYTES_MLNO = 2; + dma.TCD->SLAST = -sizeof(i2s_tx_buffer); + dma.TCD->DOFF = 0; + dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); + dma.enable(); + + I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; + I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; +#endif + update_responsibility = update_setup(); + dma.attachInterrupt(isr_f32); + // change the I2S frequencies to make the requested sample rate + // Won't happen for T4.x - later maybe setI2SFreq(AudioOutputI2S_F32::sample_rate_Hz); - enabled = 1; - - //AudioInputI2S_F32::begin_guts(); } - +// //////////////////////////// +#if 0 void AudioOutputI2S_F32::isr(void) { -#if defined(KINETISK) +#if defined(KINETISK) || defined(__IMXRT1062__) int16_t *dest; - audio_block_t *blockL, *blockR; + audio_block_f32_t *blockL, *blockR; uint32_t saddr, offsetL, offsetR; saddr = (uint32_t)(dma.TCD->SADDR); @@ -290,11 +1079,194 @@ void AudioOutputI2S_F32::isr(void) } #endif } +#endif +// ///////if 0 + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++From Typan USE +void AudioOutputI2S_F32::isr_f32(void) //should be called every half of an audio block +{ + int32_t *dest; //int32 is the data type being sent to the audio codec + audio_block_f32_t *blockL, *blockR; + uint32_t saddr; + uint32_t offsetL, offsetR; + + saddr = (uint32_t)(dma.TCD->SADDR); + dma.clearInterrupt(); + //if (saddr < (uint32_t)i2s_tx_buffer + sizeof(i2s_tx_buffer) / 2) { //original 16-bit + if (saddr < (uint32_t)i2s_tx_buffer + I2S_BUFFER_TO_USE_BYTES / 2) { //are we transmitting the first half or second half of the buffer? + // DMA is transmitting the first half of the buffer + // so we must fill the second half + //dest = (int16_t *)&i2s_tx_buffer[AUDIO_BLOCK_SAMPLES/2]; //original, half-way through buffer (buffer is 32-bit elements filled with 16-bit stereo samples) + dest = (int32_t *)&i2s_tx_buffer[2*(audio_block_samples/2)]; //half-way through the buffer..remember, buffer is 32-bit elements filled with 32-bit stereo samples) + if (AudioOutputI2S_F32::update_responsibility) AudioStream_F32::update_all(); + } else { + // DMA is transmitting the second half of the buffer so we must fill the first half + dest = (int32_t *)i2s_tx_buffer; //beginning of the buffer + } + + blockL = AudioOutputI2S_F32::block_left_1st; + blockR = AudioOutputI2S_F32::block_right_1st; + offsetL = AudioOutputI2S_F32::block_left_offset; + offsetR = AudioOutputI2S_F32::block_right_offset; + + + int32_t *d = dest; + if (blockL && blockR) { + //memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR); + //memcpy_tointerleaveLRwLen(dest, blockL->data + offsetL, blockR->data + offsetR, audio_block_samples/2); + float32_t *pL = blockL->data + offsetL; + float32_t *pR = blockR->data + offsetR; + for (int i=0; i < audio_block_samples/2; i++) { //loop over half of the audio block (this routine gets called every half an audio block) + *d++ = (int32_t) (*pL++); + *d++ = (int32_t) (*pR++); //cast and interleave + } + offsetL += (audio_block_samples / 2); + offsetR += (audio_block_samples / 2); + } else if (blockL) { + //memcpy_tointerleaveLR(dest, blockL->data + offsetL, blockR->data + offsetR); + float32_t *pL = blockL->data + offsetL; + for (int i=0; i < audio_block_samples /2; i++) { + *d++ = (int32_t) *pL++; //cast and interleave + *d++ = 0; + } + offsetL += (audio_block_samples / 2); + } else if (blockR) { + float32_t *pR = blockR->data + offsetR; + for (int i=0; i < audio_block_samples /2; i++) { + *d++ = 0; + *d++ = (int32_t) *pR++; //cast and interleave + } + offsetR += (audio_block_samples / 2); + } else { + //memset(dest,0,AUDIO_BLOCK_SAMPLES * 2); //half buffer (AUDIO_BLOCK_SAMPLES/2), 16-bits per sample (AUDIO_BLOCK_SAMPLES/2*2), stereo (AUDIO_BLOCK_SAMPLES/2*2*2) + //memset(dest,0,audio_block_samples * 2 * 4 / 2);//half buffer (AUDIO_BLOCK_SAMPLES/2), 32-bits per sample (AUDIO_BLOCK_SAMPLES/2*4), stereo (AUDIO_BLOCK_SAMPLES/2*4*2) + for (int i=0; i < audio_block_samples/2; i++) { //loop over half of the audio block (this routine gets called every half an audio block) + *d++ = (int32_t) 0; + *d++ = (int32_t) 0; + //*d++ = (int32_t) (-200000000L); + } + return; + } + + //if (offsetL < AUDIO_BLOCK_SAMPLES) { //original + if (offsetL < (uint16_t)audio_block_samples) { + AudioOutputI2S_F32::block_left_offset = offsetL; + } else { + AudioOutputI2S_F32::block_left_offset = 0; + AudioStream_F32::release(blockL); + AudioOutputI2S_F32::block_left_1st = AudioOutputI2S_F32::block_left_2nd; + AudioOutputI2S_F32::block_left_2nd = NULL; + } + //if (offsetR < AUDIO_BLOCK_SAMPLES) { + if (offsetR < (uint16_t)audio_block_samples) { + AudioOutputI2S_F32::block_right_offset = offsetR; + } else { + AudioOutputI2S_F32::block_right_offset = 0; + AudioStream_F32::release(blockR); + AudioOutputI2S_F32::block_right_1st = AudioOutputI2S_F32::block_right_2nd; + AudioOutputI2S_F32::block_right_2nd = NULL; + } + +} + + + +//+++++++++++++++++++++++++++++++++++++++++++++++++++++++ +//update has to be carefully coded so that, if audio_blocks are not available, the code exits +//gracefully and won't hang. That'll cause the whole system to hang, which would be very bad. +void AudioOutputI2S_F32::update(void) +{ + // null audio device: discard all incoming data + //if (!active) return; + //audio_block_t *block = receiveReadOnly(); + //if (block) release(block); + + audio_block_f32_t *block_f32; + audio_block_f32_t *block_f32_scaled = AudioStream_F32::allocate_f32(); + audio_block_f32_t *block2_f32_scaled = AudioStream_F32::allocate_f32(); + if ((!block_f32_scaled) || (!block2_f32_scaled)) { + //couldn't get some working memory. Return. + if (block_f32_scaled) AudioStream_F32::release(block_f32_scaled); + if (block2_f32_scaled) AudioStream_F32::release(block2_f32_scaled); + return; + } + + //now that we have our working memory, proceed with getting the audio data and processing + block_f32 = receiveReadOnly_f32(0); // input 0 = left channel + if (block_f32) { + if (block_f32->length != audio_block_samples) { + Serial.print("AudioOutputI2S_F32: *** WARNING ***: audio_block says len = "); + Serial.print(block_f32->length); + Serial.print(", but I2S settings want it to be = "); + Serial.println(audio_block_samples); + } + //Serial.print("AudioOutputI2S_F32: audio_block_samples = "); + //Serial.println(audio_block_samples); + + //scale F32 to Int32 + //block_f32_scaled = AudioStream_F32::allocate_f32(); + scale_f32_to_i32(block_f32->data, block_f32_scaled->data, audio_block_samples); + + //now process the data blocks + __disable_irq(); + if (block_left_1st == NULL) { + block_left_1st = block_f32_scaled; + block_left_offset = 0; + __enable_irq(); + } else if (block_left_2nd == NULL) { + block_left_2nd = block_f32_scaled; + __enable_irq(); + } else { + audio_block_f32_t *tmp = block_left_1st; + block_left_1st = block_left_2nd; + block_left_2nd = block_f32_scaled; + block_left_offset = 0; + __enable_irq(); + AudioStream_F32::release(tmp); + } + AudioStream_F32::transmit(block_f32,0); AudioStream_F32::release(block_f32); //echo the incoming audio out the outputs + } else { + //this branch should never get called, but if it does, let's release the buffer that was never used + AudioStream_F32::release(block_f32_scaled); + } + + block_f32_scaled = block2_f32_scaled; //this is simply renaming the pre-allocated buffer + block_f32 = receiveReadOnly_f32(1); // input 1 = right channel + if (block_f32) { + //scale F32 to Int32 + //block_f32_scaled = AudioStream_F32::allocate_f32(); + scale_f32_to_i32(block_f32->data, block_f32_scaled->data, audio_block_samples); + + __disable_irq(); + if (block_right_1st == NULL) { + block_right_1st = block_f32_scaled; + block_right_offset = 0; + __enable_irq(); + } else if (block_right_2nd == NULL) { + block_right_2nd = block_f32_scaled; + __enable_irq(); + } else { + audio_block_f32_t *tmp = block_right_1st; + block_right_1st = block_right_2nd; + block_right_2nd = block_f32_scaled; + block_right_offset = 0; + __enable_irq(); + AudioStream_F32::release(tmp); + } + AudioStream_F32::transmit(block_f32,1); AudioStream_F32::release(block_f32); //echo the incoming audio out the outputs + } else { + //this branch should never get called, but if it does, let's release the buffer that was never used + AudioStream_F32::release(block_f32_scaled); + } +} +// ++++++++++++++++++++++++++++++ + void AudioOutputI2S_F32::convert_f32_to_i16(float32_t *p_f32, int16_t *p_i16, int len) { for (int i=0; idata[27],5); //<<<<<<<<<<<<<<<<<<<<<<<<<< //convert F32 to Int16 block = AudioStream::allocate(); convert_f32_to_i16(block_f32->data, block->data, audio_block_samples); + Serial.print("OI "); Serial.println(block->data[27]); //<<<<<<<<<<<<<<<<<<<<<<<<<< AudioStream_F32::release(block_f32); //now process the data blocks @@ -344,8 +1317,9 @@ void AudioOutputI2S_F32::update(void) //convert F32 to Int16 block = AudioStream::allocate(); convert_f32_to_i16(block_f32->data, block->data, audio_block_samples); + AudioStream_F32::release(block_f32); - + __disable_irq(); if (block_right_1st == NULL) { block_right_1st = block; @@ -364,8 +1338,13 @@ void AudioOutputI2S_F32::update(void) } } } +#endif +// END OLF update +//***T4X*** +#if defined(KINETISK) || defined(KINETISL) + // MCLK needs to be 48e6 / 1088 * 256 = 11.29411765 MHz -> 44.117647 kHz sample rate // #if F_CPU == 96000000 || F_CPU == 48000000 || F_CPU == 24000000 @@ -413,8 +1392,112 @@ void AudioOutputI2S_F32::update(void) #endif #endif +//***T4X*** +#endif + void AudioOutputI2S_F32::config_i2s(void) { +//***T4*** From current I16 of Teensy Audio: +#if defined(KINETISK) || defined(KINETISL) + SIM_SCGC6 |= SIM_SCGC6_I2S; + SIM_SCGC7 |= SIM_SCGC7_DMA; + SIM_SCGC6 |= SIM_SCGC6_DMAMUX; + + // if either transmitter or receiver is enabled, do nothing + if (I2S0_TCSR & I2S_TCSR_TE) return; + if (I2S0_RCSR & I2S_RCSR_RE) return; + + // enable MCLK output + I2S0_MCR = I2S_MCR_MICS(MCLK_SRC) | I2S_MCR_MOE; + while (I2S0_MCR & I2S_MCR_DUF) ; + I2S0_MDR = I2S_MDR_FRACT((MCLK_MULT-1)) | I2S_MDR_DIVIDE((MCLK_DIV-1)); + + // configure transmitter + I2S0_TMR = 0; + I2S0_TCR1 = I2S_TCR1_TFW(1); // watermark at half fifo size + I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1) + | I2S_TCR2_BCD | I2S_TCR2_DIV(1); + I2S0_TCR3 = I2S_TCR3_TCE; + I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF + | I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_TCR4_FSD; + I2S0_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); + + // configure receiver (sync'd to transmitter clocks) + I2S0_RMR = 0; + I2S0_RCR1 = I2S_RCR1_RFW(1); + I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1) + | I2S_RCR2_BCD | I2S_RCR2_DIV(1); + I2S0_RCR3 = I2S_RCR3_RCE; + I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; + I2S0_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); + + // configure pin mux for 3 clock signals + CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK) + CORE_PIN9_CONFIG = PORT_PCR_MUX(6); // pin 9, PTC3, I2S0_TX_BCLK + CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK + +#elif defined(__IMXRT1062__) + + CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON); + + // if either transmitter or receiver is enabled, do nothing + if (I2S1_TCSR & I2S_TCSR_TE) return; + if (I2S1_RCSR & I2S_RCSR_RE) return; +//PLL: + int fs = AUDIO_SAMPLE_RATE_EXACT; + // PLL between 27*24 = 648MHz und 54*24=1296MHz + 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); + + // clear SAI1_CLK register locations + CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI1_CLK_SEL_MASK)) + | CCM_CSCMR1_SAI1_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4 + CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK)) + | CCM_CS1CDR_SAI1_CLK_PRED(n1-1) // &0x07 + | CCM_CS1CDR_SAI1_CLK_PODF(n2-1); // &0x3f + + // Select MCLK + IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1 + & ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK)) + | (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0)); + + CORE_PIN23_CONFIG = 3; //1:MCLK + CORE_PIN21_CONFIG = 3; //1:RX_BCLK + CORE_PIN20_CONFIG = 3; //1:RX_SYNC + + int rsync = 0; + int tsync = 1; + + I2S1_TMR = 0; + //I2S1_TCSR = (1<<25); //Reset + I2S1_TCR1 = I2S_TCR1_RFW(1); + I2S1_TCR2 = I2S_TCR2_SYNC(tsync) | I2S_TCR2_BCP // sync=0; tx is async; + | (I2S_TCR2_BCD | I2S_TCR2_DIV((1)) | I2S_TCR2_MSEL(1)); + I2S1_TCR3 = I2S_TCR3_TCE; + I2S1_TCR4 = I2S_TCR4_FRSZ((2-1)) | I2S_TCR4_SYWD((32-1)) | I2S_TCR4_MF + | I2S_TCR4_FSD | I2S_TCR4_FSE | I2S_TCR4_FSP; + I2S1_TCR5 = I2S_TCR5_WNW((32-1)) | I2S_TCR5_W0W((32-1)) | I2S_TCR5_FBT((32-1)); + + I2S1_RMR = 0; + //I2S1_RCSR = (1<<25); //Reset + I2S1_RCR1 = I2S_RCR1_RFW(1); + I2S1_RCR2 = I2S_RCR2_SYNC(rsync) | I2S_RCR2_BCP // sync=0; rx is async; + | (I2S_RCR2_BCD | I2S_RCR2_DIV((1)) | I2S_RCR2_MSEL(1)); + I2S1_RCR3 = I2S_RCR3_RCE; + I2S1_RCR4 = I2S_RCR4_FRSZ((2-1)) | I2S_RCR4_SYWD((32-1)) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; + I2S1_RCR5 = I2S_RCR5_WNW((32-1)) | I2S_RCR5_W0W((32-1)) | I2S_RCR5_FBT((32-1)); + +#endif + +/* SAVE FOR NOW-BEFORE T4: SIM_SCGC6 |= SIM_SCGC6_I2S; SIM_SCGC7 |= SIM_SCGC7_DMA; SIM_SCGC6 |= SIM_SCGC6_DMAMUX; @@ -452,47 +1535,72 @@ void AudioOutputI2S_F32::config_i2s(void) CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK) CORE_PIN9_CONFIG = PORT_PCR_MUX(6); // pin 9, PTC3, I2S0_TX_BCLK CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK +END B4 T4 */ } /******************************************************************/ +//***T4X*** Need to get thi back in for slave codec timing +/***************************************************************** -/* void AudioOutputI2Sslave::begin(void) { + dma.begin(true); // Allocate the DMA channel first - //pinMode(2, OUTPUT); block_left_1st = NULL; block_right_1st = NULL; AudioOutputI2Sslave::config_i2s(); - CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 + #if defined(KINETISK) + CORE_PIN22_CONFIG = PORT_PCR_MUX(6); // pin 22, PTC1, I2S0_TXD0 dma.TCD->SADDR = i2s_tx_buffer; dma.TCD->SOFF = 2; dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); dma.TCD->NBYTES_MLNO = 2; dma.TCD->SLAST = -sizeof(i2s_tx_buffer); - dma.TCD->DADDR = &I2S0_TDR0; + dma.TCD->DADDR = (void *)((uint32_t)&I2S0_TDR0 + 2); dma.TCD->DOFF = 0; dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; dma.TCD->DLASTSGA = 0; dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; -#endif dma.triggerAtHardwareEvent(DMAMUX_SOURCE_I2S0_TX); - update_responsibility = update_setup(); dma.enable(); - I2S0_TCSR |= I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE | I2S_TCSR_FR; + I2S0_TCSR = I2S_TCSR_SR; + I2S0_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; + +#elif defined(__IMXRT1062__) + CORE_PIN7_CONFIG = 3; //1:TX_DATA0 + dma.TCD->SADDR = i2s_tx_buffer; + dma.TCD->SOFF = 2; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(1) | DMA_TCD_ATTR_DSIZE(1); + dma.TCD->NBYTES_MLNO = 2; + dma.TCD->SLAST = -sizeof(i2s_tx_buffer); + dma.TCD->DOFF = 0; + dma.TCD->CITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->DLASTSGA = 0; + dma.TCD->BITER_ELINKNO = sizeof(i2s_tx_buffer) / 2; + dma.TCD->DADDR = (void *)((uint32_t)&I2S1_TDR0 + 2); + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_TX); + dma.enable(); + + I2S1_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; + I2S1_TCSR = I2S_TCSR_TE | I2S_TCSR_BCE | I2S_TCSR_FRDE; +#endif + + update_responsibility = update_setup(); dma.attachInterrupt(isr); } void AudioOutputI2Sslave::config_i2s(void) { +#if defined(KINETISK) SIM_SCGC6 |= SIM_SCGC6_I2S; SIM_SCGC7 |= SIM_SCGC7_DMA; SIM_SCGC6 |= SIM_SCGC6_DMAMUX; @@ -512,10 +1620,10 @@ void AudioOutputI2Sslave::config_i2s(void) I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP; I2S0_TCR3 = I2S_TCR3_TCE; - I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(15) | I2S_TCR4_MF + I2S0_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF | I2S_TCR4_FSE | I2S_TCR4_FSP; - I2S0_TCR5 = I2S_TCR5_WNW(15) | I2S_TCR5_W0W(15) | I2S_TCR5_FBT(15); + I2S0_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); // configure receiver (sync'd to transmitter clocks) I2S0_RMR = 0; @@ -523,15 +1631,52 @@ void AudioOutputI2Sslave::config_i2s(void) I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP; I2S0_RCR3 = I2S_RCR3_RCE; - I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(15) | I2S_RCR4_MF + I2S0_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF | I2S_RCR4_FSE | I2S_RCR4_FSP | I2S_RCR4_FSD; - I2S0_RCR5 = I2S_RCR5_WNW(15) | I2S_RCR5_W0W(15) | I2S_RCR5_FBT(15); + I2S0_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); // configure pin mux for 3 clock signals CORE_PIN23_CONFIG = PORT_PCR_MUX(6); // pin 23, PTC2, I2S0_TX_FS (LRCLK) CORE_PIN9_CONFIG = PORT_PCR_MUX(6); // pin 9, PTC3, I2S0_TX_BCLK CORE_PIN11_CONFIG = PORT_PCR_MUX(6); // pin 11, PTC6, I2S0_MCLK + +#elif defined(__IMXRT1062__) + + CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON); + + // if either transmitter or receiver is enabled, do nothing + if (I2S1_TCSR & I2S_TCSR_TE) return; + if (I2S1_RCSR & I2S_RCSR_RE) return; + + // not using MCLK in slave mode - hope that's ok? + //CORE_PIN23_CONFIG = 3; // AD_B1_09 ALT3=SAI1_MCLK + CORE_PIN21_CONFIG = 3; // AD_B1_11 ALT3=SAI1_RX_BCLK + CORE_PIN20_CONFIG = 3; // AD_B1_10 ALT3=SAI1_RX_SYNC + IOMUXC_SAI1_RX_BCLK_SELECT_INPUT = 1; // 1=GPIO_AD_B1_11_ALT3, page 868 + IOMUXC_SAI1_RX_SYNC_SELECT_INPUT = 1; // 1=GPIO_AD_B1_10_ALT3, page 872 + + // configure transmitter + I2S1_TMR = 0; + I2S1_TCR1 = I2S_TCR1_RFW(1); // watermark at half fifo size + I2S1_TCR2 = I2S_TCR2_SYNC(1) | I2S_TCR2_BCP; + I2S1_TCR3 = I2S_TCR3_TCE; + I2S1_TCR4 = I2S_TCR4_FRSZ(1) | I2S_TCR4_SYWD(31) | I2S_TCR4_MF + | I2S_TCR4_FSE | I2S_TCR4_FSP | I2S_RCR4_FSD; + I2S1_TCR5 = I2S_TCR5_WNW(31) | I2S_TCR5_W0W(31) | I2S_TCR5_FBT(31); + + // configure receiver + I2S1_RMR = 0; + I2S1_RCR1 = I2S_RCR1_RFW(1); + I2S1_RCR2 = I2S_RCR2_SYNC(0) | I2S_TCR2_BCP; + I2S1_RCR3 = I2S_RCR3_RCE; + I2S1_RCR4 = I2S_RCR4_FRSZ(1) | I2S_RCR4_SYWD(31) | I2S_RCR4_MF + | I2S_RCR4_FSE | I2S_RCR4_FSP; + I2S1_RCR5 = I2S_RCR5_WNW(31) | I2S_RCR5_W0W(31) | I2S_RCR5_FBT(31); + +#endif } -*/ +* **********************************/ +// #if 0 end... +#endif diff --git a/output_i2s_f32.h.xxx b/output_i2s_f32.h.xxx index 12e941d..6db50e3 100644 --- a/output_i2s_f32.h.xxx +++ b/output_i2s_f32.h.xxx @@ -1,4 +1,9 @@ -/* Audio Library for Teensy 3.X +/* output_i2s_f32.h - Input block of float samples from I2S + * + * Adapted to F32 output and Open Audio AudioSettings_F32 by Chip Audette + * Modified for Teensy 4.x Bob Larkin June 2020 + * + * Direct from: Audio Library for Teensy 3.X * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com * * Development of this audio library was funded by PJRC.COM, LLC by sales of @@ -23,13 +28,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +/* Extended by Chip Audette, OpenAudio, May 2019 + * Converted to F32 and to variable audio block length + * The F32 conversion is under the MIT License. Use at your own risk. + */ #ifndef output_i2s_f32_h_ #define output_i2s_f32_h_ #include "Arduino.h" #include "AudioStream_F32.h" -#include "AudioStream.h" +//include "AudioStream.h" #include "DMAChannel.h" @@ -46,20 +55,39 @@ public: } virtual void update(void); void begin(void); + void begin(bool); + void sub_begin_i32(void); + void sub_begin_i16(void); friend class AudioInputI2S_F32; - static void convert_f32_to_i16( float32_t *p_f32, int16_t *p_i16, int len) ; - + static void scale_f32_to_i16( float32_t *p_f32, float32_t *p_i16, int len) ; + static void scale_f32_to_i24( float32_t *p_f32, float32_t *p_i16, int len) ; + static void scale_f32_to_i32( float32_t *p_f32, float32_t *p_i32, int len) ; + static float setI2SFreq(const float); + /* I16 version supported these for T4: +#if defined(__IMXRT1062__) + friend class AudioOutputI2SQuad; + friend class AudioInputI2SQuad; + friend class AudioOutputI2SHex; + friend class AudioInputI2SHex; + friend class AudioOutputI2SOct; + friend class AudioInputI2SOct; +#endif + */ protected: //AudioOutputI2S_F32(const AudioSettings &settings): AudioStream_F32(2, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !! static void config_i2s(void); - static audio_block_t *block_left_1st; - static audio_block_t *block_right_1st; + static void config_i2s(bool); + static void config_i2s_i16(void); + static void config_i2s_i32(void); + 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); + static void isr_16(void); + static void isr_32(void); private: - static audio_block_t *block_left_2nd; - static audio_block_t *block_right_2nd; + static audio_block_f32_t *block_left_2nd; + static audio_block_f32_t *block_right_2nd; static uint16_t block_left_offset; static uint16_t block_right_offset; audio_block_f32_t *inputQueueArray[2]; @@ -67,8 +95,110 @@ private: static int audio_block_samples; volatile uint8_t enabled = 1; }; +#endif +#if 0 +/////////////////////////SAVE ////////////////////// +/* output_i2s_f32.h - Input block of float samples from I2S + * + * Adapted to F32 output and Open Audio AudioSettings_F32 by Chip Audette + * Modified for Teensy 4.x Bob Larkin June 2020 + * + * Direct from: + * Audio Library for Teensy 3.X + * Copyright (c) 2014, 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. + */ + +#ifndef output_i2s_f32_h_ +#define output_i2s_f32_h_ + +#include "Arduino.h" +#include "AudioStream_F32.h" +#include "AudioStream.h" +#include "DMAChannel.h" + +class AudioOutputI2S_F32 : public AudioStream_F32 +{ +//GUI: inputs:2, outputs:0 //this line used for automatic generation of GUI node +public: + // To use default AUDIO_SAMPLE_RATE and BLOCK_SIZE_SAMPLES from AudioStream.h: + AudioOutputI2S_F32(void) : AudioStream_F32(2, inputQueueArray) { begin();} + // Or, to allow for change in either: + AudioOutputI2S_F32(const AudioSettings_F32 &settings) : AudioStream_F32(2, inputQueueArray) { + sample_rate_Hz = settings.sample_rate_Hz; + audio_block_samples = settings.audio_block_samples; + begin(); + } + + virtual void update(void); + void begin(void); + friend class AudioInputI2S_F32; + static void convert_f32_to_i16( float32_t *p_f32, int16_t *p_i16, int len) ; // NOT IN TYMPAN +/* I16 version supported these for T4: +#if defined(__IMXRT1062__) + friend class AudioOutputI2SQuad; + friend class AudioInputI2SQuad; + friend class AudioOutputI2SHex; + friend class AudioInputI2SHex; + friend class AudioOutputI2SOct; + friend class AudioInputI2SOct; +#endif +*/ +///////////////////TYMPAN public: ///////////////// + + //void begin(void); + void begin(bool); + void sub_begin_i32(void); + void sub_begin_i16(void); + friend class AudioInputI2S_F32; + static void scale_f32_to_i16( float32_t *p_f32, float32_t *p_i16, int len) ; + static void scale_f32_to_i24( float32_t *p_f32, float32_t *p_i16, int len) ; + static void scale_f32_to_i32( float32_t *p_f32, float32_t *p_i32, int len) ; + static float setI2SFreq(const float); +//////////////////////////////////////////////// +protected: + //AudioOutputI2S_F32(const AudioSettings &settings): AudioStream_F32(2, inputQueueArray) {} // to be used only inside AudioOutputI2Sslave !! + static void config_i2s(void); + 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_f32(void); +private: + static audio_block_f32_t *block_left_2nd; + static audio_block_f32_t *block_right_2nd; + static uint16_t block_left_offset; + static uint16_t block_right_offset; + audio_block_f32_t *inputQueueArray[2]; + static float sample_rate_Hz; + static int audio_block_samples; + volatile uint8_t enabled = 1; +}; +#endif #endif +// #if 0 diff --git a/readme.md b/readme.md index 55cd819..04ac555 100644 --- a/readme.md +++ b/readme.md @@ -31,7 +31,15 @@ OpenAudio Library for Teensy 26-Brought in RadioFMDetector_F32 and Example 27-Brought in synth_sin_cos_F32 and test example 28-Brought in RadioNoiseBlanker_F32 and Example - +29-Created output_i2s_OA_F32.h and .cpp to have F32 input. Work in Progress, DO NOT USE. Set to .xxx + +**Special Note 2*** 2 July 2020 - Recently, the input and output I2S for F32 have been disabled as they had major hardware problems with +Teensy 4.x. The previous output_i2s_f32 files supported variable sample rate and variable block size, but only +for T3.x. To get this going again, the old classes will be re-enabled for T3.x only . And, new classes have been +created that compile and run under T4.x (and T3.5, T3.6) but, for now, have no provision for changing block size, sample rate or ADC +output word (always 16-bit). The new files are output_i2s_OA_F32.h and output_i2s_OA_F32.cpp with the corresponding inputs to come soon. The classes are AudioInputI2S_OA_F32 and AudioOutputI2S_OA_F32 with the same functionality as their parallel 16-bit +integer classes under Teensy Audio. If you need variable block size and/or variable data rate (T3.x only) use the Convert_I16toF32 and +Convert_F32toU16 classes. **Purpose**: The purpose of this library is to build upon the [Teensy Audio Library](http://www.pjrc.com/teensy/td_libs_Audio.html) to enable new functionality for real-time audio processing. diff --git a/synth_GaussianWhiteNoise_F32.cpp b/synth_GaussianWhiteNoise_F32.cpp index e316820..b1dc332 100644 --- a/synth_GaussianWhiteNoise_F32.cpp +++ b/synth_GaussianWhiteNoise_F32.cpp @@ -58,23 +58,29 @@ void AudioSynthGaussian_F32::update(void) * 2nd ed, with the comment "this is about as good as any 32-bit linear * congruential generator, entirely adequate for many uses." */ + + + // Try: + union { + uint32_t i32; + float32_t f32; + } uinf; + + for(int i=0; i