| @@ -3148,3 +3148,415 @@ void AudioFilterFIR::update(void) | |||
| 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); | |||
| } | |||
| } | |||
| @@ -576,6 +576,69 @@ private: | |||
| /******************************************************************/ | |||
| // 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; | |||
| }; | |||
| @@ -0,0 +1,264 @@ | |||
| /* | |||
| 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); | |||
| } | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| /* | |||
| 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. | |||
| */ | |||
| @@ -0,0 +1,314 @@ | |||
| /* | |||
| 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); | |||
| } | |||
| } | |||
| @@ -0,0 +1,44 @@ | |||
| /* | |||
| 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. | |||
| */ | |||