diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index 6006e91..6809e81 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -29,6 +29,7 @@ #include "AudioSettings_F32.h" #include "AudioSpectralDenoise_F32.h" #include "input_i2s_f32.h" +#include "input_i2s_quad_f32.h" #include "input_spdif3_f32.h" #include "async_input_spdif3_F32.h" #include "output_i2s_f32.h" diff --git a/examples/PassThroughQuadF32/PassThroughQuadF32.ino b/examples/PassThroughQuadF32/PassThroughQuadF32.ino new file mode 100644 index 0000000..6d63f25 --- /dev/null +++ b/examples/PassThroughQuadF32/PassThroughQuadF32.ino @@ -0,0 +1,125 @@ +// Demonstration of AudioOutputI2SQuad_F32 four channel I2S output object. +// Greg Raven KF5N November 2025. +// The first left and right channels are output on pin 7, which drives the +// Teensy Audio Adapter (Teensy 4.1). The second left and right channels are output on pin 32 (Teensy 4.1). + +#include +#include +#include +#include +#include + +// T3.x supported sample rates: 2000, 8000, 11025, 16000, 22050, 24000, 32000, 44100, 44117, 48000, +// 88200, 88235 (44117*2), 95680, 96000, 176400, 176470, 192000 +// T4.x supports any sample rate the codec will handle. + +const int sample_rate_Hz = 192000; +const int audio_block_samples = 128; // Always 128 + +AudioControlSGTL5000 sgtl5000_1; +AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); + +AudioAnalyzePeak_F32 peak0L(audio_settings); +AudioAnalyzePeak_F32 peak1R(audio_settings); +AudioAnalyzePeak_F32 peak2L(audio_settings); +AudioAnalyzePeak_F32 peak3R(audio_settings); +AudioInputI2SQuad_F32 i2s_in(audio_settings); +AudioOutputI2SQuad_F32 i2s_out(audio_settings); + +AudioConnection_F32 connect0(i2s_in, 0, peak0L, 0); +AudioConnection_F32 connect1(i2s_in, 1, peak1R, 0); +AudioConnection_F32 connect2(i2s_in, 2, peak2L, 0); +AudioConnection_F32 connect3(i2s_in, 3, peak3R, 0); + +AudioConnection_F32 patchCord1(i2s_in, 0, i2s_out, 0); +AudioConnection_F32 patchCord2(i2s_in, 1, i2s_out, 1); +AudioConnection_F32 patchCord3(i2s_in, 2, i2s_out, 2); +AudioConnection_F32 patchCord4(i2s_in, 3, i2s_out, 3); +// Select Which Input from Teensy Audio Adaptor +//const int myInput = AUDIO_INPUT_LINEIN; +const int myInput = AUDIO_INPUT_MIC; + +void setup() { + + Serial.begin(115200); + + /* check for CrashReport stored from previous run */ + if (CrashReport) { + Serial.print(CrashReport); + } + + // Audio connections require memory to work. For more + // detailed information, see the MemoryAndCpuUsage example + AudioMemory_F32(20,audio_settings); + + // Enable the first audio shield, select input, and enable output + sgtl5000_1.setAddress(LOW); + sgtl5000_1.enable(); + sgtl5000_1.inputSelect(myInput); + sgtl5000_1.micGain(0); + sgtl5000_1.volume(0.8); // Set headphone volume. + sgtl5000_1.unmuteHeadphone(); +#if 0 + // Enable the second audio shield, select input, and enable output + sgtl5000_2.setAddress(HIGH); + sgtl5000_2.enable(); + sgtl5000_2.inputSelect(myInput); + sgtl5000_2.micGain(0); + sgtl5000_2.volume(0.8); + sgtl5000_2.unmuteHeadphone(); + #else + // Specific code for T41-EP ADC PCM1808/ DAC PCM5102 +#define UNMUTEAUDIO LOW +#define MUTEAUDIO HIGH + const int MUTE = 38; // Mute Audio, HIGH = "On" Audio PA, LOW = Mute Audio PA off. This may be reversed depending on PA. + + pinMode(MUTE, OUTPUT); + Serial.printf("UNMUTE\n"); + digitalWrite(MUTE, UNMUTEAUDIO); // Keep audio junk out of the speakers/headphones until configuration is complete. + + #endif + setI2SFreq(sample_rate_Hz); + +} + +void loop() { + + Serial.printf("PassThrough Running Now\n"); + Serial.printf("Sample Rate = %d\n",sample_rate_Hz); + + if (myInput == AUDIO_INPUT_MIC){ + Serial.printf("Now using mic!\n"); + } else { + Serial.printf("Now using line in!\n"); + } + Serial.print("Max float memory = "); + Serial.println(AudioStream_F32::f32_memory_used_max); + if(peak0L.available()) Serial.print(peak0L.read(), 6); + Serial.print(" <-0L 1R-> "); + if(peak1R.available()) Serial.println(peak1R.read(), 6); + if(peak2L.available()) Serial.print(peak2L.read(), 6); + Serial.print(" <-2L 3R-> "); + if(peak3R.available()) Serial.println(peak3R.read(), 6); + delay(1000); +// DMA debug code. + uint32_t* dmaErrors; + dmaErrors = (uint32_t*)(0x400E8000 + 0x4); + Serial.printf("DMA errors = %x\n", *dmaErrors); +// + +} +// Frank B's routine for setting I2S clocks. *** FOR T4.x ONLY *** +void setI2SFreq(int freq1) // Thank ypu, Frank B. + { + // 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) / (freq1 * 256 * n1); + double C = ((double)freq1 * 256 * n1 * n2) / 24000000; + int c0 = C; + int c2 = 10000; + int c1 = C * c2 - (c0 * c2); + set_audioClock(c0, c1, c2, true); + 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 + } diff --git a/input_i2s_quad_f32.cpp b/input_i2s_quad_f32.cpp new file mode 100644 index 0000000..9c427c5 --- /dev/null +++ b/input_i2s_quad_f32.cpp @@ -0,0 +1,210 @@ +/* + * input_i2s_quad_f32.cpp + * + * 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. + */ + /* + * Extended by Terrance Robertson, May 2025 + * Converted to F32 + * Changed By Julia Stephenson, July 2026 + * from 16Bit to 32Bit Transfers from I2S + */ + +#include +#include "input_i2s_quad_f32.h" +#include "output_i2s_quad_f32.h" +#include "output_i2s_f32.h" + +DMAMEM __attribute__((aligned(32))) static uint32_t i2s_rx_buffer[AUDIO_BLOCK_SAMPLES*4];// 4 channels 128 samples each, total = 512. +audio_block_f32_t * AudioInputI2SQuad_F32::block_ch1 = NULL; +audio_block_f32_t * AudioInputI2SQuad_F32::block_ch2 = NULL; +audio_block_f32_t * AudioInputI2SQuad_F32::block_ch3 = NULL; +audio_block_f32_t * AudioInputI2SQuad_F32::block_ch4 = NULL; +uint32_t AudioInputI2SQuad_F32::block_offset = 0; +bool AudioInputI2SQuad_F32::update_responsibility = false; +DMAChannel AudioInputI2SQuad_F32::dma(false); + +float AudioInputI2SQuad_F32::sample_rate_Hz = AUDIO_SAMPLE_RATE; +int AudioInputI2SQuad_F32::audio_block_samples = AUDIO_BLOCK_SAMPLES; + +void AudioInputI2SQuad_F32::begin(void) { + dma.begin(true); // Allocate the DMA channel first + + AudioOutputI2S_F32::config_i2s(); + CORE_PIN8_CONFIG = 3; + CORE_PIN6_CONFIG = 3; + IOMUXC_SAI1_RX_DATA0_SELECT_INPUT = 2; // GPIO_B1_00_ALT3, pg 873 // Teensy Pin 8 + IOMUXC_SAI1_RX_DATA1_SELECT_INPUT = 1; // GPIO_B0_10_ALT3, pg 873 // Teensy Pin 6 + dma.TCD->SADDR = (void *)((uint32_t)&I2S1_RDR0); + dma.TCD->SOFF = 4; + dma.TCD->ATTR = DMA_TCD_ATTR_SSIZE(2) | DMA_TCD_ATTR_DSIZE(2); + dma.TCD->NBYTES_MLOFFYES = DMA_TCD_NBYTES_SMLOE | + DMA_TCD_NBYTES_MLOFFYES_MLOFF(-8) | + DMA_TCD_NBYTES_MLOFFYES_NBYTES(8); + dma.TCD->SLAST = -8; + dma.TCD->DADDR = i2s_rx_buffer; + dma.TCD->DOFF = 4; + dma.TCD->CITER_ELINKNO = AUDIO_BLOCK_SAMPLES * 2; + dma.TCD->DLASTSGA = -sizeof(i2s_rx_buffer); + dma.TCD->BITER_ELINKNO = AUDIO_BLOCK_SAMPLES * 2; + dma.TCD->CSR = DMA_TCD_CSR_INTHALF | DMA_TCD_CSR_INTMAJOR; + dma.triggerAtHardwareEvent(DMAMUX_SOURCE_SAI1_RX); + + I2S1_RCSR = 0; + I2S1_RCR3 = I2S_RCR3_RCE_2CH; + + I2S1_RCSR = I2S_RCSR_RE | I2S_RCSR_BCE | I2S_RCSR_FRDE | I2S_RCSR_FR; + update_responsibility = update_setup(); + dma.enable(); + dma.attachInterrupt(isr); +} + +void AudioInputI2SQuad_F32::isr(void) { + uint32_t daddr, offset; + const int32_t *src; + float32_t *dest1, *dest2, *dest3, *dest4; + + daddr = (uint32_t)(dma.TCD->DADDR); + dma.clearInterrupt(); + + if(daddr < (uint32_t)i2s_rx_buffer + sizeof(i2s_rx_buffer) / 2) { + // DMA is receiving to the first half of the buffer + // need to remove data from the second half + src = (int32_t *)&i2s_rx_buffer[AUDIO_BLOCK_SAMPLES * 2]; + if(update_responsibility) update_all(); + } else { + // DMA is receiving to the second half of the buffer + // need to remove data from the first half + src = (int32_t *)&i2s_rx_buffer[0]; + } + if(block_ch1) { + offset = block_offset; + if(offset <= AUDIO_BLOCK_SAMPLES/2) { + arm_dcache_delete((void*)src, sizeof(i2s_rx_buffer) / 2); + block_offset = offset + AUDIO_BLOCK_SAMPLES/2; + dest1 = &(block_ch1->data[offset]); + dest2 = &(block_ch2->data[offset]); + dest3 = &(block_ch3->data[offset]); + dest4 = &(block_ch4->data[offset]); + for (int i=0; i < AUDIO_BLOCK_SAMPLES/2; i++) { + *dest1++ = (float32_t)*src++; + *dest3++ = (float32_t)*src++; + *dest2++ = (float32_t)*src++; + *dest4++ = (float32_t)*src++; + } + } + } +} + +#define I32_TO_F32_NORM_FACTOR (4.656612875245797e-10) //which is 1/(2^31 - 1) +void AudioInputI2SQuad_F32::scale_i32_to_f32( float32_t *p_i32, float32_t *p_f32, int len) { + for (int i=0; i= AUDIO_BLOCK_SAMPLES) { + // the DMA filled 4 blocks, so grab them and get the + // 4 new blocks to the DMA, as quickly as possible + out1 = block_ch1; + block_ch1 = new1; + out2 = block_ch2; + block_ch2 = new2; + out3 = block_ch3; + block_ch3 = new3; + out4 = block_ch4; + block_ch4 = new4; + block_offset = 0; + __enable_irq(); + + //scale the float values so that the maximum possible audio values span -1.0 to + 1.0 + scale_i32_to_f32(out1->data, out1->data, AUDIO_BLOCK_SAMPLES); + scale_i32_to_f32(out2->data, out2->data, AUDIO_BLOCK_SAMPLES); + scale_i32_to_f32(out3->data, out3->data, AUDIO_BLOCK_SAMPLES); + scale_i32_to_f32(out4->data, out4->data, AUDIO_BLOCK_SAMPLES); + + // then transmit the DMA's former blocks + AudioStream_F32::transmit(out1, 0); + AudioStream_F32::release(out1); + AudioStream_F32::transmit(out2, 1); + AudioStream_F32::release(out2); + AudioStream_F32::transmit(out3, 2); + AudioStream_F32::release(out3); + AudioStream_F32::transmit(out4, 3); + AudioStream_F32::release(out4); + } else if(new1 != NULL) { + // the DMA didn't fill blocks, but we allocated blocks + if(block_ch1 == NULL) { + // the DMA doesn't have any blocks to fill, so + // give it the ones we just allocated + block_ch1 = new1; + block_ch2 = new2; + block_ch3 = new3; + block_ch4 = new4; + block_offset = 0; + __enable_irq(); + } else { + // the DMA already has blocks, doesn't need these + __enable_irq(); + AudioStream_F32::release(new1); + AudioStream_F32::release(new2); + AudioStream_F32::release(new3); + AudioStream_F32::release(new4); + } + } else { + // The DMA didn't fill blocks, and we could not allocate + // memory... the system is likely starving for memory! + // Sadly, there's nothing we can do. + __enable_irq(); + } +} diff --git a/input_i2s_quad_f32.h b/input_i2s_quad_f32.h new file mode 100644 index 0000000..a190750 --- /dev/null +++ b/input_i2s_quad_f32.h @@ -0,0 +1,76 @@ +/* + * input_i2s_quad_f32.h + * + * 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. + */ + /* + * Extended by Terrance Robertson, May 2025 + * Converted to F32 + * Changed By Julia Stephenson, July 2026 + * from 16Bit to 32Bit Transfers from I2S + */ + +#ifndef _input_i2s_quad_f32_h_ +#define _input_i2s_quad_f32_h_ + +#include +#include +#include +#include + +class AudioInputI2SQuad_F32 : public AudioStream_F32 +{ +//GUI: inputs:0, outputs:4 //this line used for automatic generation of GUI node +public: + //uses default AUDIO_SAMPLE_RATE and BLOCK_SIZE_SAMPLES from AudioStream.h: + AudioInputI2SQuad_F32(void) : AudioStream_F32(0, NULL) { begin(); } + // Allow variable sample rate and block size: + AudioInputI2SQuad_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) { + sample_rate_Hz = settings.sample_rate_Hz; + audio_block_samples = settings.audio_block_samples; + begin(); + } + + virtual void update(void); + static void scale_i32_to_f32( float32_t *p_i32, float32_t *p_f32, int len); + void begin(void); +protected: + static void isr_32(void); +private: + static bool update_responsibility; + static DMAChannel dma; + static void isr(void); + static audio_block_f32_t *block_ch1; + static audio_block_f32_t *block_ch2; + static audio_block_f32_t *block_ch3; + static audio_block_f32_t *block_ch4; + static uint32_t block_offset; + static float sample_rate_Hz; + static int audio_block_samples; +}; + + +#endif