From 224611d2091d23bb6e38db71be0f940be8f06699 Mon Sep 17 00:00:00 2001 From: Chip Audette Date: Wed, 2 Oct 2024 16:21:33 -0400 Subject: [PATCH] Example: CalibrateAnalogIO: add auto-stepping of test freq (and add other stuff) --- .../CalibrateAnalogIO/AudioProcessing.h | 91 +++++++++++++------ .../CalibrateAnalogIO/CalibrateAnalogIO.ino | 52 +++++++---- .../CalibrateAnalogIO/SerialManager.h | 38 +++++--- examples/02-Utility/CalibrateAnalogIO/State.h | 19 +++- .../CalibrateAnalogIO/TestToneControl.h | 76 ++++++++++++++++ 5 files changed, 211 insertions(+), 65 deletions(-) create mode 100644 examples/02-Utility/CalibrateAnalogIO/TestToneControl.h diff --git a/examples/02-Utility/CalibrateAnalogIO/AudioProcessing.h b/examples/02-Utility/CalibrateAnalogIO/AudioProcessing.h index 13c5424..3bcd98d 100644 --- a/examples/02-Utility/CalibrateAnalogIO/AudioProcessing.h +++ b/examples/02-Utility/CalibrateAnalogIO/AudioProcessing.h @@ -2,11 +2,11 @@ #define _AudioProcessing_h AudioInputI2S_F32 i2s_in(audio_settings); //Digital audio input from the ADC -AudioCalcLevel_F32 calcInputLevel_L(audio_settings); //use this to measure the input signal level -AudioCalcLevel_F32 calcInputLevel_R(audio_settings); //use this to measure the input signal level +AudioCalcLeq_F32 calcInputLevel_L(audio_settings); //use this to measure the input signal level +AudioCalcLeq_F32 calcInputLevel_R(audio_settings); //use this to measure the input signal level AudioSynthWaveform_F32 sineWave(audio_settings); //generate a synthetic sine wave AudioSwitchMatrix4_F32 outputSwitchMatrix(audio_settings); //use this to route the sine wave to L, R, or Both -AudioCalcLevel_F32 calcOutputLevel(audio_settings); //use this to measure the input signal level +AudioCalcLeq_F32 calcOutputLevel(audio_settings); //use this to measure the input signal level AudioSDWriter_F32 audioSDWriter(audio_settings); //this is stereo by default AudioOutputI2S_F32 i2s_out(audio_settings); //Digital audio output to the DAC. Should always be last. @@ -79,26 +79,17 @@ void setConfiguration(int config) { } } -float setCalcLevelTimeConstants(float time_const_sec) { - time_const_sec = max(time_const_sec,0.0001); - calcInputLevel_L.setTimeConst_sec(time_const_sec); - myState.calcLevel_timeConst_sec = calcInputLevel_L.getTimeConst_sec(); - calcInputLevel_R.setTimeConst_sec(myState.calcLevel_timeConst_sec); - calcOutputLevel.setTimeConst_sec(myState.calcLevel_timeConst_sec); - return myState.calcLevel_timeConst_sec; +void printInputConfiguration(void) { + switch (myState.input_source) { + case State::INPUT_PCBMICS: + Serial.print("PCB Mics"); break; + case State::INPUT_JACK_MIC: + Serial.print("Input Jack as Mic"); break; + case State::INPUT_JACK_LINE: + Serial.print("Input Jack as Line-In"); break; + } } -float setFrequency_Hz(float freq_Hz) { - myState.sine_freq_Hz = constrain(freq_Hz, 125.0f/4.0, 16000.0f); //constrain between 32 Hz and 16 kHz - sineWave.frequency(myState.sine_freq_Hz); - return myState.sine_freq_Hz; -} - -float setAmplitude(float amplitude) { - myState.sine_amplitude = constrain(amplitude, 0.0, 1.0); //constrain between 0.0 and 1.0 - sineWave.amplitude(myState.sine_amplitude); - return myState.sine_amplitude; -} int setOutputChan(int chan) { const int inputChanForSineWave = 0; @@ -111,23 +102,67 @@ int setOutputChan(int chan) { Serial.println("setOutputChan: chan = " + String(chan) + ". Setting to LEFT..."); outputSwitchMatrix.setInputToOutput(inputChanForSineWave,outputChanLeft); //sine to left output outputSwitchMatrix.setInputToOutput(inputChanForMutedOutput,outputChanRight); //mute the right output - return chan; //return early + myState.output_chan = chan; + break; case State::OUT_RIGHT: Serial.println("setOutputChan: chan = " + String(chan) + ". Setting to RIGHT..."); outputSwitchMatrix.setInputToOutput(inputChanForMutedOutput,outputChanLeft); //mute the left output outputSwitchMatrix.setInputToOutput(inputChanForSineWave,outputChanRight); //sine to right output - return chan; //return early + myState.output_chan = chan; + break; case State::OUT_BOTH: Serial.println("setOutputChan: chan = " + String(chan) + ". Setting to BOTH..."); outputSwitchMatrix.setInputToOutput(inputChanForSineWave,outputChanLeft); //sine to left output outputSwitchMatrix.setInputToOutput(inputChanForSineWave,outputChanRight); //sine to right output - return chan; //return early + myState.output_chan = chan; + break; default: - Serial.println("setOutputChan: *** WARNING ***: chan = " + String(chan) + " not recognized. Muting all output channels..."); - outputSwitchMatrix.setInputToOutput(inputChanForMutedOutput,outputChanLeft); //sine to left output - outputSwitchMatrix.setInputToOutput(inputChanForMutedOutput,outputChanRight); //sine to right output + Serial.println("setOutputChan: *** WARNING ***: chan = " + String(chan) + " not recognized. Setting to BOTH..."); + outputSwitchMatrix.setInputToOutput(inputChanForSineWave,outputChanLeft); //sine to left output + outputSwitchMatrix.setInputToOutput(inputChanForSineWave,outputChanRight); //sine to right output + myState.output_chan = chan; + break; } - return -1; //indicates an error + return myState.output_chan; } +void printOutputChannel(void) { + switch (myState.output_chan) { + case State::OUT_LEFT: + Serial.print("LEFT"); break; + case State::OUT_RIGHT: + Serial.print("RIGHT"); break; + case State::OUT_BOTH: + Serial.print("BOTH"); break; + } +} + +// /////////// Functions for configuring the audio processing + +float setCalcLevelTimeWindow(float time_window_sec) { + time_window_sec = max(time_window_sec,0.0001); //don't windows that are too short + calcInputLevel_L.setTimeWindow_sec(time_window_sec); //try to set the window time + myState.calcLevel_timeWindow_sec = calcInputLevel_L.getTimeWindow_sec(); //ask what time was actually set + + //set the rest of the time windows + calcInputLevel_R.setTimeWindow_sec(myState.calcLevel_timeWindow_sec); + calcOutputLevel.setTimeWindow_sec(myState.calcLevel_timeWindow_sec); + return myState.calcLevel_timeWindow_sec; +} + +float setFrequency_Hz(float freq_Hz) { + const float min_freq_Hz = 125.0/8; + const float max_freq_Hz = 20000.0; + myState.sine_freq_Hz = constrain(freq_Hz, min_freq_Hz, max_freq_Hz); //constrain between 32 Hz and 16 kHz + sineWave.frequency(myState.sine_freq_Hz); + return myState.sine_freq_Hz; +} + +float setAmplitude(float amplitude) { + myState.sine_amplitude = constrain(amplitude, 0.0, 1.0); //constrain between 0.0 and 1.0 + sineWave.amplitude(myState.sine_amplitude); + return myState.sine_amplitude; +} + + #endif \ No newline at end of file diff --git a/examples/02-Utility/CalibrateAnalogIO/CalibrateAnalogIO.ino b/examples/02-Utility/CalibrateAnalogIO/CalibrateAnalogIO.ino index 58d8163..cd7c78e 100644 --- a/examples/02-Utility/CalibrateAnalogIO/CalibrateAnalogIO.ino +++ b/examples/02-Utility/CalibrateAnalogIO/CalibrateAnalogIO.ino @@ -10,6 +10,10 @@ You can measure the voltage of this signal using a volt meter. You can then feed the signal back into the Tympan's input (pink) jack to perform the calibration. + This example includes a mode for automatically stepping up through many test + frequencies. See the options available in the Serial Monitor menu. Send + an "h" (without quotes) in the Serial Monitor to see the help menu. + This example also lets you record the audio to the SD card for analysis on your PC or Mac. @@ -30,17 +34,21 @@ #include "State.h" //set the sample rate and block size -const float sample_rate_Hz = 44100.0f ; //24000 or 44117 or 96000 (or other frequencies in the table in AudioOutputI2S_F32) -const int audio_block_samples = 128; //do not make bigger than AUDIO_BLOCK_SAMPLES from AudioStream.h (which is 128) Must be 128 for SD recording. +const float sample_rate_Hz = 44100.0f ; //24000 or 44117 or 96000 (or other frequencies in the table in AudioOutputI2S_F32) +const int audio_block_samples = 128; //do not make bigger than AUDIO_BLOCK_SAMPLES from AudioStream.h (which is 128) Must be 128 for SD recording. AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); // Create the audio objects and then connect them -Tympan myTympan(TympanRev::F,audio_settings); //do TympanRev::D or TympanRev::E or TympanRev::F +Tympan myTympan(TympanRev::F,audio_settings); //do TympanRev::D or TympanRev::E or TympanRev::F #include "AudioProcessing.h" //see here for audio objects, connections, and configuration functions -// /////////// Create classes for controlling the system, espcially via USB Serial and via the App -SerialManager serialManager; //create the serial manager for real-time control (via USB or App) -State myState(&audio_settings, &myTympan, &serialManager); //keeping one's state is useful for the App's GUI +// Create classes for controlling the system, espcially via USB Serial and via the App +SerialManager serialManager; //create the serial manager for real-time control (via USB or App) +State myState(&audio_settings, &myTympan, &serialManager); //keeping one's state is useful for the App's GUI + +// Be aware that this calibration program can output steady tones or it can automatically step the tones across frequencies +#include "TestToneControl.h" //see here for the relevant functions for managing the changing test tones + // ///////////////// Main setup() and loop() as required for all Arduino programs @@ -64,20 +72,19 @@ void setup() { //Select the input that we will use setConfiguration(myState.input_source); //use the default that is written in State.h - //Setup the sine wave - setFrequency_Hz(myState.sine_freq_Hz); - setAmplitude(myState.sine_amplitude); - setOutputChan(myState.output_chan); - //Setup the level measuring - setCalcLevelTimeConstants(myState.calcLevel_timeConst_sec); - Serial.println("Setup: Setting time constants for level measurements to " + String(myState.calcLevel_timeConst_sec,4) + " sec"); + setCalcLevelTimeWindow(myState.calcLevel_timeWindow_sec); + Serial.println("Setup: Setting time windows for level measurements to " + String(myState.calcLevel_timeWindow_sec,4) + " sec"); //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. Serial.println("Setup: SD configured for " + String(audioSDWriter.getNumWriteChannels()) + " channels."); - + + //Setup the output signal + setOutputChan(myState.output_chan); + switchTestToneMode(myState.current_test_tone_mode); + //End of setup Serial.println("Setup: complete."); serialManager.printHelp(); @@ -99,11 +106,20 @@ void loop() { //periodically print the CPU and Memory Usage if (myState.enable_printCpuToUSB) myState.printCPUandMemory(millis(), 3000); //print every 3000msec (method is built into TympanStateBase.h, which myState inherits from) - //periodically print the signal levels - if (myState.flag_printInputLevelToUSB) printInputSignalLevels(millis(),1000); //print every 1000 msec + //check to see what test mode we're in + if (myState.current_test_tone_mode == State::TONE_MODE_STEPPED_FREQUENCY) { //are we doing stepped tones? + + //update the stepped dones + serviceSteppedToneTest(millis()); //update the tones + + } else { //we are not doing stepped tones + //periodically print the signal levels + if (myState.flag_printInputLevelToUSB) printInputSignalLevels(millis(),1000); //print every 1000 msec + + //periodically print the output levels + if (myState.flag_printOutputLevelToUSB) printOutputSignalLevels(millis(),1000); //print every 1000 msec + } - //periodically print the output levels - if (myState.flag_printOutputLevelToUSB) printOutputSignalLevels(millis(),1000); //print every 1000 msec } //end loop() // //////////////////////////////////////// Other functions diff --git a/examples/02-Utility/CalibrateAnalogIO/SerialManager.h b/examples/02-Utility/CalibrateAnalogIO/SerialManager.h index f978115..b7657c7 100644 --- a/examples/02-Utility/CalibrateAnalogIO/SerialManager.h +++ b/examples/02-Utility/CalibrateAnalogIO/SerialManager.h @@ -19,6 +19,10 @@ extern float setFrequency_Hz(float freq_Hz); extern float setAmplitude(float amplitude); extern int setOutputChan(int chan); extern bool enablePrintMemoryAndCPU(bool _enable); +extern void printInputConfiguration(void); +extern void printOutputChannel(void); +extern void printTestToneMode(void); +extern int switchTestToneMode(int new_mode); class SerialManager : public SerialManagerBase { // see Tympan_Library for SerialManagerBase for more functions! public: @@ -35,19 +39,17 @@ class SerialManager : public SerialManagerBase { // see Tympan_Library for Ser void SerialManager::printHelp(void) { Serial.println("SerialManager Help: Available Commands:"); - Serial.println(" General: No Prefix"); - Serial.println(" h: Print this help"); - Serial.print( " w: Switch Input to PCB Mics"); if (myState.input_source==State::INPUT_PCBMICS) {Serial.println(" (active)");} else { Serial.println(); } - Serial.print( " W: Switch Input to MicIn on the Pink Jack"); if (myState.input_source==State::INPUT_JACK_MIC) {Serial.println(" (active)");} else { Serial.println(); } - Serial.print( " e: Switch Input to LineIn on the Pink Jack"); if (myState.input_source==State::INPUT_JACK_LINE) {Serial.println(" (active)");} else { Serial.println(); } - Serial.println(" i/I: Input: Increase or decrease input gain (current = " + String(myState.input_gain_dB,1) + " dB)"); - Serial.println(" f/F: Sine: Increase or decrease sine frequency (current = " + String(myState.sine_freq_Hz,1) + " Hz"); - Serial.println(" a/A: Sine: Increase or decrease sine amplitude (current = " + String(20*log10(myState.sine_amplitude)-3.0,1) + " dBFS (output), " + String(myState.sine_amplitude,3) + " amplitude)"); - Serial.println(" 1/2/3: Sine: Output to left (1), right (2), or both (3)"); - Serial.print( " p/P: Printing: start/Stop printing of input signal levels"); if (myState.flag_printInputLevelToUSB) {Serial.println(" (active)");} else { Serial.println(" (off)"); } - Serial.print( " o/O: Printing: start/Stop printing of output signal levels"); if (myState.flag_printOutputLevelToUSB) {Serial.println(" (active)");} else { Serial.println(" (off)"); } - Serial.println(" r: SD: Start recording audio to SD card"); - Serial.println(" s: SD: Stop recording audio to SD card"); + Serial.println("General: No Prefix"); + Serial.println(" h: Print this help"); + Serial.print( " w/W/E: Input: Use PCB mics (w), jack as mic (W), jack as line-in (e) (current = "); printInputConfiguration(); Serial.println(")"); + Serial.println(" i/I: Input: Increase or decrease input gain (current = " + String(myState.input_gain_dB,1) + " dB)"); + Serial.println(" f/F: Sine: Increase or decrease steady-tone frequency (current = " + String(myState.sine_freq_Hz,1) + " Hz)"); + Serial.println(" a/A: Sine: Increase or decrease sine amplitude (current = " + String(20*log10(myState.sine_amplitude)-3.0,1) + " dB re: output FS = " + String(myState.sine_amplitude,3) + " amplitude)"); + Serial.print( " 1/2/3: Sine: Output to left (1), right (2), or both (3) (current = "); printOutputChannel(); Serial.println(")"); + Serial.print( " t/T: TestMode: Switch between steady tone (t) or stepped-tone (T) modes (current = "); printTestToneMode(); Serial.println(")"); + Serial.print( " p/P: Printing: start/Stop printing of input signal levels"); if (myState.flag_printInputLevelToUSB) {Serial.println(" (active)");} else { Serial.println(" (off)"); } + Serial.print( " o/O: Printing: start/Stop printing of output signal levels"); if (myState.flag_printOutputLevelToUSB) {Serial.println(" (active)");} else { Serial.println(" (off)"); } + Serial.println(" r/s: SD: Start recording (r) or stop (s) audio to SD card"); Serial.println(); } @@ -88,11 +90,11 @@ bool SerialManager::processCharacter(char c) { //this is called by SerialManager Serial.println("SerialManager: Decreased input gain to: " + String(myState.input_gain_dB) + " dB"); break; case 'f': - setFrequency_Hz(myState.sine_freq_Hz*frequencyIncrement_factor); + setFrequency_Hz(max(125.0/4,min(16000.0,myState.sine_freq_Hz*frequencyIncrement_factor))); //octave-based incrementing. Limit freuqencies to 31.25 Hz -> 16 kHz Serial.println("SerialManager: increased tone frequency to " + String(myState.sine_freq_Hz)); break; case 'F': - setFrequency_Hz(myState.sine_freq_Hz/frequencyIncrement_factor); + setFrequency_Hz(max(125.0/4,min(16000.0,myState.sine_freq_Hz/frequencyIncrement_factor))); //octave-based incrementing. Limit freuqencies to 31.25 Hz -> 16 kHz Serial.println("SerialManager: decreased tone frequency to " + String(myState.sine_freq_Hz)); break; case 'a': @@ -115,6 +117,12 @@ bool SerialManager::processCharacter(char c) { //this is called by SerialManager Serial.println("SerialManager: outputing sine to both left and right"); setOutputChan(State::OUT_BOTH); break; + case 't': + switchTestToneMode(State::TONE_MODE_STEADY); + break; + case 'T': + switchTestToneMode(State::TONE_MODE_STEPPED_FREQUENCY); + break; case 'p': myState.flag_printInputLevelToUSB = true; Serial.println("SerialManager: enabled printing of the input signal levels"); diff --git a/examples/02-Utility/CalibrateAnalogIO/State.h b/examples/02-Utility/CalibrateAnalogIO/State.h index 13dc5dd..a1ef2e0 100644 --- a/examples/02-Utility/CalibrateAnalogIO/State.h +++ b/examples/02-Utility/CalibrateAnalogIO/State.h @@ -33,16 +33,27 @@ class State : public TympanStateBase_UI { // look in TympanStateBase or TympanSt //set highpass filter on ADC float adc_hp_cutoff_Hz = 31.25f; - //variables related to the sine wave - float sine_freq_Hz = 1000.0f; - float sine_amplitude = sqrt(2)*sqrt(pow(10.0,0.1*-20.0)); //default amplitude (-20dBFS converted to linear and then converted from RMS to amplitude) + //variables related to the test tone mode + enum Test_Tone_Mode { TONE_MODE_MUTE, TONE_MODE_STEADY, TONE_MODE_STEPPED_FREQUENCY}; + int current_test_tone_mode = TONE_MODE_STEADY; + float stepped_test_start_freq_Hz = 20.0; //starting frequency for stepped tones + float stepped_test_end_freq_Hz = 20000.0; //ending frequency for stepped tones + float stepped_test_step_Hz = 50.0; //frequency inrement for stepped tones + float stepped_test_step_dur_sec = 1.0; //duration at each step + unsigned long stepped_test_next_change_millis = 0UL; + + //variables related to the current sine wave + float default_sine_freq_Hz = 1000.0; //used whenever switching the TONE_MODE + float default_sine_amplitude = sqrt(2.0)*sqrt(pow(10.0,0.1*-20.0)); //(-20dBFS converted to linear and then converted from RMS to amplitude) + float sine_freq_Hz = 1000.0; //the current frequency + float sine_amplitude = 0.0; //the current amplitude //variables relating to the output switching of the sine wave enum OUT_CHAN { OUT_LEFT=0, OUT_RIGHT=1, OUT_BOTH=9}; int output_chan = OUT_BOTH; //variables associated with level measurement - float calcLevel_timeConst_sec = 0.125f; + float calcLevel_timeWindow_sec = 0.125f; //Put different gain settings (except those in the compressors) here to ease the updating of the GUI float input_gain_dB = 0.0; //gain of the hardware PGA in the AIC diff --git a/examples/02-Utility/CalibrateAnalogIO/TestToneControl.h b/examples/02-Utility/CalibrateAnalogIO/TestToneControl.h new file mode 100644 index 0000000..ebb4b2a --- /dev/null +++ b/examples/02-Utility/CalibrateAnalogIO/TestToneControl.h @@ -0,0 +1,76 @@ + +#ifndef _TestToneControl_h +#define _TestToneControl_h + +//These should existing in the main *.ino file +extern void printInputSignalLevels(unsigned long, unsigned long); +extern void printOutputSignalLevels(unsigned long, unsigned long); + +float setSteppedTone(float freq_Hz) { + Serial.println("setSteppedTone: setting frequency to " + String(freq_Hz) + " Hz"); + setFrequency_Hz(freq_Hz); //setFrequency is in "AudioProcessing.h" + setAmplitude(myState.default_sine_amplitude); //setAmplitude is in "AudioProcessing.h" + myState.stepped_test_next_change_millis = millis() + (unsigned long)(1000.0*myState.stepped_test_step_dur_sec); + return myState.sine_freq_Hz; +} + +int switchTestToneMode(int new_mode) { + switch (new_mode) { + case State::TONE_MODE_MUTE: + myState.current_test_tone_mode = new_mode; + Serial.print("switchTestToneMode: Switching to "); printTestToneMode(); Serial.println(); + setFrequency_Hz(myState.default_sine_freq_Hz); //setFrequency is in "AudioProcessing.h" + setAmplitude(0.0); //mute (setAmplitude is in "AudioProcessing.h") + break; + case State::TONE_MODE_STEADY: + myState.current_test_tone_mode = new_mode; + Serial.print("switchTestToneMode: Switching to "); printTestToneMode(); Serial.println(); + setFrequency_Hz(myState.default_sine_freq_Hz); //setFrequency is in "AudioProcessing.h" + setAmplitude(myState.default_sine_amplitude); //setAmplitude is in "AudioProcessing.h" + break; + case State::TONE_MODE_STEPPED_FREQUENCY: + myState.current_test_tone_mode = new_mode; + Serial.print("switchTestToneMode: Switching to "); printTestToneMode(); Serial.println(); + setSteppedTone(myState.stepped_test_start_freq_Hz); + break; + default: + Serial.println("switchTestToneMode: *** WARNING ***: mode = " + String(new_mode) + " not recognized. Ignoring."); + break; + } + return myState.current_test_tone_mode; +} + + +void serviceSteppedToneTest(unsigned long current_millis) { + if ( myState.current_test_tone_mode != State::TONE_MODE_STEPPED_FREQUENCY) return; + if (current_millis >= myState.stepped_test_next_change_millis) { + //compute the next test tone frequency + float new_freq_Hz = myState.sine_freq_Hz + myState.stepped_test_step_Hz; + + //we're about to make a change, so print the current levels + if (myState.flag_printOutputLevelToUSB) printOutputSignalLevels(millis(),0); //the "0" forces it to print now, regardless of the current time (millis()) + if (myState.flag_printInputLevelToUSB) printInputSignalLevels(millis(),0); //the "0" forces it to print now, regardless of the current time (millis()) + + //is this frequency allowed or are we done? + if (new_freq_Hz > myState.stepped_test_end_freq_Hz) { + Serial.println("serviceStppedToneTest: stepped-tone test completed!"); + switchTestToneMode(State::TONE_MODE_MUTE); + } else { + //set the sine wave to the new frequency + setSteppedTone(new_freq_Hz); + } + } +} + +void printTestToneMode(void) { + switch (myState.current_test_tone_mode) { + case State::TONE_MODE_MUTE: + Serial.print("Muted"); break; + case State::TONE_MODE_STEADY: + Serial.print("Steady Tone"); break; + case State::TONE_MODE_STEPPED_FREQUENCY: + Serial.print("Stepped Tones"); break; + } +} + +#endif \ No newline at end of file