@@ -26,6 +26,7 @@ | |||
// A u d i o E f f e c t C h o r u s | |||
// Written by Pete (El Supremo) Jan 2014 | |||
// 140529 - change to handle mono stream - change modify() to voices() | |||
// 140219 - correct storage class (not static) | |||
boolean AudioEffectChorus::begin(short *delayline,int d_length,int n_chorus) | |||
@@ -37,10 +38,8 @@ Serial.print(n_chorus); | |||
Serial.println(")"); | |||
l_delayline = NULL; | |||
r_delayline = NULL; | |||
delay_length = 0; | |||
l_circ_idx = 0; | |||
r_circ_idx = 0; | |||
if(delayline == NULL) { | |||
return(false); | |||
@@ -53,14 +52,13 @@ Serial.println(")"); | |||
} | |||
l_delayline = delayline; | |||
r_delayline = delayline + d_length/2; | |||
delay_length = d_length/2; | |||
num_chorus = n_chorus; | |||
return(true); | |||
} | |||
void AudioEffectChorus::modify(int n_chorus) | |||
void AudioEffectChorus::voices(int n_chorus) | |||
{ | |||
num_chorus = n_chorus; | |||
} | |||
@@ -74,7 +72,6 @@ void AudioEffectChorus::update(void) | |||
int c_idx; | |||
if(l_delayline == NULL)return; | |||
if(r_delayline == NULL)return; | |||
// do passthru | |||
// It stores the unmodified data in the delay line so that | |||
@@ -94,20 +91,6 @@ void AudioEffectChorus::update(void) | |||
transmit(block,0); | |||
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; | |||
} | |||
// L E F T C H A N N E L | |||
@@ -133,38 +116,10 @@ void AudioEffectChorus::update(void) | |||
*bp++ = sum/num_chorus; | |||
} | |||
// send the effect output to the left channel | |||
// transmit the block | |||
transmit(block,0); | |||
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; | |||
sum = 0; | |||
c_idx = r_circ_idx; | |||
for(int k = 0; k < num_chorus; k++) { | |||
sum += r_delayline[c_idx]; | |||
if(num_chorus > 1)c_idx -= delay_length/(num_chorus - 1) - 1; | |||
if(c_idx < 0) { | |||
c_idx += delay_length; | |||
} | |||
} | |||
*bp++ = sum/num_chorus; | |||
} | |||
// send the effect output to the left channel | |||
transmit(block,1); | |||
release(block); | |||
} | |||
} | |||
@@ -38,20 +38,18 @@ public AudioStream | |||
{ | |||
public: | |||
AudioEffectChorus(void): | |||
AudioStream(2,inputQueueArray), num_chorus(2) | |||
AudioStream(1,inputQueueArray), num_chorus(2) | |||
{ } | |||
boolean begin(short *delayline,int delay_length,int n_chorus); | |||
virtual void update(void); | |||
void stop(void); | |||
void modify(int n_chorus); | |||
void voices(int n_chorus); | |||
private: | |||
audio_block_t *inputQueueArray[2]; | |||
audio_block_t *inputQueueArray[1]; | |||
short *l_delayline; | |||
short *r_delayline; | |||
short l_circ_idx; | |||
short r_circ_idx; | |||
int num_chorus; | |||
int delay_length; | |||
}; |
@@ -1,8 +1,21 @@ | |||
/* | |||
PROC/MEM 9/4 | |||
VERSION 2 - use modified library which has been changed to handle | |||
one channel instead of two | |||
140529 | |||
Proc = 7 (7), Mem = 4 (4) | |||
2a | |||
- default at startup is to have passthru ON and the button | |||
switches the chorus effect in. | |||
previous performance measures were PROC/MEM 9/4 | |||
140219 | |||
p | |||
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: | |||
The audio board uses the following pins. | |||
@@ -41,18 +54,14 @@ many blocks you provided with AudioMemory(). | |||
// Number of samples in ONE channel | |||
// Number of samples in each delay line | |||
#define CHORUS_DELAY_LENGTH (16*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 CHORUS_DELAYLINE (CHORUS_DELAY_LENGTH*2) | |||
// The delay line for left and right channels | |||
short delayline[CHORUS_DELAYLINE]; | |||
// If this pin is grounded the chorus is turned off | |||
// which makes it just pass through the audio | |||
// Allocate the delay lines for left and right channels | |||
short l_delayline[CHORUS_DELAY_LENGTH]; | |||
short r_delayline[CHORUS_DELAY_LENGTH]; | |||
// Default is to just pass the audio through. Grounding this pin | |||
// applies the chorus effect | |||
// Don't use any of the pins listed above | |||
#define PASSTHRU_PIN 1 | |||
@@ -62,16 +71,17 @@ Bounce b_passthru = Bounce(PASSTHRU_PIN,15); | |||
const int myInput = AUDIO_INPUT_LINEIN; | |||
AudioInputI2S audioInput; // audio shield: mic or line-in | |||
AudioEffectChorus myEffect; | |||
AudioEffectChorus l_myEffect; | |||
AudioEffectChorus r_myEffect; | |||
AudioOutputI2S audioOutput; // audio shield: headphones & line-out | |||
// Create Audio connections between the components | |||
// Both channels of the audio input go to the chorus effect | |||
AudioConnection c1(audioInput, 0, myEffect, 0); | |||
AudioConnection c2(audioInput, 1, myEffect, 1); | |||
// both channels from the chorus effect go to the audio output | |||
AudioConnection c3(myEffect, 0, audioOutput, 0); | |||
AudioConnection c4(myEffect, 1, audioOutput, 1); | |||
AudioConnection c1(audioInput, 0, l_myEffect, 0); | |||
AudioConnection c2(audioInput, 1, r_myEffect, 0); | |||
// both channels chorus effects go to the audio output | |||
AudioConnection c3(l_myEffect, 0, audioOutput, 0); | |||
AudioConnection c4(r_myEffect, 0, audioOutput, 1); | |||
AudioControlSGTL5000 audioShield; | |||
@@ -103,22 +113,35 @@ void setup() { | |||
Serial.println(") is grounded"); | |||
} | |||
// Initialize the effect | |||
// - address of delayline | |||
// - total number of samples (left AND right) in the delay line | |||
// - number of voices in the chorus INCLUDING the original voice | |||
if(!myEffect.begin(delayline,CHORUS_DELAYLINE,n_chorus)) { | |||
Serial.println("AudioEffectChorus - begin failed"); | |||
// Initialize the effect - left channel | |||
// address of delayline | |||
// total number of samples in the delay line | |||
// number of voices in the chorus INCLUDING the original voice | |||
if(!l_myEffect.begin(l_delayline,CHORUS_DELAY_LENGTH,n_chorus)) { | |||
Serial.println("AudioEffectChorus - left channel begin failed"); | |||
while(1); | |||
} | |||
// Initialize the effect - right channel | |||
// address of delayline | |||
// total number of samples in the delay line | |||
// number of voices in the chorus INCLUDING the original voice | |||
if(!r_myEffect.begin(r_delayline,CHORUS_DELAY_LENGTH,n_chorus)) { | |||
Serial.println("AudioEffectChorus - left channel begin failed"); | |||
while(1); | |||
} | |||
// Initially the effect is off. It is switched on when the | |||
// PASSTHRU button is pushed. | |||
l_myEffect.voices(0); | |||
r_myEffect.voices(0); | |||
// I want output on the line out too | |||
audioShield.unmuteLineout(); | |||
// audioShield.muteHeadphone(); | |||
Serial.println("setup done"); | |||
AudioProcessorUsageMaxReset(); | |||
AudioMemoryUsageMaxReset(); | |||
AudioProcessorUsageMaxReset(); | |||
AudioMemoryUsageMaxReset(); | |||
} | |||
@@ -151,14 +174,16 @@ if(0) { | |||
// update the button | |||
b_passthru.update(); | |||
// If the passthru button is pushed, switch the effect to passthru | |||
// If the passthru button is pushed, switch the chorus on | |||
if(b_passthru.fallingEdge()) { | |||
myEffect.modify(0); | |||
l_myEffect.voices(n_chorus); | |||
r_myEffect.voices(n_chorus); | |||
} | |||
// If passthru button is released, restore the previous chorus | |||
// If passthru button is released, turn on passthru | |||
if(b_passthru.risingEdge()) { | |||
myEffect.modify(n_chorus); | |||
l_myEffect.voices(0); | |||
r_myEffect.voices(0); | |||
} | |||
} |
@@ -5,7 +5,7 @@ CHORUS and FLANGE effects | |||
occurred in the past. An obvious effect this would allow would be | |||
an echo where the current sample is combined with a sample from, | |||
say, 250 milliseconds ago. The chorus and flange effects do this | |||
as well but they combine samples from only about 50ms or less ago. | |||
as well but they combine samples from only about 50ms (or less) ago. | |||
CHORUS EFFECT | |||
This combines one or more samples up to about 50ms ago. In this | |||
@@ -17,7 +17,7 @@ CHORUS EFFECT | |||
combines the most recent sample, the oldest sample and the sample | |||
in the middle of the delay line. | |||
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 | |||
the sample in the delay line from dt milliseconds ago. | |||
@@ -31,13 +31,24 @@ FLANGE EFFECT | |||
-depth to +depth. Thus, the delayed sample will be selected from | |||
the range (dt-depth) to (dt+depth). This selection will vary | |||
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 | |||
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. | |||
*/ |