| 19 - SCL | 19 - SCL | ||||
| 22 - TX | 22 - TX | ||||
| 23 - LRCLK | 23 - LRCLK | ||||
| */ | */ | ||||
| //#include <arm_math.h> | //#include <arm_math.h> | ||||
| AudioInputI2S audioInput; // audio shield: mic or line-in | AudioInputI2S audioInput; // audio shield: mic or line-in | ||||
| // Use the fast FIR filter for left and right channels | |||||
| AudioFilterFIR myFilterL(true); | |||||
| AudioFilterFIR myFilterR(true); | |||||
| AudioFilterFIR myFilterL; | |||||
| AudioFilterFIR myFilterR; | |||||
| AudioOutputI2S audioOutput; // audio shield: headphones & line-out | AudioOutputI2S audioOutput; // audio shield: headphones & line-out | ||||
| // Create Audio connections between the components | // Create Audio connections between the components | ||||
| void setup() { | void setup() { | ||||
| Serial.begin(9600); | Serial.begin(9600); | ||||
| while (!Serial) ; | |||||
| delay(3000); | |||||
| delay(300); | |||||
| pinMode(PASSTHRU_PIN,INPUT_PULLUP); | |||||
| pinMode(FILTER_PIN,INPUT_PULLUP); | |||||
| pinMode(PASSTHRU_PIN, INPUT_PULLUP); | |||||
| pinMode(FILTER_PIN, INPUT_PULLUP); | |||||
| // It doesn't work properly with any less than 8 | |||||
| // allocate memory for the audio library | |||||
| AudioMemory(8); | AudioMemory(8); | ||||
| audioShield.enable(); | audioShield.enable(); | ||||
| Serial.println(") is grounded"); | Serial.println(") is grounded"); | ||||
| } | } | ||||
| // Initialize the filter | // Initialize the filter | ||||
| myFilterL.begin(fir_list[0].coeffs,fir_list[0].num_coeffs); | |||||
| myFilterR.begin(fir_list[0].coeffs,fir_list[0].num_coeffs); | |||||
| myFilterL.begin(fir_list[0].coeffs, fir_list[0].num_coeffs); | |||||
| myFilterR.begin(fir_list[0].coeffs, fir_list[0].num_coeffs); | |||||
| Serial.println("setup done"); | Serial.println("setup done"); | ||||
| } | } | ||||
| // audio volume | // audio volume | ||||
| int volume = 0; | int volume = 0; | ||||
| unsigned long last_time = millis(); | unsigned long last_time = millis(); | ||||
| void loop() | void loop() | ||||
| { | { | ||||
| // Volume control | // Volume control | ||||
| int n = analogRead(15); | int n = analogRead(15); | ||||
| if (n != volume) { | if (n != volume) { | ||||
| volume = n; | volume = n; | ||||
| audioShield.volume((float)n / 1023); | |||||
| //uncomment this line if your audio shield has the volume pot | |||||
| //audioShield.volume((float)n / 1023); | |||||
| } | } | ||||
| // update the two buttons | // update the two buttons | ||||
| // If the passthru button is pushed, save the current | // If the passthru button is pushed, save the current | ||||
| // filter index and then switch the filter to passthru | // filter index and then switch the filter to passthru | ||||
| if(b_passthru.fallingEdge()) { | |||||
| if (b_passthru.fallingEdge()) { | |||||
| old_idx = fir_idx; | old_idx = fir_idx; | ||||
| myFilterL.begin(FIR_PASSTHRU,0); | |||||
| myFilterR.begin(FIR_PASSTHRU,0); | |||||
| myFilterL.begin(FIR_PASSTHRU, 0); | |||||
| myFilterR.begin(FIR_PASSTHRU, 0); | |||||
| } | } | ||||
| // If passthru button is released, restore previous filter | // If passthru button is released, restore previous filter | ||||
| if(b_passthru.risingEdge()) { | |||||
| if (b_passthru.risingEdge()) { | |||||
| if(old_idx != -1) { | if(old_idx != -1) { | ||||
| myFilterL.begin(fir_list[fir_idx].coeffs,fir_list[fir_idx].num_coeffs); | |||||
| myFilterR.begin(fir_list[fir_idx].coeffs,fir_list[fir_idx].num_coeffs); | |||||
| myFilterL.begin(fir_list[fir_idx].coeffs, fir_list[fir_idx].num_coeffs); | |||||
| myFilterR.begin(fir_list[fir_idx].coeffs, fir_list[fir_idx].num_coeffs); | |||||
| } | } | ||||
| old_idx = -1; | old_idx = -1; | ||||
| } | } | ||||
| // switch to next filter in the list | // switch to next filter in the list | ||||
| if(b_filter.fallingEdge()) { | |||||
| if (b_filter.fallingEdge()) { | |||||
| fir_idx++; | fir_idx++; | ||||
| if(fir_list[fir_idx].num_coeffs == 0)fir_idx = 0; | |||||
| myFilterL.begin(fir_list[fir_idx].coeffs,fir_list[fir_idx].num_coeffs); | |||||
| myFilterR.begin(fir_list[fir_idx].coeffs,fir_list[fir_idx].num_coeffs); | |||||
| if (fir_list[fir_idx].num_coeffs == 0) fir_idx = 0; | |||||
| myFilterL.begin(fir_list[fir_idx].coeffs, fir_list[fir_idx].num_coeffs); | |||||
| myFilterR.begin(fir_list[fir_idx].coeffs, fir_list[fir_idx].num_coeffs); | |||||
| } | } | ||||
| if(1) { | |||||
| // With fast_fir | |||||
| // Proc = 18 (18), Mem = 4 (6) | |||||
| if(millis() - last_time >= 5000) { | |||||
| // print information about resource usage | |||||
| // Proc = 18 (18), Mem = 4 (5) | |||||
| if (millis() - last_time >= 2500) { | |||||
| Serial.print("Proc = "); | Serial.print("Proc = "); | ||||
| Serial.print(AudioProcessorUsage()); | Serial.print(AudioProcessorUsage()); | ||||
| Serial.print(" ("); | Serial.print(" ("); | ||||
| Serial.println(")"); | Serial.println(")"); | ||||
| last_time = millis(); | last_time = millis(); | ||||
| } | } | ||||
| } | |||||
| } | } |
| #include "filters.h" | #include "filters.h" | ||||
| short low_pass[NUM_COEFFS] = { | short low_pass[NUM_COEFFS] = { | ||||
| #include "lopass_4000_44100.h" | |||||
| #include "lopass_1000_44100.h" | |||||
| }; | }; | ||||
| short band_pass[NUM_COEFFS] = { | short band_pass[NUM_COEFFS] = { |
| #include "filter_fir.h" | #include "filter_fir.h" | ||||
| void AudioFilterFIR::begin(short *cp, int n_coeffs) | |||||
| { | |||||
| // pointer to coefficients | |||||
| coeff_p = cp; | |||||
| // Initialize FIR instances for the left and right channels | |||||
| if(coeff_p && (coeff_p != FIR_PASSTHRU)) { | |||||
| arm_fir_init_q15(&l_fir_inst, n_coeffs, coeff_p, | |||||
| &l_StateQ15[0], AUDIO_BLOCK_SAMPLES); | |||||
| } | |||||
| } | |||||
| // This has the same effect as begin(NULL,0); | |||||
| void AudioFilterFIR::stop(void) | |||||
| void AudioFilterFIR::update(void) | |||||
| { | { | ||||
| coeff_p = NULL; | |||||
| } | |||||
| audio_block_t *block, *b_new; | |||||
| block = receiveReadOnly(); | |||||
| if (!block) return; | |||||
| void AudioFilterFIR::update(void) | |||||
| { | |||||
| audio_block_t *block,*b_new; | |||||
| // If there's no coefficient table, give up. | // If there's no coefficient table, give up. | ||||
| if(coeff_p == NULL) return; | |||||
| if (coeff_p == NULL) { | |||||
| release(block); | |||||
| return; | |||||
| } | |||||
| // do passthru | // do passthru | ||||
| if (coeff_p == FIR_PASSTHRU) { | if (coeff_p == FIR_PASSTHRU) { | ||||
| // Just passthrough | // Just passthrough | ||||
| block = receiveReadOnly(0); | |||||
| if(block) { | |||||
| transmit(block,0); | |||||
| release(block); | |||||
| } | |||||
| transmit(block); | |||||
| release(block); | |||||
| return; | return; | ||||
| } | } | ||||
| // Left Channel | |||||
| block = receiveReadOnly(0); | |||||
| // get a block for the FIR output | // get a block for the FIR output | ||||
| b_new = allocate(); | b_new = allocate(); | ||||
| if (block && b_new) { | |||||
| if (arm_fast) { | |||||
| arm_fir_fast_q15(&l_fir_inst, (q15_t *)block->data, | |||||
| (q15_t *)b_new->data, AUDIO_BLOCK_SAMPLES); | |||||
| } else { | |||||
| arm_fir_q15(&l_fir_inst, (q15_t *)block->data, | |||||
| (q15_t *)b_new->data, AUDIO_BLOCK_SAMPLES); | |||||
| } | |||||
| transmit(b_new,0); // send the FIR output | |||||
| if (b_new) { | |||||
| arm_fir_fast_q15(&fir_inst, (q15_t *)block->data, | |||||
| (q15_t *)b_new->data, AUDIO_BLOCK_SAMPLES); | |||||
| transmit(b_new); // send the FIR output | |||||
| release(b_new); | |||||
| } | } | ||||
| if (block) release(block); | |||||
| if (b_new) release(b_new); | |||||
| release(block); | |||||
| } | } | ||||
| // Indicates that the code should just pass through the audio | // Indicates that the code should just pass through the audio | ||||
| // without any filtering (as opposed to doing nothing at all) | // without any filtering (as opposed to doing nothing at all) | ||||
| #define FIR_PASSTHRU ((short *) 1) | |||||
| #define FIR_PASSTHRU ((const short *) 1) | |||||
| #define FIR_MAX_COEFFS 120 | |||||
| #define FIR_MAX_COEFFS 200 | |||||
| class AudioFilterFIR : public AudioStream | class AudioFilterFIR : public AudioStream | ||||
| { | { | ||||
| public: | public: | ||||
| AudioFilterFIR(const boolean a_f): AudioStream(1,inputQueueArray), | |||||
| arm_fast(a_f), coeff_p(NULL) { | |||||
| AudioFilterFIR(void): AudioStream(1,inputQueueArray), coeff_p(NULL) { | |||||
| } | |||||
| void begin(const short *cp, int n_coeffs) { | |||||
| coeff_p = cp; | |||||
| // Initialize FIR instance (ARM DSP Math Library) | |||||
| if (coeff_p && (coeff_p != FIR_PASSTHRU) && n_coeffs <= FIR_MAX_COEFFS) { | |||||
| arm_fir_init_q15(&fir_inst, n_coeffs, (q15_t *)coeff_p, | |||||
| &StateQ15[0], AUDIO_BLOCK_SAMPLES); | |||||
| } | |||||
| } | |||||
| void end(void) { | |||||
| coeff_p = NULL; | |||||
| } | } | ||||
| void begin(short *coeff_p,int n_coeffs); | |||||
| virtual void update(void); | virtual void update(void); | ||||
| void stop(void); | |||||
| private: | private: | ||||
| audio_block_t *inputQueueArray[1]; | audio_block_t *inputQueueArray[1]; | ||||
| // arm state arrays and FIR instances for left and right channels | |||||
| // the state arrays are defined to handle a maximum of MAX_COEFFS | |||||
| // coefficients in a filter | |||||
| q15_t l_StateQ15[AUDIO_BLOCK_SAMPLES + FIR_MAX_COEFFS]; | |||||
| // Whether to use the fast arm FIR code | |||||
| const boolean arm_fast; | |||||
| // pointer to current coefficients or NULL or FIR_PASSTHRU | // pointer to current coefficients or NULL or FIR_PASSTHRU | ||||
| short *coeff_p; | |||||
| arm_fir_instance_q15 l_fir_inst; | |||||
| const short *coeff_p; | |||||
| // ARM DSP Math library filter instance | |||||
| arm_fir_instance_q15 fir_inst; | |||||
| q15_t StateQ15[AUDIO_BLOCK_SAMPLES + FIR_MAX_COEFFS]; | |||||
| }; | }; | ||||
| #endif | #endif |
| <tr class=top><th>Port</th><th>Purpose</th></tr> | <tr class=top><th>Port</th><th>Purpose</th></tr> | ||||
| <tr class=odd><td align=center></td><td></td></tr> | <tr class=odd><td align=center></td><td></td></tr> | ||||
| </table> | </table> | ||||
| <!--<h3>Parameters</h3> | |||||
| <table class=doc align=center cellpadding=3> | |||||
| <tr class=top><th>Name</th><th>Type</th><th>Function</th></tr> | |||||
| <tr class=odd><td align=center></td><td>Integer</td><td></td></tr> | |||||
| </table> | |||||
| <p>Extra description... Section only present if object has params</p>--> | |||||
| <h3>Functions</h3> | <h3>Functions</h3> | ||||
| <p class=func><span class=keyword>begin</span>(delayBuffer, length, n_chorus);</p> | <p class=func><span class=keyword>begin</span>(delayBuffer, length, n_chorus);</p> | ||||
| <p class=desc>blah blah blah blah | <p class=desc>blah blah blah blah | ||||
| <tr class=top><th>Port</th><th>Purpose</th></tr> | <tr class=top><th>Port</th><th>Purpose</th></tr> | ||||
| <tr class=odd><td align=center></td><td></td></tr> | <tr class=odd><td align=center></td><td></td></tr> | ||||
| </table> | </table> | ||||
| <!--<h3>Parameters</h3> | |||||
| <table class=doc align=center cellpadding=3> | |||||
| <tr class=top><th>Name</th><th>Type</th><th>Function</th></tr> | |||||
| <tr class=odd><td align=center></td><td>Integer</td><td></td></tr> | |||||
| </table> | |||||
| <p>Extra description... Section only present if object has params</p>--> | |||||
| <h3>Functions</h3> | <h3>Functions</h3> | ||||
| <p class=func><span class=keyword>begin</span>(delayBuffer, length, offset, depth, delayRate);</p> | <p class=func><span class=keyword>begin</span>(delayBuffer, length, offset, depth, delayRate);</p> | ||||
| <p class=desc>blah blah blah blah | <p class=desc>blah blah blah blah | ||||
| </script> | </script> | ||||
| <script type="text/x-red" data-help-name="AudioFilterFIR"> | <script type="text/x-red" data-help-name="AudioFilterFIR"> | ||||
| <h3>Summary</h3> | <h3>Summary</h3> | ||||
| <p>description</p> | |||||
| <div> | |||||
| <p>Finite impulse response filter, useful for all sorts of filtering. | |||||
| </p> | |||||
| <p align=center><img src="fir_filter.png"></p> | |||||
| </div> | |||||
| <h3>Audio Connections</h3> | <h3>Audio Connections</h3> | ||||
| <table class=doc align=center cellpadding=3> | <table class=doc align=center cellpadding=3> | ||||
| <tr class=top><th>Port</th><th>Purpose</th></tr> | <tr class=top><th>Port</th><th>Purpose</th></tr> | ||||
| <tr class=odd><td align=center></td><td></td></tr> | |||||
| </table> | |||||
| <!--<h3>Parameters</h3> | |||||
| <table class=doc align=center cellpadding=3> | |||||
| <tr class=top><th>Name</th><th>Type</th><th>Function</th></tr> | |||||
| <tr class=odd><td align=center></td><td>Integer</td><td></td></tr> | |||||
| <tr class=odd><td align=center>In 0</td><td>Signal to be filtered</td></tr> | |||||
| <tr class=odd><td align=center>Out 0</td><td>Filtered Signal Output</td></tr> | |||||
| </table> | </table> | ||||
| <p>Extra description... Section only present if object has params</p>--> | |||||
| <h3>Functions</h3> | <h3>Functions</h3> | ||||
| <p class=func><span class=keyword>function</span>(parm1, parm2);</p> | |||||
| <p class=desc>blah blah blah blah | |||||
| <p class=func><span class=keyword>begin</span>(array, length);</p> | |||||
| <p class=desc>Initialize the filter. The array must be 16 bit integers (the | |||||
| filter's impulse response), and | |||||
| length indicates the number of points in the array. Array may also be | |||||
| FIR_PASSTHRU (length = 0), to directly pass the input to output without | |||||
| filtering. | |||||
| </p> | |||||
| <p class=func><span class=keyword>end</span>();</p> | |||||
| <p class=desc>Turn the filter off. | |||||
| </p> | </p> | ||||
| <h3>Notes</h3> | <h3>Notes</h3> | ||||
| <p></p> | |||||
| <p>FIR filters requires more CPU time than Biquad (IIR), but they can | |||||
| implement filters with better phase response. | |||||
| </p> | |||||
| <p>A 100 point filter requires 9% CPU time on Teensy 3.1. The maximum | |||||
| supported filter length is 200 points. | |||||
| </p> | |||||
| <p>The free | |||||
| <a href="http://t-filter.appspot.com/fir/index.html" target="_blank"> TFilter Design Tool</a> | |||||
| can be used to create the impulse response array. Be sure to set the sampling | |||||
| frequency to 44117 HZ (it defaults to only 2000 Hz) and the output type to "int" (16 bit). | |||||
| </p> | |||||
| <p> | |||||
| If you use TFilter Design's "C/C++ array" option, it's output has "int" definition, which | |||||
| is 32 bits on Teensy 3.1. Edit "int" to "short" for an array of 16 bit numbers, | |||||
| and add "const" to avoid consuming extra RAM. | |||||
| </p> | |||||
| </script> | </script> | ||||
| <script type="text/x-red" data-template-name="AudioFilterFIR"> | <script type="text/x-red" data-template-name="AudioFilterFIR"> | ||||
| <div class="form-row"> | <div class="form-row"> | ||||
| </script> | </script> | ||||
| <script type="text/x-red" data-help-name="AudioAnalyzePeak"> | <script type="text/x-red" data-help-name="AudioAnalyzePeak"> | ||||
| <h3>Summary</h3> | <h3>Summary</h3> | ||||
| <p>description</p> | |||||
| <p>Track the signal peak amplitude. Very useful for simple | |||||
| audio level response projects, and general troubleshooting.</p> | |||||
| <h3>Audio Connections</h3> | <h3>Audio Connections</h3> | ||||
| <table class=doc align=center cellpadding=3> | <table class=doc align=center cellpadding=3> | ||||
| <tr class=top><th>Port</th><th>Purpose</th></tr> | <tr class=top><th>Port</th><th>Purpose</th></tr> | ||||
| <script type="text/x-red" data-help-name="AudioAnalyzePrint"> | <script type="text/x-red" data-help-name="AudioAnalyzePrint"> | ||||
| <h3>Summary</h3> | <h3>Summary</h3> | ||||
| <p>Print raw audio data to the Arduino Serial Monitor. This | <p>Print raw audio data to the Arduino Serial Monitor. This | ||||
| object creates massive output quickly, and should not normall be used.</p> | |||||
| object creates massive output quickly, and should not normally be used.</p> | |||||
| <h3>Audio Connections</h3> | <h3>Audio Connections</h3> | ||||
| <table class=doc align=center cellpadding=3> | <table class=doc align=center cellpadding=3> | ||||
| <tr class=top><th>Port</th><th>Purpose</th></tr> | <tr class=top><th>Port</th><th>Purpose</th></tr> | ||||
| <tr class=odd><td align=center></td><td></td></tr> | |||||
| <tr class=odd><td align=center>In 0</td><td>Signal to print</td></tr> | |||||
| </table> | </table> | ||||
| <!--<h3>Parameters</h3> | |||||
| <table class=doc align=center cellpadding=3> | |||||
| <tr class=top><th>Name</th><th>Type</th><th>Function</th></tr> | |||||
| <tr class=odd><td align=center></td><td>Integer</td><td></td></tr> | |||||
| </table> | |||||
| <p>Extra description... Section only present if object has params</p>--> | |||||
| <h3>Functions</h3> | <h3>Functions</h3> | ||||
| <p class=func><span class=keyword>name</span>(string);</p> | <p class=func><span class=keyword>name</span>(string);</p> | ||||
| <p class=desc>blah blah blah blah | <p class=desc>blah blah blah blah | ||||
| <p class=desc>blah blah blah blah | <p class=desc>blah blah blah blah | ||||
| </p> | </p> | ||||
| <h3>Notes</h3> | <h3>Notes</h3> | ||||
| <p></p> | |||||
| <p>This object doesn't work very well and probably should not be used.</p> | |||||
| </script> | </script> | ||||
| <script type="text/x-red" data-template-name="AudioAnalyzePrint"> | <script type="text/x-red" data-template-name="AudioAnalyzePrint"> | ||||
| <div class="form-row"> | <div class="form-row"> |