| @@ -57,6 +57,7 @@ | |||
| #include "effect_flange.h" | |||
| #include "effect_envelope.h" | |||
| #include "effect_multiply.h" | |||
| #include "effect_delay.h" | |||
| #include "filter_biquad.h" | |||
| #include "filter_fir.h" | |||
| #include "input_adc.h" | |||
| @@ -0,0 +1,147 @@ | |||
| /* 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,98 @@ | |||
| /* 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 | |||
| @@ -1113,6 +1113,62 @@ | |||
| <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"> | |||
| RED.nodes.registerType('AudioFilterBiquad',{ | |||
| shortName: "biquad", | |||
| @@ -16,6 +16,7 @@ AudioEffectFade KEYWORD2 | |||
| AudioEffectFlange KEYWORD2 | |||
| AudioEffectEnvelope KEYWORD2 | |||
| AudioEffectMultiply KEYWORD2 | |||
| AudioEffectDelay KEYWORD2 | |||
| AudioFilterBiquad KEYWORD2 | |||
| AudioFilterFIR KEYWORD2 | |||
| AudioInputAnalog KEYWORD2 | |||