if(b_new)release(b_new); | if(b_new)release(b_new); | ||||
} | } | ||||
/******************************************************************/ | |||||
// A u d i o E f f e c t F l a n g e | |||||
// Written by Pete (El Supremo) Jan 2014 | |||||
// circular addressing indices for left and right channels | |||||
short AudioEffectFlange::l_circ_idx; | |||||
short AudioEffectFlange::r_circ_idx; | |||||
short * AudioEffectFlange::l_delayline = NULL; | |||||
short * AudioEffectFlange::r_delayline = NULL; | |||||
// User-supplied offset for the delayed sample | |||||
// but start with passthru | |||||
int AudioEffectFlange::delay_offset_idx = DELAY_PASSTHRU; | |||||
int AudioEffectFlange::delay_length; | |||||
int AudioEffectFlange::delay_depth; | |||||
int AudioEffectFlange::delay_rate_incr; | |||||
unsigned int AudioEffectFlange::l_delay_rate_index; | |||||
unsigned int AudioEffectFlange::r_delay_rate_index; | |||||
// fails if the user provides unreasonable values but will | |||||
// coerce them and go ahead anyway. e.g. if the delay offset | |||||
// is >= CHORUS_DELAY_LENGTH, the code will force it to | |||||
// CHORUS_DELAY_LENGTH-1 and return false. | |||||
// delay_rate is the rate (in Hz) of the sine wave modulation | |||||
// delay_depth is the maximum variation around delay_offset | |||||
// i.e. the total offset is delay_offset + delay_depth * sin(delay_rate) | |||||
boolean AudioEffectFlange::begin(short *delayline,int d_length,int delay_offset,int d_depth,float delay_rate) | |||||
{ | |||||
boolean all_ok = true; | |||||
Serial.print("AudioEffectFlange.begin(ofsset = "); | |||||
Serial.print(delay_offset); | |||||
Serial.print(", depth = "); | |||||
Serial.print(d_depth); | |||||
Serial.print(", rate = "); | |||||
Serial.print(delay_rate,3); | |||||
Serial.println(")"); | |||||
Serial.print(" CHORUS_DELAY_LENGTH = "); | |||||
Serial.println(d_length); | |||||
delay_length = d_length/2; | |||||
l_delayline = delayline; | |||||
r_delayline = delayline + delay_length; | |||||
delay_depth = d_depth; | |||||
// initial index | |||||
l_delay_rate_index = 0; | |||||
r_delay_rate_index = 0; | |||||
l_circ_idx = 0; | |||||
r_circ_idx = 0; | |||||
delay_rate_incr = 2*PI*delay_rate/44100.*2147483648.; | |||||
//Serial.println(delay_rate_incr,HEX); | |||||
delay_offset_idx = delay_offset; | |||||
// Allow the passthru code to go through | |||||
if(delay_offset_idx < -1) { | |||||
delay_offset_idx = 0; | |||||
all_ok = false; | |||||
} | |||||
if(delay_offset_idx >= delay_length) { | |||||
delay_offset_idx = delay_length - 1; | |||||
all_ok = false; | |||||
} | |||||
return(all_ok); | |||||
} | |||||
boolean AudioEffectFlange::modify(int delay_offset,int d_depth,float delay_rate) | |||||
{ | |||||
boolean all_ok = true; | |||||
delay_depth = d_depth; | |||||
delay_rate_incr = 2*PI*delay_rate/44100.*2147483648.; | |||||
delay_offset_idx = delay_offset; | |||||
// Allow the passthru code to go through | |||||
if(delay_offset_idx < -1) { | |||||
delay_offset_idx = 0; | |||||
all_ok = false; | |||||
} | |||||
if(delay_offset_idx >= delay_length) { | |||||
delay_offset_idx = delay_length - 1; | |||||
all_ok = false; | |||||
} | |||||
l_delay_rate_index = 0; | |||||
r_delay_rate_index = 0; | |||||
l_circ_idx = 0; | |||||
r_circ_idx = 0; | |||||
return(all_ok); | |||||
} | |||||
void AudioEffectFlange::update(void) | |||||
{ | |||||
audio_block_t *block; | |||||
int idx; | |||||
short *bp; | |||||
short frac; | |||||
int idx1; | |||||
if(l_delayline == NULL)return; | |||||
if(r_delayline == NULL)return; | |||||
// do passthru | |||||
if(delay_offset_idx == DELAY_PASSTHRU) { | |||||
// Just passthrough | |||||
block = receiveWritable(0); | |||||
if(block) { | |||||
bp = block->data; | |||||
for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
l_circ_idx++; | |||||
if(l_circ_idx >= delay_length) { | |||||
l_circ_idx = 0; | |||||
} | |||||
l_delayline[l_circ_idx] = *bp++; | |||||
} | |||||
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 | |||||
block = receiveWritable(0); | |||||
if(block) { | |||||
bp = block->data; | |||||
for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
l_circ_idx++; | |||||
if(l_circ_idx >= delay_length) { | |||||
l_circ_idx = 0; | |||||
} | |||||
l_delayline[l_circ_idx] = *bp; | |||||
idx = arm_sin_q15( (q15_t)((l_delay_rate_index >> 16) & 0x7fff)); | |||||
idx = (idx * delay_depth) >> 15; | |||||
//Serial.println(idx); | |||||
idx = l_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 = (l_delay_rate_index >> 1) &0x7fff; | |||||
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; | |||||
l_delay_rate_index += delay_rate_incr; | |||||
if(l_delay_rate_index & 0x80000000) { | |||||
l_delay_rate_index &= 0x7fffffff; | |||||
} | |||||
} | |||||
// send the effect output to the left channel | |||||
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; | |||||
idx = arm_sin_q15( (q15_t)((r_delay_rate_index >> 16)&0x7fff)); | |||||
idx = (idx * 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 C h o r u s | |||||
// Written by Pete (El Supremo) Jan 2014 | |||||
// circular addressing indices for left and right channels | |||||
short AudioEffectChorus::l_circ_idx; | |||||
short AudioEffectChorus::r_circ_idx; | |||||
short * AudioEffectChorus::l_delayline = NULL; | |||||
short * AudioEffectChorus::r_delayline = NULL; | |||||
int AudioEffectChorus::delay_length; | |||||
// An initial value of zero indicates passthru | |||||
int AudioEffectChorus::num_chorus = 0; | |||||
// All three must be valid. | |||||
boolean AudioEffectChorus::begin(short *delayline,int d_length,int n_chorus) | |||||
{ | |||||
Serial.print("AudioEffectChorus.begin(Chorus delay line length = "); | |||||
Serial.print(d_length); | |||||
Serial.print(", n_chorus = "); | |||||
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); | |||||
} | |||||
if(d_length < 10) { | |||||
return(false); | |||||
} | |||||
if(n_chorus < 1) { | |||||
return(false); | |||||
} | |||||
l_delayline = delayline; | |||||
r_delayline = delayline + d_length/2; | |||||
delay_length = d_length/2; | |||||
num_chorus = n_chorus; | |||||
return(true); | |||||
} | |||||
// This has the same effect as begin(NULL,0); | |||||
void AudioEffectChorus::stop(void) | |||||
{ | |||||
} | |||||
void AudioEffectChorus::modify(int n_chorus) | |||||
{ | |||||
num_chorus = n_chorus; | |||||
} | |||||
int iabs(int x) | |||||
{ | |||||
if(x < 0)return(-x); | |||||
return(x); | |||||
} | |||||
//static int d_count = 0; | |||||
int last_idx = 0; | |||||
void AudioEffectChorus::update(void) | |||||
{ | |||||
audio_block_t *block; | |||||
short *bp; | |||||
int sum; | |||||
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 | |||||
// it isn't as likely to click | |||||
if(num_chorus < 1) { | |||||
// Just passthrough | |||||
block = receiveWritable(0); | |||||
if(block) { | |||||
bp = block->data; | |||||
for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
l_circ_idx++; | |||||
if(l_circ_idx >= delay_length) { | |||||
l_circ_idx = 0; | |||||
} | |||||
l_delayline[l_circ_idx] = *bp++; | |||||
} | |||||
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 | |||||
block = receiveWritable(0); | |||||
if(block) { | |||||
bp = block->data; | |||||
for(int i = 0;i < AUDIO_BLOCK_SAMPLES;i++) { | |||||
l_circ_idx++; | |||||
if(l_circ_idx >= delay_length) { | |||||
l_circ_idx = 0; | |||||
} | |||||
l_delayline[l_circ_idx] = *bp; | |||||
sum = 0; | |||||
c_idx = l_circ_idx; | |||||
for(int k = 0; k < num_chorus; k++) { | |||||
sum += l_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,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); | |||||
} | |||||
} | |||||
/******************************************************************/ | |||||
// A u d i o E f f e c t F l a n g e | |||||
// Written by Pete (El Supremo) Jan 2014 | |||||
#define DELAY_PASSTHRU 0 | |||||
class AudioEffectFlange : | |||||
public AudioStream | |||||
{ | |||||
public: | |||||
AudioEffectFlange(void): | |||||
AudioStream(2,inputQueueArray) { | |||||
} | |||||
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); | |||||
virtual void update(void); | |||||
void stop(void); | |||||
private: | |||||
audio_block_t *inputQueueArray[2]; | |||||
static short *l_delayline; | |||||
static short *r_delayline; | |||||
static int delay_length; | |||||
static short l_circ_idx; | |||||
static short r_circ_idx; | |||||
static int delay_depth; | |||||
static int delay_offset_idx; | |||||
static int delay_rate_incr; | |||||
static unsigned int l_delay_rate_index; | |||||
static unsigned int r_delay_rate_index; | |||||
}; | |||||
/******************************************************************/ | |||||
// A u d i o E f f e c t C h o r u s | |||||
// Written by Pete (El Supremo) Jan 2014 | |||||
#define DELAY_PASSTHRU -1 | |||||
class AudioEffectChorus : | |||||
public AudioStream | |||||
{ | |||||
public: | |||||
AudioEffectChorus(void): | |||||
AudioStream(2,inputQueueArray) { | |||||
} | |||||
boolean begin(short *delayline,int delay_length,int n_chorus); | |||||
virtual void update(void); | |||||
void stop(void); | |||||
void modify(int n_chorus); | |||||
private: | |||||
audio_block_t *inputQueueArray[2]; | |||||
static short *l_delayline; | |||||
static short *r_delayline; | |||||
static short l_circ_idx; | |||||
static short r_circ_idx; | |||||
static int num_chorus; | |||||
static int delay_length; | |||||
}; | |||||
/* | |||||
PROC/MEM 9/4 | |||||
Modify filter_test_f to try out a chorus effect. | |||||
TODO: | |||||
140203 | |||||
o | |||||
I have mixed up the names "chorus" and flange". The sketches named | |||||
chorus have, up to version 'm', actually implemented a flanger. | |||||
From version 'o' onwards the sketches named chorus will implement | |||||
a real chorus effect and flange will do a flanging effect. | |||||
n only changed the effect parameters | |||||
m | |||||
140201 | |||||
l - found the problem at last using my_flange_cd_usd_c | |||||
YES! Way back when I found I hadn't been using d_depth. Now I | |||||
have just discovered that I haven't been using delay_offset!!!!! | |||||
140201 | |||||
k | |||||
interpolation doesn't remove the ticking | |||||
140131 | |||||
j | |||||
>>> The lower the frequency, the less ticking. | |||||
- try interpolation | |||||
140201 | |||||
- got this restored to the way it was last night | |||||
and then reinstated the changes. I had a couple of | |||||
changes to the right channel that were incorrect or | |||||
weren't carried over from the left channel changes | |||||
i | |||||
- don't know why but this version seems to have more "presence" | |||||
than previous versions. | |||||
The presence occurred when "sign = 1" was put in front of the left. | |||||
It essentially makes it a passthrough. | |||||
If both have "sign=1" then it is identical to passthrough | |||||
Ticking is still not fixed | |||||
h | |||||
- add sign reversal. It seems to make audio much clearer | |||||
BUT it hasn't got rid of the ticking noise | |||||
g | |||||
- I wasn't even using delay_depth!!!! | |||||
140131 | |||||
f | |||||
- added code to print the processor and memory usage every 5 seconds | |||||
NOW the problem is to try to remove the ticking | |||||
e | |||||
FOUND the problem with the right channel. It was also in the left channel | |||||
but the placement of the delay line arrays made it more noticeable in the | |||||
right channel. I was not calculating idx properly. In particular, the | |||||
resuling index could be negative. | |||||
I have shortened the delay line to only 2*AUDIO_BLOCK_SAMPLES | |||||
- removed redundancies in the update code. rewrite the block | |||||
instead of getting a new one | |||||
Haven't solved the noise in the right channel yet. | |||||
Tried duplicating right channel code to left but noise stays on the right | |||||
140130 | |||||
d | |||||
The noise stays in the right channel even if it is calculated first | |||||
Switching the L/R inputs doesn't switch the noise to the left channel | |||||
c | |||||
>> Now add a sinusoidal modulation to the offset | |||||
There's an awful noise in both channels but it is much louder in | |||||
the right channel. NOPE - it is ONLY in the right channel | |||||
but the audio does sound like it is working (sort of) except that it | |||||
is rather tinny. Maybe it needs to have the interpolation added. | |||||
b | |||||
- this works with clip16_6s.wav. | |||||
The original of this audio file was from http://www.donreiman.com/Chorus/Chorus.htm | |||||
I reworked it with Goldwave to make it a stereo WAV file | |||||
But with Rick Wakewan's Jane Seymour it seems to act more like | |||||
a high-pass filter. | |||||
a | |||||
- removed FIR stuff and changed the name to AudioEffectChorus | |||||
it's basically a blank template and compiles. | |||||
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. | |||||
6 - MEMCS | |||||
7 - MOSI | |||||
9 - BCLK | |||||
10 - SDCS | |||||
11 - MCLK | |||||
12 - MISO | |||||
13 - RX | |||||
14 - SCLK | |||||
15 - VOL | |||||
18 - SDA | |||||
19 - SCL | |||||
22 - TX | |||||
23 - LRCLK | |||||
AudioProcessorUsage() | |||||
AudioProcessorUsageMax() | |||||
AudioProcessorUsageMaxReset() | |||||
AudioMemoryUsage() | |||||
AudioMemoryUsageMax() | |||||
AudioMemoryUsageMaxReset() | |||||
The CPU usage is an integer from 0 to 100, and the memory is from 0 to however | |||||
many blocks you provided with AudioMemory(). | |||||
*/ | |||||
#include <arm_math.h> | |||||
#include <Audio.h> | |||||
#include <Wire.h> | |||||
//#include <WM8731.h> | |||||
#include <SD.h> | |||||
#include <SPI.h> | |||||
#include <Bounce.h> | |||||
// Number of samples in ONE channel | |||||
#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 FIR filter is turned which | |||||
// makes just pass through the audio | |||||
// Don't use any of the pins listed above | |||||
#define PASSTHRU_PIN 1 | |||||
Bounce b_passthru = Bounce(PASSTHRU_PIN,15); | |||||
//const int myInput = AUDIO_INPUT_MIC; | |||||
const int myInput = AUDIO_INPUT_LINEIN; | |||||
AudioInputI2S audioInput; // audio shield: mic or line-in | |||||
AudioEffectChorus myEffect; | |||||
AudioOutputI2S audioOutput; // audio shield: headphones & line-out | |||||
// Create Audio connections between the components | |||||
// Both channels of the audio input go to the FIR filter | |||||
AudioConnection c1(audioInput, 0, myEffect, 0); | |||||
AudioConnection c2(audioInput, 1, myEffect, 1); | |||||
// both channels from the FIR filter go to the audio output | |||||
AudioConnection c3(myEffect, 0, audioOutput, 0); | |||||
AudioConnection c4(myEffect, 1, audioOutput, 1); | |||||
AudioControlSGTL5000 audioShield; | |||||
// number of "voices" in the chorus which INCLUDES the original voice | |||||
int n_chorus = 3; | |||||
// <<<<<<<<<<<<<<>>>>>>>>>>>>>>>> | |||||
void setup() { | |||||
Serial.begin(9600); | |||||
while (!Serial) ; | |||||
delay(3000); | |||||
pinMode(PASSTHRU_PIN,INPUT_PULLUP); | |||||
// Maximum memory usage was reported as 4 | |||||
// Proc = 9 (9), Mem = 4 (4) | |||||
AudioMemory(4); | |||||
audioShield.enable(); | |||||
audioShield.inputSelect(myInput); | |||||
audioShield.volume(50); | |||||
// Warn that the passthru pin is grounded | |||||
if(!digitalRead(PASSTHRU_PIN)) { | |||||
Serial.print("PASSTHRU_PIN ("); | |||||
Serial.print(PASSTHRU_PIN); | |||||
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"); | |||||
while(1); | |||||
} | |||||
// I want output on the line out too | |||||
audioShield.unmuteLineout(); | |||||
// audioShield.muteHeadphone(); | |||||
Serial.println("setup done"); | |||||
AudioProcessorUsageMaxReset(); | |||||
AudioMemoryUsageMaxReset(); | |||||
} | |||||
// audio volume | |||||
int volume = 0; | |||||
unsigned long last_time = millis(); | |||||
void loop() | |||||
{ | |||||
// Volume control | |||||
int n = analogRead(15); | |||||
if (n != volume) { | |||||
volume = n; | |||||
audioShield.volume((float)n / 10.23); | |||||
} | |||||
if(1) { | |||||
if(millis() - last_time >= 5000) { | |||||
Serial.print("Proc = "); | |||||
Serial.print(AudioProcessorUsage()); | |||||
Serial.print(" ("); | |||||
Serial.print(AudioProcessorUsageMax()); | |||||
Serial.print("), Mem = "); | |||||
Serial.print(AudioMemoryUsage()); | |||||
Serial.print(" ("); | |||||
Serial.print(AudioMemoryUsageMax()); | |||||
Serial.println(")"); | |||||
last_time = millis(); | |||||
} | |||||
} | |||||
// update the button | |||||
b_passthru.update(); | |||||
// If the passthru button is pushed, switch the effect to passthru | |||||
if(b_passthru.fallingEdge()) { | |||||
myEffect.modify(0); | |||||
} | |||||
// If passthru button is released, restore the previous chorus | |||||
if(b_passthru.risingEdge()) { | |||||
myEffect.modify(n_chorus); | |||||
} | |||||
} | |||||
/* | |||||
CHORUS and FLANGE effects | |||||
Both effects use a delay line to hold previous samples. This allows | |||||
the current sample to be combined in some way with a sample that | |||||
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. | |||||
CHORUS EFFECT | |||||
This combines one or more samples up to about 50ms ago. In this | |||||
library, the additional samples are evenly spread through the | |||||
supplied delay line. | |||||
E.G. If the number of voices is specified as 2 then the effect | |||||
combines the current sample and the oldest sample (the last one in | |||||
the delay line). If the number of voices is 3 then the 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) | |||||
where sample(0) represents the current sample and sample(dt) is | |||||
the sample in the delay line from dt milliseconds ago. | |||||
FLANGE EFFECT | |||||
This combines only one sample from the delay line but the position | |||||
of that sample varies sinusoidally. | |||||
In this case the effect can be represented as: | |||||
result = sample(0) + sample(dt + depth*sin(2*PI*Fe)) | |||||
The value of the sine function is always a number from -1 to +1 | |||||
and so the result of depth*(sinFe) is always a number from | |||||
-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! | |||||
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 | |||||
flange or chorus effects don't work well. | |||||
*/ |
/* | |||||
Change the chorus code to produce a flange effect | |||||
PROC/MEM 25/4 | |||||
140204 | |||||
d | |||||
- fixed the problem with user-supplied delay line | |||||
140203 | |||||
c | |||||
140203 | |||||
b | |||||
- when switching to/from passthru, keep the delay line filled | |||||
BUT to be effective must also fix up begin and add another | |||||
function to allow changing to/from passthru without | |||||
reinitialing everything. | |||||
I have mixed up the names "chorus" and flange". The sketches named | |||||
chorus have, up to version 'm', actually implemented a flanger. | |||||
From version 'o' onwards the sketches named chorus will implement | |||||
a real chorus effect and flange will do a flanging effect. | |||||
140202 | |||||
a | |||||
Modify filter_test_f to try out a chorus effect | |||||
m + n only changed the effect parameters | |||||
- | |||||
140201 | |||||
l - found the problem at last using my_flange_cd_usd_c | |||||
YES! Way back when I found I hadn't been using d_depth. Now I | |||||
have just discovered that I haven't been using delay_offset!!!!! | |||||
140201 | |||||
k | |||||
interpolation doesn't remove the ticking | |||||
140131 | |||||
j | |||||
>>> The lower the frequency, the less ticking. | |||||
- try interpolation | |||||
140201 | |||||
- got this restored to the way it was last night | |||||
and then reinstated the changes. I had a couple of | |||||
changes to the right channel that were incorrect or | |||||
weren't carried over from the left channel changes | |||||
i | |||||
- don't know why but this version seems to have more "presence" | |||||
than previous versions. | |||||
The presence occurred when "sign = 1" was put in front of the left. | |||||
It essentially makes it a passthrough. | |||||
If both have "sign=1" then it is identical to passthrough | |||||
Ticking is still not fixed | |||||
h | |||||
- add sign reversal. It seems to make audio much clearer | |||||
BUT it hasn't got rid of the ticking noise | |||||
g | |||||
- I wasn't even using delay_depth!!!! | |||||
140131 | |||||
f | |||||
- added code to print the processor and memory usage every 5 seconds | |||||
NOW the problem is to try to remove the ticking | |||||
e | |||||
FOUND the problem with the right channel. It was also in the left channel | |||||
but the placement of the delay line arrays made it more noticeable in the | |||||
right channel. I was not calculating idx properly. In particular, the | |||||
resuling index could be negative. | |||||
I have shortened the delay line to only 2*AUDIO_BLOCK_SAMPLES | |||||
- removed redundancies in the update code. rewrite the block | |||||
instead of getting a new one | |||||
Haven't solved the noise in the right channel yet. | |||||
Tried duplicating right channel code to left but noise stays on the right | |||||
140130 | |||||
d | |||||
The noise stays in the right channel even if it is calculated first | |||||
Switching the L/R inputs doesn't switch the noise to the left channel | |||||
c | |||||
>> Now add a sinusoidal modulation to the offset | |||||
There's an awful noise in both channels but it is much louder in | |||||
the right channel. NOPE - it is ONLY in the right channel | |||||
but the audio does sound like it is working (sort of) except that it | |||||
is rather tinny. Maybe it needs to have the interpolation added. | |||||
b | |||||
- this works with clip16_6s.wav. | |||||
The original of this audio file was from http://www.donreiman.com/Chorus/Chorus.htm | |||||
I reworked it with Goldwave to make it a stereo WAV file | |||||
But with Rick Wakewan's Jane Seymour it seems to act more like | |||||
a high-pass filter. | |||||
a | |||||
- removed FIR stuff and changed the name to AudioEffectChorus | |||||
it's basically a blank template and compiles. | |||||
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. | |||||
6 - MEMCS | |||||
7 - MOSI | |||||
9 - BCLK | |||||
10 - SDCS | |||||
11 - MCLK | |||||
12 - MISO | |||||
13 - RX | |||||
14 - SCLK | |||||
15 - VOL | |||||
18 - SDA | |||||
19 - SCL | |||||
22 - TX | |||||
23 - LRCLK | |||||
AudioProcessorUsage() | |||||
AudioProcessorUsageMax() | |||||
AudioProcessorUsageMaxReset() | |||||
AudioMemoryUsage() | |||||
AudioMemoryUsageMax() | |||||
AudioMemoryUsageMaxReset() | |||||
The CPU usage is an integer from 0 to 100, and the memory is from 0 to however | |||||
many blocks you provided with AudioMemory(). | |||||
*/ | |||||
#include <arm_math.h> | |||||
#include <Audio.h> | |||||
#include <Wire.h> | |||||
//#include <WM8731.h> | |||||
#include <SD.h> | |||||
#include <SPI.h> | |||||
#include <Bounce.h> | |||||
// Number of samples in ONE channel | |||||
#define FLANGE_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 FLANGE_DELAYLINE (FLANGE_DELAY_LENGTH*2) | |||||
// The delay line for left and right channels | |||||
short delayline[FLANGE_DELAYLINE]; | |||||
// If this pin is grounded the FIR filter is turned which | |||||
// makes just pass through the audio | |||||
// Don't use any of the pins listed above | |||||
#define PASSTHRU_PIN 1 | |||||
Bounce b_passthru = Bounce(PASSTHRU_PIN,15); | |||||
//const int myInput = AUDIO_INPUT_MIC; | |||||
const int myInput = AUDIO_INPUT_LINEIN; | |||||
AudioInputI2S audioInput; // audio shield: mic or line-in | |||||
AudioEffectFlange myEffect; | |||||
AudioOutputI2S audioOutput; // audio shield: headphones & line-out | |||||
// Create Audio connections between the components | |||||
// Both channels of the audio input go to the FIR filter | |||||
AudioConnection c1(audioInput, 0, myEffect, 0); | |||||
AudioConnection c2(audioInput, 1, myEffect, 1); | |||||
// both channels from the FIR filter go to the audio output | |||||
AudioConnection c3(myEffect, 0, audioOutput, 0); | |||||
AudioConnection c4(myEffect, 1, audioOutput, 1); | |||||
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; | |||||
void setup() { | |||||
Serial.begin(9600); | |||||
while (!Serial) ; | |||||
delay(3000); | |||||
pinMode(PASSTHRU_PIN,INPUT_PULLUP); | |||||
// It doesn't work properly with any less than 8 | |||||
// but that was an earlier version. Processor and | |||||
// memory usage are now (ver j) | |||||
// Proc = 24 (24), Mem = 4 (4) | |||||
AudioMemory(8); | |||||
audioShield.enable(); | |||||
audioShield.inputSelect(myInput); | |||||
audioShield.volume(50); | |||||
// Warn that the passthru pin is grounded | |||||
if(!digitalRead(PASSTHRU_PIN)) { | |||||
Serial.print("PASSTHRU_PIN ("); | |||||
Serial.print(PASSTHRU_PIN); | |||||
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); | |||||
// I want output on the line out too | |||||
audioShield.unmuteLineout(); | |||||
Serial.println("setup done"); | |||||
AudioProcessorUsageMaxReset(); | |||||
AudioMemoryUsageMaxReset(); | |||||
} | |||||
// audio volume | |||||
int volume = 0; | |||||
unsigned long last_time = millis(); | |||||
void loop() | |||||
{ | |||||
// Volume control | |||||
int n = analogRead(15); | |||||
if (n != volume) { | |||||
volume = n; | |||||
audioShield.volume((float)n / 10.23); | |||||
} | |||||
if(1) { | |||||
if(millis() - last_time >= 5000) { | |||||
Serial.print("Proc = "); | |||||
Serial.print(AudioProcessorUsage()); | |||||
Serial.print(" ("); | |||||
Serial.print(AudioProcessorUsageMax()); | |||||
Serial.print("), Mem = "); | |||||
Serial.print(AudioMemoryUsage()); | |||||
Serial.print(" ("); | |||||
Serial.print(AudioMemoryUsageMax()); | |||||
Serial.println(")"); | |||||
last_time = millis(); | |||||
} | |||||
} | |||||
// update the button | |||||
b_passthru.update(); | |||||
// If the passthru button is pushed, save the current | |||||
// filter index and then switch the filter to passthru | |||||
if(b_passthru.fallingEdge()) { | |||||
myEffect.modify(DELAY_PASSTHRU,0,0); | |||||
} | |||||
// If passthru button is released, restore the effect | |||||
if(b_passthru.risingEdge()) { | |||||
myEffect.modify(s_idx,s_depth,s_freq); | |||||
} | |||||
} | |||||
/* | |||||
CHORUS and FLANGE effects | |||||
Both effects use a delay line to hold previous samples. This allows | |||||
the current sample to be combined in some way with a sample that | |||||
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. | |||||
CHORUS EFFECT | |||||
This combines one or more samples up to about 50ms ago. In this | |||||
library, the additional samples are evenly spread through the | |||||
supplied delay line. | |||||
E.G. If the number of voices is specified as 2 then the effect | |||||
combines the current sample and the oldest sample (the last one in | |||||
the delay line). If the number of voices is 3 then the 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) | |||||
where sample(0) represents the current sample and sample(dt) is | |||||
the sample in the delay line from dt milliseconds ago. | |||||
FLANGE EFFECT | |||||
This combines only one sample from the delay line but the position | |||||
of that sample varies sinusoidally. | |||||
In this case the effect can be represented as: | |||||
result = sample(0) + sample(dt + depth*sin(2*PI*Fe)) | |||||
The value of the sine function is always a number from -1 to +1 | |||||
and so the result of depth*(sinFe) is always a number from | |||||
-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! | |||||
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 | |||||
flange or chorus effects don't work well. | |||||
*/ |