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