Switching Beween All Inputs

I am working on some variations of the library examples, and I am bumping up against something that seems easiest to get to the bottom of here.

Below is my code, which I have modified from the TympanPDMMics.ino example sketch, which is working, YAY!

The ultimate goal is to be able to change the input audio source between all the sources, with the option to record audio from any input source.

  • On-Board Mics
  • External Mic
  • Line In breakout
  • Tympan Synthesized Audio (as done in the OutputTone.ino library example)
  • Play WAV file from SD Card
  • Record to SD from any of the above (except SD wav file play)

The thing I think I’m running into has to do with the AudioConnection_F32 patchcord variables. Is there a way to ‘disconnect’ and ‘reconnect’ the various patchcords?

Also, the sketch below has SD card record/stop record via serial keystrokes. Is there a better way to service the SD card (recording and playing) that can ‘turn off’ the SD card? Or otherwise not spend time servicing it? Or am I over-thinking things?

I am using latest Arduino v.2.3.8 with latest Teensy board files v1.6.0
Using a Tympan Rev F

/*
Created: Joel, March 2026
Based on TympanPDMMics.ino example sketch, Tympan Library. Eric, Sept 2019

Functionality
 -Select from PCB mic, line in, or external mic using keystrokes
 -Pass thru audio to headphone
 -Record/Stop Record to SD card using keystrokes

*/

#include <Tympan_Library.h>

//definitions for SD writing
#define PRINT_OVERRUN_WARNING 1   //set to 1 to print a warning that the there's been a hiccup in the writing to the SD.

//set the sample rate and block size
const float sample_rate_Hz    = 44100.0f ;  //Allowed values: 8000, 11025, 16000, 22050, 24000, 32000, 44100, 44118, or 48000 Hz
const int audio_block_samples = 128;     //do not make bigger than AUDIO_BLOCK_SAMPLES from AudioStream.h (which is 128)
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples);

//setup the  Tympan using the default settings
Tympan                    myTympan(TympanRev::F, audio_settings);   //do TympanRev::D or E or F
// Define audio objects
AudioInputI2S_F32             i2s_in(audio_settings);       //This is the Teensy Audio library's built-in 4-channel I2S class
AudioMixer4_F32               mixerIn(audio_settings);
AudioSDWriter_F32             audioSDWriter(audio_settings); //this is stereo by default
AudioOutputI2S_F32            i2s_out(audio_settings);      //This is the Teensy Audio library's built-in 4-channel I2S class
//AUDIO CONNECTIONS
//Connect Left & Right Input Channel to Left and Right SD card queue
AudioConnection_F32           patchcord1(i2s_in, 0, audioSDWriter, 0);   //connect Raw audio to queue (to enable SD writing)
AudioConnection_F32           patchcord2(i2s_in, 1, audioSDWriter, 1);  //connect Raw audio to queue (to enable SD writing)
//Connect Left & Right Input to Audio Output Left and Right
AudioConnection_F32           patchcord3(i2s_in, 0, i2s_out, 0);    //echo audio to output
AudioConnection_F32           patchcord4(i2s_in, 1, i2s_out, 1);    //echo audio to output


String overall_name = String("InputSelect.ino");

//control display and serial interaction
bool enable_printCPUandMemory = false;
void togglePrintMemoryAndCPU(void) {  enable_printCPUandMemory = !enable_printCPUandMemory; }; 
void setPrintMemoryAndCPU(bool state) { enable_printCPUandMemory = state;};
bool enable_printAveSignalLevels = false, printAveSignalLevels_as_dBSPL = false;
void togglePrintAveSignalLevels(bool as_dBSPL) {
  enable_printAveSignalLevels = !enable_printAveSignalLevels;
  printAveSignalLevels_as_dBSPL = as_dBSPL;
};

//set the recording configuration
const int config_pcb_mics = 20;
const int config_mic_jack = 21;
const int config_line_in_SE  = 22;

int current_config = 0;

float input_gain_dB = 0.0;        //gain on the microphone
float vol_knob_gain_dB = 0.0;      //speaker output gain

