diff --git a/AudioSwitch_F32.cpp b/AudioSwitch_F32.cpp new file mode 100644 index 0000000..aa41e13 --- /dev/null +++ b/AudioSwitch_F32.cpp @@ -0,0 +1,26 @@ +/* + * AudioSwitch_F32.cpp see AudioSwitch_F32.h for notes + * Created: Chip Audette, OpenAudio, April 2019 + * MIT License. use at your own risk. +*/ +#include "AudioSwitch_F32.h" + +void AudioSwitch4_OA_F32::update(void) { + audio_block_f32_t *out=NULL; + + out = receiveReadOnly_f32(0); + if (!out) return; + + AudioStream_F32::transmit(out,outputChannel); //just output to the one channel + AudioStream_F32::release(out); +} + +void AudioSwitch8_OA_F32::update(void) { + audio_block_f32_t *out=NULL; + + out = receiveReadOnly_f32(0); + if (!out) return; + + AudioStream_F32::transmit(out,outputChannel); + AudioStream_F32::release(out); +} diff --git a/AudioSwitch_F32.h b/AudioSwitch_F32.h new file mode 100644 index 0000000..e6e606c --- /dev/null +++ b/AudioSwitch_F32.h @@ -0,0 +1,68 @@ +/* + * AudioSwitch_F32.h + * + * AudioSwitch4 + * Created: Chip Audette, OpenAudio, April 2019 + * Purpose: Switch one input to 4 outputs, which should only trigger one of the 4 + * audio processing paths (therebys saving computation on paths that you don't + * care about). + * Assumes floating-point data. + * From Tympan Library, to OpenAudio_ArduinoLibrary June 2020, add 8 position class. + * + * This processes a single stream of audio data (ie, it is mono) + * + * MIT License. use at your own risk. +*/ + +#ifndef AUDIOSWITCH_OA_F32_H +#define AUDIOSWITCH_OA_F32_H + +#include + +class AudioSwitch4_OA_F32 : public AudioStream_F32 { +//GUI: inputs:1, outputs:4 //this line used for automatic generation of GUI node +//GUI: shortName:Switch4 +public: + AudioSwitch4_OA_F32() : AudioStream_F32(1, inputQueueArray) { setDefaultValues(); } + AudioSwitch4_OA_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1, inputQueueArray) { setDefaultValues(); } + + void setDefaultValues(void) { + outputChannel = 0; + } + + virtual void update(void); + + int setChannel(unsigned int channel) { + if (channel >= 4 || channel < 0) return outputChannel; //invalid! stick with previous channel + return outputChannel = channel; + } + + private: + audio_block_f32_t *inputQueueArray[1]; + int outputChannel; +}; + +class AudioSwitch8_OA_F32 : public AudioStream_F32 { +//GUI: inputs:1, outputs:8 //this line used for automatic generation of GUI node +//GUI: shortName:Switch8 +public: + AudioSwitch8_OA_F32() : AudioStream_F32(1, inputQueueArray) { setDefaultValues(); } + AudioSwitch8_OA_F32(const AudioSettings_F32 &settings) : AudioStream_F32(1, inputQueueArray) { setDefaultValues(); } + + void setDefaultValues(void) { + outputChannel = 0; + } + + virtual void update(void); + + int setChannel(unsigned int channel) { + if (channel >= 8 || channel < 0) return outputChannel; //invalid! stick with previous channel + return outputChannel = channel; + } + + private: + audio_block_f32_t *inputQueueArray[1]; + int outputChannel; +}; + +#endif diff --git a/FFT_F32.h b/FFT_F32.h new file mode 100644 index 0000000..22326d6 --- /dev/null +++ b/FFT_F32.h @@ -0,0 +1,175 @@ + +/* + * FFT_F32 + * + * Purpose: Encapsulate the ARM floating point FFT/IFFT functions + * in a way that naturally handles the radix2 vs radix4 + * constraints on FFT size inherent to the particular + * version of the ARM CMSIS FFT functions included with + * the Teensy libraries. + * + * Created: Chip Audette (openaudio.blogspot.com) + * Jan-Jul 2017 + * + * License: MIT License + */ + +#ifndef _FFT_h +#define _FFT_h + +#include //for Serial +//include +#include + +class FFT_F32 +{ + public: + FFT_F32(void) {}; + FFT_F32(const int _N_FFT) { + setup(_N_FFT); + } + FFT_F32(const int _N_FFT, const int _is_IFFT) { + setup(_N_FFT, _is_IFFT); + } + ~FFT_F32(void) { delete window; }; //destructor + + virtual int setup(const int _N_FFT) { + int _is_IFFT = 0; + return setup(_N_FFT,_is_IFFT); + } + virtual int setup(const int _N_FFT, const int _is_IFFT) { + if (!is_valid_N_FFT(_N_FFT)) { + Serial.println(F("FFT_F32: *** ERROR ***")); + Serial.print(F(" : Cannot use N_FFT = ")); Serial.println(N_FFT); + Serial.print(F(" : Must be power of 2 between 16 and 2048")); + return -1; + } + N_FFT = _N_FFT; + is_IFFT = _is_IFFT; + + if ((N_FFT == 16) || (N_FFT == 64) || (N_FFT == 256) || (N_FFT == 1024)) { + arm_cfft_radix4_init_f32(&fft_inst_r4, N_FFT, is_IFFT, 1); //FFT + is_rad4 = 1; + } else { + arm_cfft_radix2_init_f32(&fft_inst_r2, N_FFT, is_IFFT, 1); //FFT + } + + //allocate window + window = new float[N_FFT]; + if (is_IFFT) { + useRectangularWindow(); //default to no windowing for IFFT + } else { + useHanningWindow(); //default to windowing for FFT + } + return N_FFT; + } + static int is_valid_N_FFT(const int N) { + if ((N == 16) || (N == 32) || (N == 64) || (N == 128) || + (N == 256) || (N == 512) || (N==1024) || (N==2048)) { + return 1; + } else { + return 0; + } + } + + virtual void useRectangularWindow(void) { + flag__useWindow = 0; + if (window != NULL) { + for (int i=0; i < N_FFT; i++) window[i] = 1.0; + } + //if (Serial) { Serial.print("FFT_F32: useRectangularWindow. flag__useWindow = "); Serial.println(flag__useWindow); } + } + virtual void useHanningWindow(void) { + flag__useWindow = 1; + if (window != NULL) { + //Serial.print("FFT_F32: useHanningWindow, N="); + //Serial.print(N_FFT); Serial.print(": "); + for (int i=0; i < N_FFT; i++) { + window[i] = 0.5*(1.0 - cosf(2.0*M_PI*(float)i/((float)(N_FFT-1)))); + //Serial.print(window[i]); + //Serial.print(" "); + } + //Serial.println(); + } + //if (Serial) { Serial.print("FFT_F32: useHanningWindow. flag__useWindow = "); Serial.println(flag__useWindow); } + } + + virtual void applyWindowToRealPartOfComplexVector(float32_t *complex_2N_buffer) { + for (int i=0; i < N_FFT; i++) { + complex_2N_buffer[2*i] *= window[i]; + } + } + virtual void applyWindowToRealVector(float32_t *real_N_buffer) { + for (int i=0; i < N_FFT; i++) { + real_N_buffer[i] *= window[i]; + } + } + + virtual void execute(float32_t *complex_2N_buffer) { //interleaved [real,imaginary], total length is 2*N_FFT + if (N_FFT == 0) return; + + //apply window before FFT (if it is an FFT and not IFFT) + if ((!is_IFFT) && (flag__useWindow)) applyWindowToRealPartOfComplexVector(complex_2N_buffer); + + //do the FFT + if (is_rad4) { + arm_cfft_radix4_f32(&fft_inst_r4, complex_2N_buffer); + } else { + arm_cfft_radix2_f32(&fft_inst_r2, complex_2N_buffer); + } + + //apply window after FFT (if it is an IFFT and not FFT) + if ((is_IFFT) && (flag__useWindow)) applyWindowToRealPartOfComplexVector(complex_2N_buffer); + + } + + virtual void rebuildNegativeFrequencySpace(float *complex_2N_buffer) { + //create the negative frequency space via complex conjugate of the positive frequency space + + //int ind_nyquist_bin = N_FFT/2; //nyquist is neither positive nor negative + //int targ_ind = ind_nyquist_bin+1; //negative frequencies start start one above nyquist + //for (int source_ind = ind_nyquist_bin-1; source_ind > 0; source_ind--) { //exclude the 0'th bin as DC is neither positive nor negative + //complex_2N_buffer[2*targ_ind] = complex_2N_buffer[2*source_ind]; //real + //complex_2N_buffer[2*targ_ind+1] = -complex_2N_buffer[2*source_ind+1]; //imaginary. negative makes it the complex conjugate, which is what we want for the neg freq space + //targ_ind++; + //} + + int targ_ind = 0; + for (int source_ind = 1; source_ind < (N_FFT/2-1); source_ind++) { + targ_ind = N_FFT - source_ind; + complex_2N_buffer[2*targ_ind] = complex_2N_buffer[2*source_ind]; //real + complex_2N_buffer[2*targ_ind+1] = -complex_2N_buffer[2*source_ind+1]; //imaginary. negative makes it the complex conjugate, which is what we want for the neg freq space + } + + } + virtual int getNFFT(void) { return N_FFT; }; + int get_flagUseWindow(void) { return flag__useWindow; }; + + private: + int N_FFT=0; + int is_IFFT=0; + int is_rad4=0; + float *window; + int flag__useWindow=0; + arm_cfft_radix4_instance_f32 fft_inst_r4; + arm_cfft_radix2_instance_f32 fft_inst_r2; + +}; + + +class IFFT_F32 : public FFT_F32 // is basically the same as FFT, so let's inherent FFT +{ + public: + IFFT_F32(void) : FFT_F32() {}; + IFFT_F32(const int _N_FFT): FFT_F32(_N_FFT) { + //constructor + IFFT_F32::setup(_N_FFT); //call FFT's setup routine + } + virtual int setup(const int _N_FFT) { + const int _is_IFFT = 1; + return FFT_F32::setup(_N_FFT, _is_IFFT); //call FFT's setup routine + } + //all other functions are in FFT +}; + +#endif diff --git a/FFT_Overlapped_F32.cpp b/FFT_Overlapped_F32.cpp new file mode 100644 index 0000000..91c7274 --- /dev/null +++ b/FFT_Overlapped_F32.cpp @@ -0,0 +1,70 @@ + +#include "FFT_Overlapped_F32.h" + +void FFT_Overlapped_F32::execute(audio_block_f32_t *block, float *complex_2N_buffer) //results returned inc omplex_2N_buffer +{ + int targ_ind; + + //get a pointer to the latest data + //audio_block_f32_t *block = AudioStream_F32::receiveReadOnly_f32(); + if (!block) return; + + //add a claim to this block. As a result, be sure that this function issues a "release()". + //Also, be sure that the calling function issues its own release() to release its claim. + block->ref_count++; + + //shuffle all of input data blocks in preperation for this latest processing + AudioStream_F32::release(buff_blocks[0]); //release the oldest one...this is the release the corresponds to the claim above + for (int i = 1; i < N_BUFF_BLOCKS; i++) buff_blocks[i - 1] = buff_blocks[i]; + buff_blocks[N_BUFF_BLOCKS - 1] = block; //append the newest input data to the complex_buffer blocks + + //copy all input data blocks into one big block...the big block is interleaved [real,imaginary] + targ_ind = 0; + //Serial.print("Overlapped_FFT_F32: N_BUFF_BLOCKS = "); Serial.print(N_BUFF_BLOCKS); + //Serial.print(", audio_block_samples = "); Serial.println(audio_block_samples); + for (int i = 0; i < N_BUFF_BLOCKS; i++) { + for (int j = 0; j < audio_block_samples; j++) { + complex_2N_buffer[2*targ_ind] = buff_blocks[i]->data[j]; //real + complex_2N_buffer[2*targ_ind+1] = 0; //imaginary + targ_ind++; + } + } + //call the FFT...windowing of the data happens in the FFT routine, if configured + myFFT.execute(complex_2N_buffer); +} + +audio_block_f32_t* IFFT_Overlapped_F32::execute(float *complex_2N_buffer) { //real results returned through audio_block_f32_t + + //Serial.print("Overlapped_IFFT_F32: N_BUFF_BLOCKS = "); Serial.print(N_BUFF_BLOCKS); + //Serial.print(", audio_block_samples = "); Serial.println(audio_block_samples); + + + //call the IFFT...any follow-up windowing is handdled in the IFFT routine, if configured + myIFFT.execute(complex_2N_buffer); + + + //prepare for the overlap-and-add for the output + audio_block_f32_t *temp_buff = buff_blocks[0]; //hold onto this one for a moment...it'll get overwritten later + for (int i = 1; i < N_BUFF_BLOCKS; i++) buff_blocks[i - 1] = buff_blocks[i]; //shuffle the output data blocks + buff_blocks[N_BUFF_BLOCKS - 1] = temp_buff; //put the oldest output buffer back in the list + + //do overlap and add with previously computed data + int output_count = 0; + for (int i = 0; i < (N_BUFF_BLOCKS-1); i++) { //Notice that this loop does NOT do the last block. That's a special case after. + for (int j = 0; j < audio_block_samples; j++) { + buff_blocks[i]->data[j] += complex_2N_buffer[2*output_count]; //add only the real part into the previous results + output_count++; + } + } + + //now write in the newest data into the last block, overwriting any garbage that might have existed there + for (int j = 0; j < audio_block_samples; j++) { + buff_blocks[N_BUFF_BLOCKS - 1]->data[j] = complex_2N_buffer[2*output_count]; //overwrite with the newest data + output_count++; + } + + //send the oldest data. Don't issue the release command here because we will release it the next time through this routine + //transmit(buff_blocks[0]); //don't release this buffer because we re-use it every time this is called + return buff_blocks[0]; //send back the pointer to this audio block...but don't release it because we'll re-use it here +}; + diff --git a/FFT_Overlapped_F32.h b/FFT_Overlapped_F32.h new file mode 100644 index 0000000..3cc9528 --- /dev/null +++ b/FFT_Overlapped_F32.h @@ -0,0 +1,177 @@ +/* + * FFT_Overrlapped_F32 + * + * Purpose: Encapsulate the ARM floating point FFT/IFFT functions + * in a way that naturally interfaces to my float32 + * extension of the Teensy Audio Library. + * + * Provides functionality to do overlapped FFT/IFFT where + * each audio block is a fraction (1, 1/2, 1/4) of the + * totaly FFT length. This class handles all of the + * data shuffling to composite the previous data blocks + * with the current data block to provide the full FFT. + * Does similar data shuffling (overlapp-add) for IFFT. + * + * Created: Chip Audette (openaudio.blogspot.com) + * Jan-Jul 2017 + * + * Typical Usage as FFT: + * + * //setup the audio stuff + * float sample_rate_Hz = 44100.0; //define sample rate + * int audio_block_samples = 32; //define size of audio blocks + * AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); + * // ... continue creating all of your Audio Processing Blocks ... + * + * // within a custom audio processing algorithm that you've written + * // you'd create the FFT and IFFT elements + * int NFFT = 128; //define length of FFT that you want (multiple of audio_block_samples) + * FFT_Overrlapped_F32 FFT_obj(audio_settings,NFFT); //Creare FFT object + * FFT_Overrlapped_F32 IFFT_obj(audio_settings,NFFT); //Creare IFFT object + * float complex_2N_buffer[2*NFFT]; //create buffer to hold the FFT output + * + * // within your own algorithm's "update()" function (which is what + * // is called automatically by the Teensy Audio Libarary approach + * // to audio processing), you can execute the FFT and IFFT + * + * // First, get the audio and convert to frequency-domain using an FFT + * audio_block_f32_t *in_audio_block = AudioStream_F32::receiveReadOnly_f32(); + * FFT_obj.execute(in_audio_block, complex_2N_buffer); //output is in complex_2N_buffer + * AudioStream_F32::release(in_audio_block); //We just passed ownership to FFT_obj, so release it here. + * + * // Next do whatever processing you'd like on the frequency domain data + * // that is held in complex_2N_buffer + * + * // Finally, you can convert back to the time domain via IFFT + * audio_block_f32_t *out_audio_block = IFFT_obj.execute(complex_2N_buffer); + * //note that the "out_audio_block" is mananged by IFFT_obj, so don't worry about releasing it. + * + * License: MIT License + */ +#ifndef _FFT_Overlapped_F32_h +#define _FFT_Overlapped_F32_h + +#include "AudioStream_F32.h" +#include +#include "FFT_F32.h" +//#include "utility/dspinst.h" //copied from analyze_fft256.cpp. Do we need this? + +// set the max amount of allowed overlap...some number larger than you'll want to use +#define MAX_N_BUFF_BLOCKS 32 //32 blocks x 16 sample blocks enables NFFT = 512, if the Teensy could keep up. + +class FFT_Overlapped_Base_F32 { //handles all the data structures for the overlapping stuff. Doesn't care if FFT or IFFT + public: + FFT_Overlapped_Base_F32(void) {}; + ~FFT_Overlapped_Base_F32(void) { + if (N_BUFF_BLOCKS > 0) { + for (int i = 0; i < N_BUFF_BLOCKS; i++) { + if (buff_blocks[i] != NULL) AudioStream_F32::release(buff_blocks[i]); + } + } + if (complex_buffer != NULL) delete complex_buffer; + } + + virtual int setup(const AudioSettings_F32 &settings, const int _N_FFT) { + int N_FFT; + + ///choose valid _N_FFT + if (!FFT_F32::is_valid_N_FFT(_N_FFT)) { + Serial.println(F("FFT_Overlapped_Base_F32: *** ERROR ***")); + Serial.print(F(" : N_FFT ")); Serial.print(_N_FFT); + Serial.print(F(" is not allowed. Try a power of 2 between 16 and 2048")); + N_FFT = -1; + return N_FFT; + } + + //how many buffers will compose each FFT? + audio_block_samples = settings.audio_block_samples; + N_BUFF_BLOCKS = _N_FFT / audio_block_samples; //truncates! + N_BUFF_BLOCKS = max(1,min(MAX_N_BUFF_BLOCKS,N_BUFF_BLOCKS)); + + //what does the fft length actually end up being? + N_FFT = N_BUFF_BLOCKS * audio_block_samples; + + //allocate memory for buffers...this is dynamic allocation. Always dangerous. + complex_buffer = new float32_t[2*N_FFT]; //should I check to see if it was successfully allcoated? + + //initialize the blocks for holding the previous data + for (int i = 0; i < N_BUFF_BLOCKS; i++) { + buff_blocks[i] = AudioStream_F32::allocate_f32(); + clear_audio_block(buff_blocks[i]); + } + + return N_FFT; + } + virtual int getNFFT(void) = 0; + virtual int getNBuffBlocks(void) { return N_BUFF_BLOCKS; } + + protected: + int N_BUFF_BLOCKS = 0; + int audio_block_samples; + + audio_block_f32_t *buff_blocks[MAX_N_BUFF_BLOCKS]; + float32_t *complex_buffer; + + void clear_audio_block(audio_block_f32_t *block) { + for (int i = 0; i < block->length; i++) block->data[i] = 0.f; + } +}; + +class FFT_Overlapped_F32: public FFT_Overlapped_Base_F32 +{ + public: + //constructors + FFT_Overlapped_F32(void): FFT_Overlapped_Base_F32() {}; + FFT_Overlapped_F32(const AudioSettings_F32 &settings): FFT_Overlapped_Base_F32() { } + FFT_Overlapped_F32(const AudioSettings_F32 &settings, const int _N_FFT): FFT_Overlapped_Base_F32() { + setup(settings,_N_FFT); + } + + virtual int setup(const AudioSettings_F32 &settings, const int _N_FFT) { + int N_FFT = FFT_Overlapped_Base_F32::setup(settings, _N_FFT); + + //setup the FFT routines + N_FFT = myFFT.setup(N_FFT); + return N_FFT; + } + + virtual void execute(audio_block_f32_t *block, float *complex_2N_buffer); + virtual int getNFFT(void) { return myFFT.getNFFT(); }; + FFT_F32* getFFTObject(void) { return &myFFT; }; + virtual void rebuildNegativeFrequencySpace(float *complex_2N_buffer) { myFFT.rebuildNegativeFrequencySpace(complex_2N_buffer); } + + private: + FFT_F32 myFFT; +}; + + +class IFFT_Overlapped_F32: public FFT_Overlapped_Base_F32 +{ + public: + //constructors + IFFT_Overlapped_F32(void): FFT_Overlapped_Base_F32() {}; + IFFT_Overlapped_F32(const AudioSettings_F32 &settings): FFT_Overlapped_Base_F32() { } + IFFT_Overlapped_F32(const AudioSettings_F32 &settings, const int _N_FFT): FFT_Overlapped_Base_F32() { + setup(settings,_N_FFT); + } + + virtual int setup(const AudioSettings_F32 &settings, const int _N_FFT) { + int N_FFT = FFT_Overlapped_Base_F32::setup(settings, _N_FFT); + + //setup the FFT routines + N_FFT = myIFFT.setup(N_FFT); + return N_FFT; + } + + virtual audio_block_f32_t* execute(float *complex_2N_buffer); + virtual int getNFFT(void) { return myIFFT.getNFFT(); }; + IFFT_F32* getFFTObject(void) { return &myIFFT; }; + IFFT_F32* getIFFTObject(void) { return &myIFFT; }; + private: + IFFT_F32 myIFFT; +}; + + + + +#endif diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index f432507..fb5a5cd 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -29,3 +29,5 @@ #include "analyze_peak_f32.h" #include "analyze_rms_f32.h" // #include "control_tlv320aic3206.h" collides much with Teensy Audio +#include "AudioSwitch_F32.h" +#include "FFT_Overlapped_F32.h" diff --git a/examples/Switches_float/Switches_float.ino b/examples/Switches_float/Switches_float.ino new file mode 100644 index 0000000..ff406f2 --- /dev/null +++ b/examples/Switches_float/Switches_float.ino @@ -0,0 +1,55 @@ +/* Switches_float.ino Bob Larkin 20 June 2020 + * + * Cascade an 4 position switch with an 8 position to swicth between + * an RMS and a Peak measure of a sine wave. This is a trivial + * example of switching. Normally, the switched paths would be involved + * enough to benefit from saving computations and DC power. An example + * that is more like this is <<<<<<<<<<<<<<<<<<<<<<<<<< + * + */ + +#include "Audio.h" +#include "OpenAudio_ArduinoLibrary.h" + +// To work with T4.0 the I2S routine outputs 16-bit integer (I16). Then +// use Audette I16 to F32 convert. Same below for output, in reverse. +AudioInputI2S in1; +AudioSynthWaveformSine_F32 sine1; +AudioSwitch4_OA_F32 switch1; +AudioSwitch8_OA_F32 switch2; +AudioAnalyzePeak_F32 peak1; +AudioAnalyzeRMS_F32 rms1; +AudioConnection_F32 connect1(sine1, 0, switch1, 0); +AudioConnection_F32 connect2(switch1, 2, switch2, 0); +AudioConnection_F32 connect3(switch2, 1, peak1, 0); +AudioConnection_F32 connect4(switch2, 6, rms1, 0); + + +void setup(void) { + float32_t gain; + AudioMemory(5); + AudioMemory_F32(8); + + Serial.begin(1); delay(1000); + sine1.frequency(1000.0f); + sine1.amplitude(0.2f); + // switch1 is always set to channel 2 to feed switch2. + switch1.setChannel(2); +} + +void loop(void) { + // Lets switch back and forth between RMS and Peak measurements. + // This is switch2, channels 6 and 1. + switch2.setChannel(6); // RMS measurement + delay(1000); + if (rms1.available() ) { + Serial.print("RMS ="); + Serial.println(rms1.read(), 6); + } + switch2.setChannel(1); // Peak-to-peak measurement + delay(1000); + if (peak1.available() ) { + Serial.print("P-P ="); + Serial.println(peak1.readPeakToPeak(), 6);} + delay(1000); +} diff --git a/readme.md b/readme.md index 382e26b..40c5d4f 100644 --- a/readme.md +++ b/readme.md @@ -13,6 +13,8 @@ OpenAudio Library for Teensy 8-Disabled control_tlv320aic3206.h, .cpp by .xxxfile type. These collide with Teensy I16 Audio. 9-Added new analyze_peak_f32.h and .cpp that parallel similar classes in the Teensy I16 library. 10-Added new analyze_rms_f32.h and .cpp that parallel similar classes in the Teensy I16 library. +11-Moved AudioSwitch_F32 from Tympan, added 8-channel version. +12-Added /examples/Switches_float.ino for 4 and 8 channel switches. **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.