1
$\begingroup$

I am trying to code a Variable Delay (chorus/flanger) effect in MATLAB using a paradigm that would be friendly for porting to a lower level language like C. What I currently have is a working echo/delay that uses read/write indices to write into a buffer. I'm now trying to use an LFO (low freq sine wave) to modulate the delay time but can't seem to figure it out. Sometimes my errors sound "cool" but they aren't correct or what I was trying to do.

% Create LFO
lfo = abs(sin(2*pi*lfo_rate*[0:length(output)]/fs));

    % Perform the Echo Loop
    for i=1:length(x_t)

    % Write Sample Into Delay Buffer
    buffer(writer) = x_t(i);

    % Get All Delay Into Output
    if i <= length(x_t)
        output(i) = x_t(i) + (b_n * buffer(reader));
    elseif i > length(x_t)
        output(i) = b_n * buffer(reader);
    end

    % Circular Buffer
    writer = writer + 1;
        if writer > length(buffer)
            writer = 1;
        end
    reader = reader + 1;
        if reader > length(buffer)
            reader = 1;
        end
    end

I've been trying to use the LFO Vector to modulate the readers position, but I'm pretty much stuck and the stuff I can find online about this sort of thing isn't too helpful either.

$\endgroup$

4 Answers 4

0
$\begingroup$

The best source on this topic with C solutions for flanger and chorus is the DSP book by Orfanidis:

Orfanidis

For Matlab code examples on audio effects (including flanger on page 48) see the file below

Marshall

$\endgroup$
0
$\begingroup$

I answer this way because i don't have permission for comments. I don't have a complete solution. What happens with an chorus flanger is this. Chorus Flanger black box
Signal xt goes in split in two(or maybe more) signal paths and mixed together at the end. The difference is the length of the delay (Flanger <10ms and chorus 20 - 60ms).
First your Lfo discription isn't right. It should me something like :lfo = A *sin(2*pi*lfo_rate*[0:length(output)]/fs) + Offset; Where A is half the time (Positive and negative) and offset the center time where the LFO is turning around. And `A =< offset' otherwise the time gets negative

The difficulty is i think a variable buffersize. let say a chorus center time is 50 ms with ±10 ms delay. no Lfo you have 44100 * 20/1000 = 2205 samples. the max buffer = 2646 samples and the min buffer = 1764 Samples.

Hope you can do something with the info

$\endgroup$
0
$\begingroup$

I'm no matlab expert. But is this really what you want?

lfo = abs(sin(2*pi*lfo_rate*[0:length(output)]/fs));

That's going to have some sharp discontinuities in it. Try plotting it. The abs() probably isn't helpful. Certainly it is't the standard waveshape for these things.

enter image description here

Broadly you'll want:

  • An LFO (lfo_value)
  • A base delay time (delay_time)
  • To modulate the base delay time with the LFO (delay_time + lfo_value)
  • A delay (possibly one with interpolation when you read the buffer (see source below)).
  • A mixer on the output (i.e. sig_out = effect * mix + original * (1.0 - mix))

Presumably the delay is working correctly. Test that first with the mixer before adding the modulation.

Here's a fast simple delay line as a C++ class in case it's useful (you'll probably need your own version of AudioSampleBuffer but it's basically an array of float[] taken from the JUCE framework (http://www.juce.com/)):

class SingleDelay
{
public:
    SingleDelay (int bsize)
        :
        buf (1, nextPowerOfTwo (bsize)),
        mask (nextPowerOfTwo (bsize) - 1),
        ptr (mask)
    {
        buf.clear();
        data = buf.getWritePointer (0);
    }

    ~SingleDelay()
    {}

    /** Return the value for a fractional delay time in samples. */

    float geti (float delayTime)
    {
        const int   dInt    = delayTime;
        const float dFrac   = delayTime - float (dInt);

        const int dlyIndex  = (ptr + dInt) & mask;

        const float a = data[dlyIndex];
        const float b = data[ (dlyIndex + 1) & mask];

        return a * (1.0f - dFrac) + b * dFrac;
    }
    /** Store the sample. Do this before returning delay taps
     for an accurate timing. 

     @note ptr points to the sample just written. So if/when we read later
     with a delay time of zero we actually get a sample!
     */
    void put (float inSignal)
    {
        data[mask & --ptr] = inSignal;
    }

private:
    float* data;
    AudioSampleBuffer buf;
    int mask;
    int ptr;
};
$\endgroup$
1
  • $\begingroup$ In case you are curious about how to get rid of the discontinuity, try squaring the sine function instead of using absolute value. $\endgroup$
    – soultrane
    Sep 7, 2015 at 4:40
0
$\begingroup$

I'm not familiar with sound effects, but given your example code, the answer by Jan-Bert and some information found on the internet I've come up with the implementation below.

I've added the depth parameter, for which I found a value less than 1 very pleasant. Since I don't know the effect, I have no idea about desired parameter values/ranges, i.e, the lfo Frequency, Amplitude and Mean.

I'd love to receive some feedback on useful settings.

% Read audio data
[x, fs] = audioread('myaudiofile.wav');

% parameters:
lfo_f_Hz = 0.01; % LFO frequency in Hz
lfo_A_ms = 2.0;  % Amplitude of delay variations in ms
lfo_M_ms = 5.0;  % Average delay in ms
depth = 0.3; % Ratio between direct and echo contributions

assert(lfo_A_ms < lfo_M_ms);

% Create LFO
lfo_A = lfo_A_ms*fs/1000;  % Delay variations in samples
lfo_M = 5*fs/1000;  % Average delay in samples
N = length(x);
n = 0:(N-1); 

% Allow only integer delay values
% OPTION: calculate only one period and used it circular (saves memory)
lfo = round((lfo_A * sin(2*pi*f_lfo_Hz/fs*n)) + lfo_M); 

y = zeros(size(x)); 
for ii = 1:N        
    if ii <= lfo(ii)  % No flanger during startup.
        y(ii) = x(ii); 
    elseif lfo(ii) >= N  % No flanger at the end.
        y(ii) = x(ii); 
    else  % Apply flanger
        % REMARK: Save at least lfo_A + lfo_M input samples!
        y(ii) = x(ii) + depth * x(ii - lfo(ii)); 
    end    
end

audiowrite('flanger.wav', y, fs);
%p = audioplayer(x,fs);
%p.play()
p = audioplayer(y,fs);
p.play()
$\endgroup$
3
  • $\begingroup$ If you round the lfo signal it will not sound very musical. Instead, you allow for float lfo sample but you need to use interpolation to find the approximate x() samples. $\endgroup$
    – Harris
    Sep 4, 2015 at 21:14
  • $\begingroup$ @Harris: yes, this is necessary for the effect to sound good at very low LFO requencies. Brian: your code works well, but I think there is a slight typo in the line where you define the 'lfo' variable. f_lfo should be lfo_f_Hz. $\endgroup$
    – soultrane
    Sep 7, 2015 at 4:19
  • $\begingroup$ @soultrane Thanx. I modified the code. $\endgroup$
    – Brian
    Sep 7, 2015 at 10:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.