void setConfiguration(int config) { 
  switch (config) {
    case config_pcb_mics:
      //Select Input
      myTympan.inputSelect(TYMPAN_INPUT_ON_BOARD_MIC); // use the on-board microphones
      //Set mixer gain (right channel only; left is already sent to SD)
      mixerIn.gain(0,0.0); 
      mixerIn.gain(1,1.0);  
      //Set input gain to 0dB
      input_gain_dB = 0.0;
      myTympan.setInputGain_dB(input_gain_dB);
      //Store configuration
      current_config = config_pcb_mics;
      break;
      
    case config_mic_jack:
      //Select Input
      myTympan.inputSelect(TYMPAN_INPUT_JACK_AS_MIC); // use the mic jack/
      //Set mixer gain (right channel only; left is already sent to SD)
      mixerIn.gain(0,0.0); 
      mixerIn.gain(1,1.0);  
      //Set input gain to 0dB
      input_gain_dB = 0.0;
      myTympan.setInputGain_dB(input_gain_dB);
      //Store configuration
      current_config = config_mic_jack;
      break;
      
    case config_line_in_SE:      
      //Select Input
      myTympan.inputSelect(TYMPAN_INPUT_LINE_IN); // use the line-input through holes
      //Set mixer gain (right channel only; left is already sent to SD)
      mixerIn.gain(0,0.0); 
      mixerIn.gain(1,1.0);  
      //Set input gain to 0dB
      input_gain_dB = 0.0;
      myTympan.setInputGain_dB(input_gain_dB);
      //Store configuration
      current_config = config_line_in_SE;
      break;
      
      default:
        break;
  }
}



// ///////////////// Main setup() and loop() as required for all Arduino programs
void setup() {
  Serial.begin(115200); delay(1000);
  Serial.print(overall_name); Serial.println(": setup():...");
  Serial.print("Sample Rate (Hz): "); Serial.println(audio_settings.sample_rate_Hz);
  Serial.print("Audio Block Size (samples): "); Serial.println(audio_settings.audio_block_samples);

  //allocate the dynamic memory for audio processing blocks
  AudioMemory_F32(50,audio_settings); //I can only seem to allocate 400 blocks
  Serial.println("Setup: memory allocated.");
  //activate the Tympan audio hardware
  myTympan.enable(); // activate AIC
  //Set the state of the LEDs
  myTympan.setRedLED(HIGH);
  myTympan.setGreenLED(LOW);

  //prepare the SD writer for the format that we want and any error statements
  audioSDWriter.setSerial(&myTympan);         //the library will print any error info to this serial stream (note that myTympan is also a serial stream)
  audioSDWriter.setNumWriteChannels(2);       //this is also the built-in defaullt, but you could change it to 4 (maybe?), if you wanted 4 channels.
  int ret_val = audioSDWriter.setWriteDataType(AudioSDWriter::WriteDataType::INT16);  //this is the built-in default, but here you could change it to FLOAT32
  Serial.print("setup: setWriteDataType() yielded return code: ");  Serial.print(ret_val); 
  if (ret_val < 0) { Serial.println(": ERROR!"); } else {  Serial.println(": OK");  }

  //End of setup
  Serial.println("Setup: complete."); 
  printHelp();

}

void loop() {
  //update the memory and CPU usage...if enough time has passed
  if (enable_printCPUandMemory) myTympan.printCPUandMemory(millis(),3000); //print every 3000 msec

  manageSerial();
//service the SD recording
serviceSD();
}


// ///////////////// Servicing routines
void serviceSD(void) {
  static int max_max_bytes_written = 0; //for timing diagnotstics
  static int max_bytes_written = 0; //for timing diagnotstics
  static int max_dT_micros = 0; //for timing diagnotstics
  static int max_max_dT_micros = 0; //for timing diagnotstics

  unsigned long dT_micros = micros();  //for timing diagnotstics
  int bytes_written = audioSDWriter.serviceSD();
  dT_micros = micros() - dT_micros;  //timing calculation

  if ( bytes_written > 0 ) {
    
    max_bytes_written = max(max_bytes_written, bytes_written);
    max_dT_micros = max((int)max_dT_micros, (int)dT_micros);
   
    if (dT_micros > 10000) {  //if the write took a while, print some diagnostic info
      
      max_max_bytes_written = max(max_bytes_written,max_max_bytes_written);
      max_max_dT_micros = max(max_dT_micros, max_max_dT_micros);
      
      Serial.print("serviceSD: bytes written = ");
      Serial.print(bytes_written); Serial.print(", ");
      Serial.print(max_bytes_written); Serial.print(", ");
      Serial.print(max_max_bytes_written); Serial.print(", ");
      Serial.print("dT millis = "); 
      Serial.print((float)dT_micros/1000.0,1); Serial.print(", ");
      Serial.print((float)max_dT_micros/1000.0,1); Serial.print(", "); 
      Serial.print((float)max_max_dT_micros/1000.0,1);Serial.print(", ");      
      Serial.println();
      max_bytes_written = 0;
      max_dT_micros = 0;     
    }
      
    //print a warning if there has been an SD writing hiccup
    if (PRINT_OVERRUN_WARNING) {
      //if (audioSDWriter.getQueueOverrun() || i2s_in.get_isOutOfMemory()) {
      if (i2s_in.get_isOutOfMemory()) {
        float approx_time_sec = ((float)(millis()-audioSDWriter.getStartTimeMillis()))/1000.0;
        if (approx_time_sec > 0.1) {
          myTympan.print("SD Write Warning: there was a hiccup in the writing.");//  Approx Time (sec): ");
          myTympan.println(approx_time_sec );
        }
      }
    }
    i2s_in.clear_isOutOfMemory();
  }
}
// here's a function to change the volume settings. Control via Serial
void incrementInputGain(float increment_dB) {
  input_gain_dB += increment_dB;
  myTympan.setInputGain_dB(input_gain_dB);
}

