| #include "effect_flange.h" | #include "effect_flange.h" | ||||
| #include "effect_envelope.h" | #include "effect_envelope.h" | ||||
| #include "effect_multiply.h" | #include "effect_multiply.h" | ||||
| #include "effect_delay.h" | |||||
| #include "filter_biquad.h" | #include "filter_biquad.h" | ||||
| #include "filter_fir.h" | #include "filter_fir.h" | ||||
| #include "input_adc.h" | #include "input_adc.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. | |||||
| */ | |||||
| #include "effect_delay.h" | |||||
| void AudioEffectDelay::update(void) | |||||
| { | |||||
| audio_block_t *output; | |||||
| uint32_t head, tail, count, channel, index, prev, offset; | |||||
| const int16_t *src, *end; | |||||
| int16_t *dst; | |||||
| // grab incoming data and put it into the queue | |||||
| head = headindex; | |||||
| tail = tailindex; | |||||
| if (++head >= DELAY_QUEUE_SIZE) head = 0; | |||||
| if (head == tail) { | |||||
| release(queue[tail]); | |||||
| if (++tail >= DELAY_QUEUE_SIZE) tail = 0; | |||||
| } | |||||
| queue[head] = receiveReadOnly(); | |||||
| headindex = head; | |||||
| // testing only.... don't allow null pointers into the queue | |||||
| // instead, fill the empty times with blocks of zeros | |||||
| //if (queue[head] == NULL) { | |||||
| // queue[head] = allocate(); | |||||
| // if (queue[head]) { | |||||
| // dst = queue[head]->data; | |||||
| // end = dst + AUDIO_BLOCK_SAMPLES; | |||||
| // do { | |||||
| // *dst++ = 0; | |||||
| // } while (dst < end); | |||||
| // } else { | |||||
| // digitalWriteFast(2, HIGH); | |||||
| // delayMicroseconds(5); | |||||
| // digitalWriteFast(2, LOW); | |||||
| // } | |||||
| //} | |||||
| // discard unneeded blocks from the queue | |||||
| if (head >= tail) { | |||||
| count = head - tail; | |||||
| } else { | |||||
| count = DELAY_QUEUE_SIZE + head - tail; | |||||
| } | |||||
| if (count > maxblocks) { | |||||
| count -= maxblocks; | |||||
| do { | |||||
| release(queue[tail]); | |||||
| queue[tail] = NULL; | |||||
| if (++tail >= DELAY_QUEUE_SIZE) tail = 0; | |||||
| } while (--count > 0); | |||||
| } | |||||
| tailindex = tail; | |||||
| // transmit the delayed outputs using queue data | |||||
| for (channel = 0; channel < 8; channel++) { | |||||
| if (!(activemask & (1<<channel))) continue; | |||||
| index = position[channel] / AUDIO_BLOCK_SAMPLES; | |||||
| offset = position[channel] % AUDIO_BLOCK_SAMPLES; | |||||
| if (head >= index) { | |||||
| index = head - index; | |||||
| } else { | |||||
| index = DELAY_QUEUE_SIZE + head - index; | |||||
| } | |||||
| if (offset == 0) { | |||||
| // delay falls on the block boundary | |||||
| if (queue[index]) { | |||||
| transmit(queue[index], channel); | |||||
| } | |||||
| } else { | |||||
| // delay requires grabbing data from 2 blocks | |||||
| output = allocate(); | |||||
| if (!output) continue; | |||||
| dst = output->data; | |||||
| if (index > 0) { | |||||
| prev = index - 1; | |||||
| } else { | |||||
| prev = DELAY_QUEUE_SIZE-1; | |||||
| } | |||||
| if (queue[prev]) { | |||||
| end = queue[prev]->data + AUDIO_BLOCK_SAMPLES; | |||||
| src = end - offset; | |||||
| while (src < end) { | |||||
| *dst++ = *src++; // TODO: optimize | |||||
| } | |||||
| } else { | |||||
| end = dst + offset; | |||||
| while (dst < end) { | |||||
| *dst++ = 0; | |||||
| } | |||||
| } | |||||
| end = output->data + AUDIO_BLOCK_SAMPLES; | |||||
| if (queue[index]) { | |||||
| src = queue[index]->data; | |||||
| while (dst < end) { | |||||
| *dst++ = *src++; // TODO: optimize | |||||
| } | |||||
| } else { | |||||
| while (dst < end) { | |||||
| *dst++ = 0; | |||||
| } | |||||
| } | |||||
| transmit(output, channel); | |||||
| release(output); | |||||
| } | |||||
| } | |||||
| } | |||||
| /* 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. | |||||
| */ | |||||
| #ifndef effect_delay_h_ | |||||
| #define effect_delay_h_ | |||||
| #include "AudioStream.h" | |||||
| #include "utility/dspinst.h" | |||||
| #define DELAY_QUEUE_SIZE 117 | |||||
| class AudioEffectDelay : public AudioStream | |||||
| { | |||||
| public: | |||||
| AudioEffectDelay() : AudioStream(1, inputQueueArray) { | |||||
| activemask = 0; | |||||
| headindex = 0; | |||||
| tailindex = 0; | |||||
| maxblocks = 0; | |||||
| memset(queue, 0, sizeof(queue)); | |||||
| } | |||||
| void delay(uint8_t channel, float milliseconds) { | |||||
| if (channel >= 8) return; | |||||
| if (milliseconds < 0.0) milliseconds = 0.0; | |||||
| uint32_t n = (milliseconds*(AUDIO_SAMPLE_RATE_EXACT/1000.0))+0.5; | |||||
| uint32_t nmax = AUDIO_BLOCK_SAMPLES * (DELAY_QUEUE_SIZE-1); | |||||
| if (n > nmax) n = nmax; | |||||
| uint32_t blks = (n + (AUDIO_BLOCK_SAMPLES-1)) / AUDIO_BLOCK_SAMPLES + 1; | |||||
| if (!(activemask & (1<<channel))) { | |||||
| // enabling a previously disabled channel | |||||
| position[channel] = n; | |||||
| if (blks > maxblocks) maxblocks = blks; | |||||
| activemask |= (1<<channel); | |||||
| } else { | |||||
| if (n > position[channel]) { | |||||
| // new delay is greater than previous setting | |||||
| if (blks > maxblocks) maxblocks = blks; | |||||
| position[channel] = n; | |||||
| } else { | |||||
| // new delay is less than previous setting | |||||
| position[channel] = n; | |||||
| recompute_maxblocks(); | |||||
| } | |||||
| } | |||||
| } | |||||
| void disable(uint8_t channel) { | |||||
| if (channel >= 8) return; | |||||
| // diable this channel | |||||
| activemask &= ~(1<<channel); | |||||
| // recompute maxblocks for remaining enabled channels | |||||
| recompute_maxblocks(); | |||||
| } | |||||
| virtual void update(void); | |||||
| private: | |||||
| void recompute_maxblocks(void) { | |||||
| uint32_t max=0; | |||||
| uint32_t channel = 0; | |||||
| do { | |||||
| if (activemask & (1<<channel)) { | |||||
| uint32_t n = position[channel]; | |||||
| n = (n + (AUDIO_BLOCK_SAMPLES-1)) / AUDIO_BLOCK_SAMPLES + 1; | |||||
| if (n > max) max = n; | |||||
| } | |||||
| } while(++channel < 8); | |||||
| maxblocks = max; | |||||
| } | |||||
| uint8_t activemask; // which output channels are active | |||||
| uint8_t headindex; // head index (incoming) data in quueu | |||||
| uint8_t tailindex; // tail index (outgoing) data from queue | |||||
| uint8_t maxblocks; // number of blocks needed in queue | |||||
| uint16_t position[8]; // # of sample delay for each channel | |||||
| audio_block_t *queue[DELAY_QUEUE_SIZE]; | |||||
| audio_block_t *inputQueueArray[1]; | |||||
| }; | |||||
| #endif |
| <script type="text/javascript"> | |||||
| RED.nodes.registerType('AudioEffectDelay',{ | |||||
| shortName: "delay", | |||||
| inputs:1, | |||||
| outputs:8, | |||||
| category: 'effect-function', | |||||
| color:"#E6E0F8", | |||||
| icon: "arrow-in.png" | |||||
| }); | |||||
| </script> | |||||
| <script type="text/x-red" data-help-name="AudioEffectDelay"> | |||||
| <h3>Summary</h3> | |||||
| <p>Delay a signal. Up to 8 separate delay taps can be used.</p> | |||||
| <h3>Audio Connections</h3> | |||||
| <table class=doc align=center cellpadding=3> | |||||
| <tr class=top><th>Port</th><th>Purpose</th></tr> | |||||
| <tr class=odd><td align=center>In 0</td><td>Signal Input</td></tr> | |||||
| <tr class=odd><td align=center>Out 0</td><td>Delay Tap #1</td></tr> | |||||
| <tr class=odd><td align=center>Out 1</td><td>Delay Tap #2</td></tr> | |||||
| <tr class=odd><td align=center>Out 2</td><td>Delay Tap #3</td></tr> | |||||
| <tr class=odd><td align=center>Out 3</td><td>Delay Tap #4</td></tr> | |||||
| <tr class=odd><td align=center>Out 4</td><td>Delay Tap #5</td></tr> | |||||
| <tr class=odd><td align=center>Out 5</td><td>Delay Tap #6</td></tr> | |||||
| <tr class=odd><td align=center>Out 6</td><td>Delay Tap #7</td></tr> | |||||
| <tr class=odd><td align=center>Out 7</td><td>Delay Tap #8</td></tr> | |||||
| </table> | |||||
| <h3>Functions</h3> | |||||
| <p class=func><span class=keyword>delay</span>(channel, milliseconds);</p> | |||||
| <p class=desc>Set output channel (0 to 7) to delay the signals by | |||||
| milliseconds. The maximum delay is approx 333 ms. The actual delay | |||||
| is rounded to the nearest sample. Each channel can be configured for | |||||
| any delay. There is no requirement to configure the "taps" in increasing | |||||
| delay order. | |||||
| </p> | |||||
| <p class=func><span class=keyword>disable</span>(channel);</p> | |||||
| <p class=desc>Disable a channel. The output of this channel becomes | |||||
| silent. If this channel is the longest delay, memory usage is | |||||
| automatically reduced to accomodate only the remaining channels used. | |||||
| </p> | |||||
| <h3>Notes</h3> | |||||
| <p>Memory for the delayed signal is take from the memory pool allocated by | |||||
| <a href="http://www.pjrc.com/teensy/td_libs_td_libs_AudioConnection.html" target="_blank">AudioMemory()</a>. | |||||
| Each block allows about 3 milliseconds of delay, so AudioMemory | |||||
| should be increased to allow for the longest delay tap. | |||||
| </p> | |||||
| </script> | |||||
| <script type="text/x-red" data-template-name="AudioEffectDelay"> | |||||
| <div class="form-row"> | |||||
| <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> | |||||
| <input type="text" id="node-input-name" placeholder="Name"> | |||||
| </div> | |||||
| </script> | |||||
| <script type="text/javascript"> | <script type="text/javascript"> | ||||
| RED.nodes.registerType('AudioFilterBiquad',{ | RED.nodes.registerType('AudioFilterBiquad',{ | ||||
| shortName: "biquad", | shortName: "biquad", |
| AudioEffectFlange KEYWORD2 | AudioEffectFlange KEYWORD2 | ||||
| AudioEffectEnvelope KEYWORD2 | AudioEffectEnvelope KEYWORD2 | ||||
| AudioEffectMultiply KEYWORD2 | AudioEffectMultiply KEYWORD2 | ||||
| AudioEffectDelay KEYWORD2 | |||||
| AudioFilterBiquad KEYWORD2 | AudioFilterBiquad KEYWORD2 | ||||
| AudioFilterFIR KEYWORD2 | AudioFilterFIR KEYWORD2 | ||||
| AudioInputAnalog KEYWORD2 | AudioInputAnalog KEYWORD2 |