| // A u d i o E f f e c t C h o r u s | // A u d i o E f f e c t C h o r u s | ||||
| // Written by Pete (El Supremo) Jan 2014 | // Written by Pete (El Supremo) Jan 2014 | ||||
| // 140529 - change to handle mono stream - change modify() to voices() | |||||
| // 140219 - correct storage class (not static) | // 140219 - correct storage class (not static) | ||||
| boolean AudioEffectChorus::begin(short *delayline,int d_length,int n_chorus) | boolean AudioEffectChorus::begin(short *delayline,int d_length,int n_chorus) | ||||
| Serial.println(")"); | Serial.println(")"); | ||||
| l_delayline = NULL; | l_delayline = NULL; | ||||
| r_delayline = NULL; | |||||
| delay_length = 0; | delay_length = 0; | ||||
| l_circ_idx = 0; | l_circ_idx = 0; | ||||
| r_circ_idx = 0; | |||||
| if(delayline == NULL) { | if(delayline == NULL) { | ||||
| return(false); | return(false); | ||||
| } | } | ||||
| l_delayline = delayline; | l_delayline = delayline; | ||||
| r_delayline = delayline + d_length/2; | |||||
| delay_length = d_length/2; | delay_length = d_length/2; | ||||
| num_chorus = n_chorus; | num_chorus = n_chorus; | ||||
| return(true); | return(true); | ||||
| } | } | ||||
| void AudioEffectChorus::modify(int n_chorus) | |||||
| void AudioEffectChorus::voices(int n_chorus) | |||||
| { | { | ||||
| num_chorus = n_chorus; | num_chorus = n_chorus; | ||||
| } | } | ||||
| int c_idx; | int c_idx; | ||||
| if(l_delayline == NULL)return; | if(l_delayline == NULL)return; | ||||
| if(r_delayline == NULL)return; | |||||
| // do passthru | // do passthru | ||||
| // It stores the unmodified data in the delay line so that | // It stores the unmodified data in the delay line so that | ||||
| 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; | |||||
| } | } | ||||
| // L E F T C H A N N E L | // L E F T C H A N N E L | ||||
| *bp++ = sum/num_chorus; | *bp++ = sum/num_chorus; | ||||
| } | } | ||||
| // send the effect output to the left channel | |||||
| // transmit the block | |||||
| 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; | |||||
| 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); | |||||
| } | |||||
| } | } | ||||
| { | { | ||||
| public: | public: | ||||
| AudioEffectChorus(void): | AudioEffectChorus(void): | ||||
| AudioStream(2,inputQueueArray), num_chorus(2) | |||||
| AudioStream(1,inputQueueArray), num_chorus(2) | |||||
| { } | { } | ||||
| boolean begin(short *delayline,int delay_length,int n_chorus); | boolean begin(short *delayline,int delay_length,int n_chorus); | ||||
| virtual void update(void); | virtual void update(void); | ||||
| void stop(void); | void stop(void); | ||||
| void modify(int n_chorus); | |||||
| void voices(int n_chorus); | |||||
| private: | private: | ||||
| audio_block_t *inputQueueArray[2]; | |||||
| audio_block_t *inputQueueArray[1]; | |||||
| short *l_delayline; | short *l_delayline; | ||||
| short *r_delayline; | |||||
| short l_circ_idx; | short l_circ_idx; | ||||
| short r_circ_idx; | |||||
| int num_chorus; | int num_chorus; | ||||
| int delay_length; | int delay_length; | ||||
| }; | }; |
| /* | /* | ||||
| 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: | FMI: | ||||
| The audio board uses the following pins. | The audio board uses the following pins. | ||||
| // Number of samples in ONE channel | |||||
| // Number of samples in each delay line | |||||
| #define CHORUS_DELAY_LENGTH (16*AUDIO_BLOCK_SAMPLES) | #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 | // 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 | ||||
| AudioEffectChorus myEffect; | |||||
| AudioEffectChorus l_myEffect; | |||||
| AudioEffectChorus 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 chorus effect | // 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; | AudioControlSGTL5000 audioShield; | ||||
| Serial.println(") is grounded"); | 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); | 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 | // I want output on the line out too | ||||
| audioShield.unmuteLineout(); | audioShield.unmuteLineout(); | ||||
| // audioShield.muteHeadphone(); | // audioShield.muteHeadphone(); | ||||
| 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, switch the effect to passthru | |||||
| // If the passthru button is pushed, switch the chorus on | |||||
| if(b_passthru.fallingEdge()) { | 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()) { | if(b_passthru.risingEdge()) { | ||||
| myEffect.modify(n_chorus); | |||||
| l_myEffect.voices(0); | |||||
| r_myEffect.voices(0); | |||||
| } | } | ||||
| } | } |
| occurred in the past. An obvious effect this would allow would be | occurred in the past. An obvious effect this would allow would be | ||||
| an echo where the current sample is combined with a sample from, | an echo where the current sample is combined with a sample from, | ||||
| say, 250 milliseconds ago. The chorus and flange effects do this | 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 | CHORUS EFFECT | ||||
| This combines one or more samples up to about 50ms ago. In this | This combines one or more samples up to about 50ms ago. In this | ||||
| 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. | ||||
| -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. | ||||
| */ | */ |