//Increment Headphone Output Volume
void incrementHeadphoneVol(float increment_dB) {
  vol_knob_gain_dB += increment_dB;
  myTympan.volume_dB(vol_knob_gain_dB); // headphone amplifier.  -63.6 to +24 dB in 0.5dB steps.
}



const float INCREMENT_INPUT_GAIN_DB = 2.5f;  
const float INCREMENT_HEADPHONE_GAIN_DB = 2.5f;  


void printHelp(void) {
  Serial.println();
  Serial.println("SerialManager Help: Available Commands:");
  Serial.println("   h: Print this help");
  Serial.println("   c: Start printing of CPU and Memory usage");
  Serial.println("   C: Stop printing of CPU and Memory usage");  
  Serial.println("   r: begin recording");
  Serial.println("   s: stop recording");
  Serial.println("   b/n/m: Switch between (b) on-PCB mic; (n) line-input (single-ended); (m) mic jack");
  Serial.print  ("   i/o: Decrease/Increase the Input Gain by "); myTympan.print(INCREMENT_INPUT_GAIN_DB); myTympan.println(" dB");
  Serial.print  ("   j/k: Decrease/Increase the Headphone Volume by "); myTympan.print(INCREMENT_HEADPHONE_GAIN_DB); myTympan.println(" dB");

  Serial.println();
}

//switch yard to determine the desired action
void manageSerial() {
  if(Serial.available()){
    char inChar = Serial.read();
    switch (inChar) {
      case 'h': case '?':
        printHelp(); break;
      case 'c':
        Serial.println("Received: start CPU reporting");
        setPrintMemoryAndCPU(true);
        break;
      case 'C':
        Serial.println("Received: stop CPU reporting");
        setPrintMemoryAndCPU(false);
        break;
      case 'o':
        incrementInputGain(INCREMENT_INPUT_GAIN_DB);
        printGainSettings();
        break;
      case 'i':   
        incrementInputGain(-INCREMENT_INPUT_GAIN_DB);
        printGainSettings();  
        break;
      case 'k':
        incrementHeadphoneVol(INCREMENT_HEADPHONE_GAIN_DB);
        printGainSettings();
        break;
      case 'j':  
        incrementHeadphoneVol(-INCREMENT_HEADPHONE_GAIN_DB);
        printGainSettings();  
        break;
      case 'b':
        Serial.println("Command Received: switch to on-PCB mic; InputGain = 0dB");
        setConfiguration(config_pcb_mics);
        break;
      case 'n':
        Serial.println("Command Received: switch to line-input through-holes (Single-Ended); InputGain = 0dB");
        setConfiguration(config_line_in_SE);
        break;
      case 'm':
        Serial.println("Command Received: switch to external mic; InputGain = 0dB");
        setConfiguration(config_mic_jack);
        break;

      case 'r':
      myTympan.println("Received: begin SD recording");
      audioSDWriter.startRecording();
      if (audioSDWriter.getState() == AudioSDWriter_F32::STATE::RECORDING) {
        //Serial.print("SD Filename = "); Serial.println(audioSDWriter.getCurrentFilename());
      } else {
        Serial.print("SD Failed to start recording."); 
      }
      break;
    case 's':
      myTympan.println("Received: stop SD recording");
      audioSDWriter.stopRecording();
      break;

    default:
      break;
    }
  }
}

void printGainSettings(void) {
  Serial.print("Vol Knob = "); 
  Serial.print(vol_knob_gain_dB,1);
  Serial.print(", Input PGA = "); 
  Serial.print(input_gain_dB,1);
  Serial.println();
}

Hi Joel,

You asked two different questions:

  1. how best to switch among inputs,
  2. can we dynamically patch and unpatch different audio processing blocks (like the SD card).

I’ll address your second question first…can one patch and unpatch on the fly?

If your mind is thinking along the lines of creating and destroying AuioConnection_F32 patchcords on-the-fly, then no, you can’t do that. Instead, though, there are two other possibilities that might be even easier:

  • Simply disable the audio processing class. You can often call the method myAudioObject.setActive(false); to disable the processing. It’s a blunt hammer, but it ought to keep our object from doing anything.
  • Or, you can stop the audio flowing into your audio object(s). If it receives no audio, it’ll do no work.

