#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 |