/******************************************************************/ | /******************************************************************/ | ||||
// A u d i o E f f e c t F l a n g e | // A u d i o E f f e c t F l a n g e | ||||
// Written by Pete (El Supremo) Jan 2014 | // Written by Pete (El Supremo) Jan 2014 | ||||
// 140529 - change to handle mono stream and change modify() to voices() | |||||
// 140207 - fix calculation of delay_rate_incr which is expressed as | // 140207 - fix calculation of delay_rate_incr which is expressed as | ||||
// a fraction of 2*PI | // a fraction of 2*PI | ||||
// 140207 - cosmetic fix to begin() | // 140207 - cosmetic fix to begin() | ||||
} | } | ||||
delay_length = d_length/2; | delay_length = d_length/2; | ||||
l_delayline = delayline; | l_delayline = delayline; | ||||
r_delayline = delayline + delay_length; | |||||
delay_depth = d_depth; | delay_depth = d_depth; | ||||
// initial index | // initial index | ||||
l_delay_rate_index = 0; | l_delay_rate_index = 0; | ||||
r_delay_rate_index = 0; | |||||
l_circ_idx = 0; | l_circ_idx = 0; | ||||
r_circ_idx = 0; | |||||
delay_rate_incr = delay_rate/44100.*2147483648.; | delay_rate_incr = delay_rate/44100.*2147483648.; | ||||
//Serial.println(delay_rate_incr,HEX); | //Serial.println(delay_rate_incr,HEX); | ||||
} | } | ||||
boolean AudioEffectFlange::modify(int delay_offset,int d_depth,float delay_rate) | |||||
boolean AudioEffectFlange::voices(int delay_offset,int d_depth,float delay_rate) | |||||
{ | { | ||||
boolean all_ok = true; | boolean all_ok = true; | ||||
all_ok = false; | all_ok = false; | ||||
} | } | ||||
l_delay_rate_index = 0; | l_delay_rate_index = 0; | ||||
r_delay_rate_index = 0; | |||||
l_circ_idx = 0; | l_circ_idx = 0; | ||||
r_circ_idx = 0; | |||||
return(all_ok); | return(all_ok); | ||||
} | } | ||||
int idx1; | int idx1; | ||||
if(l_delayline == NULL)return; | if(l_delayline == NULL)return; | ||||
if(r_delayline == NULL)return; | |||||
// do passthru | // do passthru | ||||
if(delay_offset_idx == FLANGE_DELAY_PASSTHRU) { | if(delay_offset_idx == FLANGE_DELAY_PASSTHRU) { | ||||
block = receiveWritable(0); | block = receiveWritable(0); | ||||
if(block) { | if(block) { | ||||
bp = block->data; | bp = block->data; | ||||
// fill the delay line | |||||
for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | ||||
l_circ_idx++; | l_circ_idx++; | ||||
if(l_circ_idx >= delay_length) { | if(l_circ_idx >= delay_length) { | ||||
} | } | ||||
l_delayline[l_circ_idx] = *bp++; | l_delayline[l_circ_idx] = *bp++; | ||||
} | } | ||||
// transmit the unmodified block | |||||
transmit(block,0); | transmit(block,0); | ||||
release(block); | release(block); | ||||
} | } | ||||
block = receiveWritable(1); | |||||
if(block) { | |||||
bp = block->data; | |||||
for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
r_circ_idx++; | |||||
if(r_circ_idx >= delay_length) { | |||||
r_circ_idx = 0; | |||||
} | |||||
r_delayline[r_circ_idx] = *bp++; | |||||
} | |||||
transmit(block,1); | |||||
release(block); | |||||
} | |||||
return; | return; | ||||
} | } | ||||
// Do the interpolation | // Do the interpolation | ||||
frac = (l_delay_rate_index >> 1) &0x7fff; | frac = (l_delay_rate_index >> 1) &0x7fff; | ||||
frac = (( (int)(l_delayline[idx1] - l_delayline[idx])*frac) >> 15); | frac = (( (int)(l_delayline[idx1] - l_delayline[idx])*frac) >> 15); | ||||
//frac = 0; | |||||
*bp++ = (l_delayline[l_circ_idx] | |||||
+ l_delayline[idx] + frac | |||||
// + l_delayline[(l_circ_idx + delay_length/2) % delay_length] | |||||
)/2; | |||||
*bp++ = (l_delayline[l_circ_idx]+ l_delayline[idx] + frac)/2; | |||||
l_delay_rate_index += delay_rate_incr; | l_delay_rate_index += delay_rate_incr; | ||||
if(l_delay_rate_index & 0x80000000) { | if(l_delay_rate_index & 0x80000000) { | ||||
transmit(block,0); | transmit(block,0); | ||||
release(block); | release(block); | ||||
} | } | ||||
// R I G H T C H A N N E L | |||||
block = receiveWritable(1); | |||||
if(block) { | |||||
bp = block->data; | |||||
for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
r_circ_idx++; | |||||
if(r_circ_idx >= delay_length) { | |||||
r_circ_idx = 0; | |||||
} | |||||
r_delayline[r_circ_idx] = *bp; | |||||
frac = arm_sin_q15( (q15_t)((r_delay_rate_index >> 16)&0x7fff)); | |||||
idx = (frac * delay_depth) >> 15; | |||||
idx = r_circ_idx - (delay_offset_idx + idx); | |||||
if(idx < 0) { | |||||
idx += delay_length; | |||||
} | |||||
if(idx >= delay_length) { | |||||
idx -= delay_length; | |||||
} | |||||
if(frac < 0) | |||||
idx1 = idx - 1; | |||||
else | |||||
idx1 = idx + 1; | |||||
if(idx1 < 0) { | |||||
idx1 += delay_length; | |||||
} | |||||
if(idx1 >= delay_length) { | |||||
idx1 -= delay_length; | |||||
} | |||||
frac = (r_delay_rate_index >> 1) &0x7fff; | |||||
frac = (( (int)(r_delayline[idx1] - r_delayline[idx])*frac) >> 15); | |||||
//frac = 0; | |||||
*bp++ = (r_delayline[r_circ_idx] | |||||
+ r_delayline[idx] + frac | |||||
)/2; | |||||
r_delay_rate_index += delay_rate_incr; | |||||
if(r_delay_rate_index & 0x80000000) { | |||||
r_delay_rate_index &= 0x7fffffff; | |||||
} | |||||
} | |||||
// send the effect output to the right channel | |||||
transmit(block,1); | |||||
release(block); | |||||
} | |||||
} | } | ||||
/******************************************************************/ | /******************************************************************/ | ||||
// A u d i o E f f e c t F l a n g e | // A u d i o E f f e c t F l a n g e | ||||
// Written by Pete (El Supremo) Jan 2014 | // Written by Pete (El Supremo) Jan 2014 | ||||
// 140529 - change to handle mono stream and change modify() to voices() | |||||
#define FLANGE_DELAY_PASSTHRU 0 | #define FLANGE_DELAY_PASSTHRU 0 | ||||
{ | { | ||||
public: | public: | ||||
AudioEffectFlange(void): | AudioEffectFlange(void): | ||||
AudioStream(2,inputQueueArray) { | |||||
AudioStream(1,inputQueueArray) { | |||||
} | } | ||||
boolean begin(short *delayline,int d_length,int delay_offset,int d_depth,float delay_rate); | boolean begin(short *delayline,int d_length,int delay_offset,int d_depth,float delay_rate); | ||||
boolean modify(int delay_offset,int d_depth,float delay_rate); | |||||
boolean voices(int delay_offset,int d_depth,float delay_rate); | |||||
virtual void update(void); | virtual void update(void); | ||||
void stop(void); | void stop(void); | ||||
private: | private: | ||||
audio_block_t *inputQueueArray[2]; | |||||
audio_block_t *inputQueueArray[1]; | |||||
short *l_delayline; | short *l_delayline; | ||||
short *r_delayline; | |||||
int delay_length; | int delay_length; | ||||
short l_circ_idx; | short l_circ_idx; | ||||
short r_circ_idx; | |||||
int delay_depth; | int delay_depth; | ||||
int delay_offset_idx; | int delay_offset_idx; | ||||
int delay_rate_incr; | int delay_rate_incr; | ||||
unsigned int l_delay_rate_index; | unsigned int l_delay_rate_index; | ||||
unsigned int r_delay_rate_index; | |||||
}; | }; | ||||
#endif | #endif |
/* | /* | ||||
Change the chorus code to produce a flange effect | |||||
140219 | |||||
e | |||||
VERSION 2 - use modified library which has been changed to handle | |||||
one channel instead of two | |||||
Proc = 21 (22), Mem = 4 (6) | |||||
140529 | |||||
2a | |||||
- default at startup is to have passthru ON and the button | |||||
switches the flange effect in. | |||||
From: http://www.cs.cf.ac.uk/Dave/CM0268/PDF/10_CM0268_Audio_FX.pdf | |||||
about Comb filter effects | |||||
Effect Delay range (ms) Modulation | |||||
Resonator 0 - 20 None | |||||
Flanger 0 - 15 Sinusoidal (approx 1Hz) | |||||
Chorus 25 - 50 None | |||||
Echo >50 None | |||||
FMI: | FMI: | ||||
The audio board uses the following pins. | The audio board uses the following pins. | ||||
#include <SPI.h> | #include <SPI.h> | ||||
#include <Bounce.h> | #include <Bounce.h> | ||||
// Number of samples in ONE channel | |||||
// Number of samples in each delay line | |||||
#define FLANGE_DELAY_LENGTH (6*AUDIO_BLOCK_SAMPLES) | #define FLANGE_DELAY_LENGTH (6*AUDIO_BLOCK_SAMPLES) | ||||
// Allocate the delay line for left and right channels | |||||
// The delayline will hold left and right samples so it | |||||
// should be declared to be twice as long as the desired | |||||
// number of samples in one channel | |||||
#define FLANGE_DELAYLINE (FLANGE_DELAY_LENGTH*2) | |||||
// The delay line for left and right channels | |||||
short delayline[FLANGE_DELAYLINE]; | |||||
// If this pin is grounded the effect is turned off, | |||||
// which makes it just pass through the audio | |||||
// Allocate the delay lines for left and right channels | |||||
short l_delayline[FLANGE_DELAY_LENGTH]; | |||||
short r_delayline[FLANGE_DELAY_LENGTH]; | |||||
// Default is to just pass the audio through. Grounding this pin | |||||
// applies the flange effect | |||||
// Don't use any of the pins listed above | // Don't use any of the pins listed above | ||||
#define PASSTHRU_PIN 1 | #define PASSTHRU_PIN 1 | ||||
const int myInput = AUDIO_INPUT_LINEIN; | const int myInput = AUDIO_INPUT_LINEIN; | ||||
AudioInputI2S audioInput; // audio shield: mic or line-in | AudioInputI2S audioInput; // audio shield: mic or line-in | ||||
AudioEffectFlange myEffect; | |||||
AudioEffectFlange l_myEffect; | |||||
AudioEffectFlange r_myEffect; | |||||
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 | ||||
// Both channels of the audio input go to the flange effect | // Both channels of the audio input go to the flange effect | ||||
AudioConnection c1(audioInput, 0, myEffect, 0); | |||||
AudioConnection c2(audioInput, 1, myEffect, 1); | |||||
AudioConnection c1(audioInput, 0, l_myEffect, 0); | |||||
AudioConnection c2(audioInput, 1, r_myEffect, 0); | |||||
// both channels from the flange effect go to the audio output | // both channels from the flange effect go to the audio output | ||||
AudioConnection c3(myEffect, 0, audioOutput, 0); | |||||
AudioConnection c4(myEffect, 1, audioOutput, 1); | |||||
AudioConnection c3(l_myEffect, 0, audioOutput, 0); | |||||
AudioConnection c4(r_myEffect, 0, audioOutput, 1); | |||||
AudioControlSGTL5000 audioShield; | AudioControlSGTL5000 audioShield; | ||||
/* | |||||
int s_idx = FLANGE_DELAY_LENGTH/2; | |||||
int s_depth = FLANGE_DELAY_LENGTH/16; | |||||
double s_freq = 1; | |||||
// <<<<<<<<<<<<<<>>>>>>>>>>>>>>>> | |||||
// 12 | |||||
int s_idx = FLANGE_DELAY_LENGTH/2; | |||||
int s_depth = FLANGE_DELAY_LENGTH/8; | |||||
double s_freq = .125; | |||||
// with .125 the ticking is about 1Hz with music | |||||
// but with the noise sample it is a bit slower than that | |||||
// <<<<<<<<<<<<<<>>>>>>>>>>>>>>>> | |||||
*/ | |||||
/* | |||||
// <<<<<<<<<<<<<<>>>>>>>>>>>>>>>> | |||||
// 12 | |||||
int s_idx = FLANGE_DELAY_LENGTH/2; | |||||
int s_depth = FLANGE_DELAY_LENGTH/12; | |||||
double s_freq = .125; | |||||
// with .125 the ticking is about 1Hz with music | |||||
// but with the noise sample it is a bit slower than that | |||||
// <<<<<<<<<<<<<<>>>>>>>>>>>>>>>> | |||||
*/ | |||||
/* | |||||
//12 | |||||
int s_idx = 15*FLANGE_DELAY_LENGTH/16; | |||||
int s_depth = 15*FLANGE_DELAY_LENGTH/16; | |||||
double s_freq = 0; | |||||
*/ | |||||
/* | |||||
//12 | |||||
int s_idx = 2*FLANGE_DELAY_LENGTH/4; | |||||
int s_depth = FLANGE_DELAY_LENGTH/8; | |||||
double s_freq = .0625; | |||||
*/ | |||||
/* | |||||
//12 - good with Eric Clapton Unplugged | |||||
int s_idx = 3*FLANGE_DELAY_LENGTH/4; | |||||
int s_depth = FLANGE_DELAY_LENGTH/8; | |||||
double s_freq = .0625; | |||||
*/ | |||||
/* | |||||
// Real flange effect! delay line is 2* | |||||
int s_idx = 2*FLANGE_DELAY_LENGTH/4; | |||||
int s_depth = FLANGE_DELAY_LENGTH/4; | |||||
double s_freq = 2; | |||||
*/ | |||||
/* 2 - | |||||
int s_idx = 2*FLANGE_DELAY_LENGTH/4; | |||||
int s_depth = FLANGE_DELAY_LENGTH/8; | |||||
double s_freq = 4; | |||||
*/ | |||||
/* | |||||
// 4 | |||||
int s_idx = FLANGE_DELAY_LENGTH/4; | |||||
int s_depth = FLANGE_DELAY_LENGTH/4; | |||||
double s_freq = .25; | |||||
*/ | |||||
// 4 | |||||
int s_idx = FLANGE_DELAY_LENGTH/4; | int s_idx = FLANGE_DELAY_LENGTH/4; | ||||
int s_depth = FLANGE_DELAY_LENGTH/4; | int s_depth = FLANGE_DELAY_LENGTH/4; | ||||
double s_freq = .5; | double s_freq = .5; | ||||
Serial.println(") is grounded"); | Serial.println(") is grounded"); | ||||
} | } | ||||
// Set up the flange effect | |||||
// - address of delayline | |||||
// - total number of samples (left AND right) in the delay line | |||||
// - Index (in samples) into the delay line for the added voice | |||||
// - Depth of the flange effect | |||||
// - frequency of the flange effect | |||||
myEffect.begin(delayline,FLANGE_DELAYLINE,s_idx,s_depth,s_freq); | |||||
// Set up the flange effect: | |||||
// address of delayline | |||||
// total number of samples in the delay line | |||||
// Index (in samples) into the delay line for the added voice | |||||
// Depth of the flange effect | |||||
// frequency of the flange effect | |||||
l_myEffect.begin(l_delayline,FLANGE_DELAY_LENGTH,s_idx,s_depth,s_freq); | |||||
r_myEffect.begin(r_delayline,FLANGE_DELAY_LENGTH,s_idx,s_depth,s_freq); | |||||
// Initially the effect is off. It is switched on when the | |||||
// PASSTHRU button is pushed. | |||||
l_myEffect.voices(FLANGE_DELAY_PASSTHRU,0,0); | |||||
r_myEffect.voices(FLANGE_DELAY_PASSTHRU,0,0); | |||||
// I want output on the line out too | // I want output on the line out too | ||||
audioShield.unmuteLineout(); | audioShield.unmuteLineout(); | ||||
Serial.println("setup done"); | Serial.println("setup done"); | ||||
AudioProcessorUsageMaxReset(); | |||||
AudioMemoryUsageMaxReset(); | |||||
AudioProcessorUsageMaxReset(); | |||||
AudioMemoryUsageMaxReset(); | |||||
} | } | ||||
// update the button | // update the button | ||||
b_passthru.update(); | b_passthru.update(); | ||||
// If the passthru button is pushed, save the current | |||||
// If the passthru button is pushed | |||||
// turn the flange effect on | |||||
// filter index and then switch the effect to passthru | // filter index and then switch the effect to passthru | ||||
if(b_passthru.fallingEdge()) { | if(b_passthru.fallingEdge()) { | ||||
myEffect.modify(DELAY_PASSTHRU,0,0); | |||||
l_myEffect.voices(s_idx,s_depth,s_freq); | |||||
r_myEffect.voices(s_idx,s_depth,s_freq); | |||||
} | } | ||||
// If passthru button is released, restore the effect | |||||
// If passthru button is released restore passthru | |||||
if(b_passthru.risingEdge()) { | if(b_passthru.risingEdge()) { | ||||
myEffect.modify(s_idx,s_depth,s_freq); | |||||
l_myEffect.voices(FLANGE_DELAY_PASSTHRU,0,0); | |||||
r_myEffect.voices(FLANGE_DELAY_PASSTHRU,0,0); | |||||
} | } | ||||
} | } |
combines the most recent sample, the oldest sample and the sample | combines the most recent sample, the oldest sample and the sample | ||||
in the middle of the delay line. | in the middle of the delay line. | ||||
For two voices the effect can be represented as: | For two voices the effect can be represented as: | ||||
result = sample(0) + sample(dt) | |||||
result = (sample(0) + sample(dt))/2 | |||||
where sample(0) represents the current sample and sample(dt) is | where sample(0) represents the current sample and sample(dt) is | ||||
the sample in the delay line from dt milliseconds ago. | the sample in the delay line from dt milliseconds ago. | ||||
FLANGE EFFECT | FLANGE EFFECT | ||||
This combines only one sample from the delay line but the position | This combines only one sample from the delay line but the position | ||||
of that sample varies sinusoidally. | of that sample varies sinusoidally. | ||||
-depth to +depth. Thus, the delayed sample will be selected from | -depth to +depth. Thus, the delayed sample will be selected from | ||||
the range (dt-depth) to (dt+depth). This selection will vary | the range (dt-depth) to (dt+depth). This selection will vary | ||||
at whatever rate is specified as the frequency of the effect Fe. | at whatever rate is specified as the frequency of the effect Fe. | ||||
I have found that rates of .25 seconds or less are best, otherwise | |||||
the effect is very "watery" and in extreme cases the sound is | |||||
even off-key! | |||||
Try these settings: | |||||
#define FLANGE_DELAY_LENGTH (2*AUDIO_BLOCK_SAMPLES) | |||||
and | |||||
int s_idx = 2*FLANGE_DELAY_LENGTH/4; | |||||
int s_depth = FLANGE_DELAY_LENGTH/4; | |||||
double s_freq = 3; | |||||
The flange effect can also produce a chorus effect if a longer | |||||
delay line is used with a slower rate, for example try: | |||||
#define FLANGE_DELAY_LENGTH (12*AUDIO_BLOCK_SAMPLES) | |||||
and | |||||
int s_idx = 3*FLANGE_DELAY_LENGTH/4; | |||||
int s_depth = FLANGE_DELAY_LENGTH/8; | |||||
double s_freq = .0625; | |||||
When trying out these effects with recorded music as input, it is | When trying out these effects with recorded music as input, it is | ||||
best to use those where there is a solo voice which is clearly | best to use those where there is a solo voice which is clearly | ||||
"in front" of the accompaninemnt. Tracks which already contain | |||||
"in front" of the accompaniment. Tracks which already contain | |||||
flange or chorus effects don't work well. | flange or chorus effects don't work well. | ||||
*/ | */ |