The way that you stop the audio from flowing into your class is to use a Tympan class like AudioSwitch4. This class takes one audio input stream and can switch it out to one of 4 possible outputs. You hang your audio processing class from one of its outputs. If you want your processing to run, you tell the AudioSwtich to send the audio to its output that’s connected to your class. If you want your processing to stop, you tell AudioSwitch to send the audio to one of its other outputs.

I use this approach all the time.

Here’s an overly-simplified example…let’s say that sometimes I want to apply a filter and sometimes I don’t. I use an audio switch to create two pathways, one path with the filter and one path without the filter:

// /////// Create all the audio objects
AudioInputI2S_F32      i2s_in;
AudioSwitch4_F32       audioSwitch;  //switches one input to up to 4 outputs
AudioFilterBiquad_F32  myFilter;
AudioMixer4_F32        audioMixer;    //rejoins up to 4 inputs into 1 output
AudioOutputI2S_F32     i2s_out;

// ////// Create all the AudioConnection patchcords

//get the audio coming in
AudioConnection_F32   patchcord10(i2s_in, 0, audioSwich,0);      //connect to input of switch

// Audio Path #0: The path with the filter
AudioConnection_F32   patchcord20(audioSwich, 0, myFilter, 0);  //switch's output #0 goes to the filter
AudioConnection_F32   patchcord21(myFilter, 0, audioMixer, 0);  //filter goes to input #0 if the mixer

// Audio Path #1: The path without the filter 
AudioConnection_F32   patchcord30(audioSwich, 1, audioMixer, 1);  //switch's output #1 goes straight to the mixer

//send the audio out
AudioConnection_F32 patchcord40(audioMixer, 0, i2s_out, 0);  //connect to the output

With your audio objects created and connected, you now need to choose which of your two paths you want to use. First, let’s use the Arduino setup() function to set the default behavior as using the filter:

//configure the default configuration of the switch and mixer
setup() {
    // include all the usual stuff that's in the setup() function for the Tympan

   // set the switch to default at startup to connecting the filter
   audioSwitch.setChannel(0);  //default to channel zero (the pathway with the filter)

  // set the audio mixer to receive from all possible channels equally
  audioMixer.setDefaultValues(1.0); //sets it to sum all channels with the same gain (1.0);
}

Then, once you’re running, you can choose to bypass the filter at any time by simply re-routing the audio by calling audioSwitch.setChannel(1);. This pushes the audio to path #1, which skips the filter.

To return to using the filter, you’d call audioSwtich.setChannel(0); so that the audio goes out the switch’s output #0 and goes through the filter again.

Back and forth, back and forth, as often as you’d like!

Chip

Hey Joel,

Now back to your first question, how to select among many inputs. Some of the inputs are the analog inputs built into our audio interface chip, and some of your inputs are other sources, like a synthesized audio, or playback from a WAV file.

In this case, the switching between the analog inputs is handled by (must be handled by) using myTympan.inputSelect(), just like you showed in the code that copied in your post. That’s pretty straight-forward.

As for also allowing for synthesized audio and for an SD player, you’ll have to use the switching idea again. Unlike my previous reply, however, the class AudioSwitch_F32 is not what you want. That class takes 1 input stream and switches it among 4 output streams. You want the opposite; you want 4 (or 3) input streams to be switched to 1 output stream. For this, you can use the trusty old AudioMixer4_F32, but instead of having it “mix”, you’d ask it to “switch”.

Here’s what you might do:

// Create all the audio objects
AudioInputI2S_F32        i2s_in;
AudioSynthWaveform_F32   myWaveform;
AudioSdPlayer_F32        mySdPlayer;
AudioMixer4_F32          audioMixer;
AudioOutputI2S_F32     i2s_out;

// Create the audio connections
AudioConnection_F32    patchcord10(i2s_in, 0, audioMixer, 0);      // audioMixer path #0
AudioConnection_F32    patchcord11(myWaveform, 0, audioMixer, 1);  //audioMixer path #1
AudioConnection_F32    patchcord12(mySdPlayer, 0, audioMixer, 2);  //audioMixer path #2
AudioConnection_F32    patchcord20(audioMixer, 0, i2s_out, 0)      //audio goes to the output!

With your connections all set up, now you can choose which is the active path.

  • You want the analog inputs? Call audioMixer.switchChannel(0);
  • You want the synthesized signal? Call audioMixer.switchChannel(1);
  • You want the SD player? Call audioMixer.switchchannel(2);

I hope this helps.

Chip