Some trouble with vibrato

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

Hello.

I'm currently exploring writing a vibrato program/plug-in and I have it mostly working pretty well except for what sounds like sample noise in the output. I've parsed the code and stepped through breakpoints several times, but the problem is eluding me. Here is a sample of the output with the undesired noise:

http://www.christianfloisand.com/vibtest.wav

Here is the code (un-optimized for easier reading):

Code: Select all

SR = 44100
VDEL_T = 0.0005			// variable delay time
PHASE = 0				// phase for sine wave modifier
MDEL_S = (int) VDEL_T * SR	// maximum delay in samples
buffer = new float[1024]   // soundfile buffer
delayBuf = new float[MDEL_S]
memset (delayBuf, 0, sizeof(float)*MDEL_S)
writeIndex = 0    // write position for delayBuf

// the vibrato function, using variable delay and a sine wave LFO 
//    to modulate the delay time

void vibrato (float *input, float *delayBuf, int *writeIndex, int frames)
{
	int readIndex;
	float out, delSamples, rawPos, fract, next;

	for (int i = 0; i < frames; i++) {		// frames = 1024

// modulate delay time by sine wave LFO
		delSamples = (VDEL_T * sin(PHASE) * SR;
		if (delSamples > MDEL_S)
			delSamples = (float) MDEL_S;

// calculate read pos in buffer relative to write pos
		rawPos = *writeIndex - delSamples;
// and check bounds within circular buffer, delayBuf
		rawPos = (rawPos >= 0 ? (rawPos < MDEL_S ? rawPos : rawPos - MDEL_S) : rawPos + MDEL_S;
		readIndex = (int) rawPos; // get read pos in buffer
		frac = rawPos - readIndex; // for interpolation

		if (readIndex != MDEL_S-1)
			next = delayBuf[readIndex+1];
		else
			next = delayBuf[0];

		out = delayBuf[readIndex] + frac * (next - delayBuf[readIndex]);
		delayBuf[*writeIndex] = input[i];
		input[i] = out;

// wrap writeIndex if needed
		if (*writeIndex != MDEL_S)
			*writeIndex++;
		else
			*writeIndex = 0;

		PHASE += TWOPI * freq / SR; // increment phase, freq = 6.4Hz
		if (PHASE >= TWOPI) PHASE -= TWOPI;
		if (PHASE < 0) PHASE += TWOPI;
	}
}

// in the main loop, read from soundfile (in) into buffer 
//   (vector size of 1024), process vibrato on buffer, 
//    then write to output file (out)

do {
			
	readCount = sf_readf_float(in, buffer, VECTORSIZE);

	vibrato(buffer, delayBuf, &writeIndex, readCount);
			
	writeCount = sf_writef_float(out, buffer, readCount);
			
} while (readCount);

Any ideas what is causing the sample noise? I'm using a slightly modified variable delay algorithm from a text ("Audio Programming", by Boulanger & Lazzarini). And so far I'm only handling mono files until I get it perfected (rid of the noise). :)

Many thanks for any feedback.
Chris

Post

write a delay class and apply unit tests.

once you have a working delay object applying a modulation to the time is trivial.

here is a very quick example:

Code: Select all

	class delay
	{
	public:

		delay()
		{
			time = 0;
			size = 0;
			buffer = 0;
			end = 0;
			pos = 0;
		}

		~delay()
		{
			if (buffer) delete [] buffer;
		}

		INL int get_pos()
		{
			int p = pos - buffer - time; 
			return (p >= 0 ? p : p + size);
		}

		INL void set_size(int n)
		{
			if (size != n)
			{
				size = n;
				time = 0;
				fraction = 0.0f;

				float *n = new float[size];
				float *t = buffer;
				buffer = n
				pos = buffer;
				end = buffer + size;
				clear();

				if (t) 
				{
					delete [] t;
				}
			}
		}

		INL int get_size() 
		{ 
			return size; 
		}

		INL void set_time(float v)
		{
			ASSERT(v >= 0);
			ASSERT(v < size);
			time = ftol(v);
			fraction = v - time;
		}

		INL int get_time() { return time; }

		INL void clear()
		{
			if (buffer)
			{
				float *p = buffer;
				while (p < end) 
				{
					*p++ = 0.0f;
				}
			}
		} 

		INL void advance()
		{
			ASSERT(pos >= buffer);
			ASSERT(pos < end);
			ASSERT(end > buffer);
			pos++;
			if (pos >= end) pos = buffer;
		}

		INL float read_lerp(int i, float f)
		{
			ASSERT(pos);

			float *A = pos - i;
			float *B = pos - i - 1;

			if (B < buffer) 
			{
				B += size;
				if (A < buffer) 
				{
					A += size;
				}
			}

			return lerp(*A, *B, f) + 1e-10f;
		}

		INL float read()
		{
			return read_lerp(time, fraction);
		}

		INL void feed(float input)
		{
			ASSERT(pos);
			*pos = 1E-10f + input;
			advance();
		}

		INL float read_feed(float input)
		{
			float tmp = read();
			feed(input);
			return tmp;
		}

	private:

		int size;
		float *buffer;
		float *end;
		float *pos;

		int time;
		float fraction;
	};
it could use a LOT more assertions to make sure everything is always valid. these can be defined only when you have a specific define like ENABLE_ASSERTIONS. you can then set your project configuration up so that you have release, debug, test/assert.

the set_size function could actually allocate a new size and schedule the buffers to be exchanged on the next process call. it's quite complex enough for a simple example though.

then it just becomes a matter of calling

constructor() {
delay.set_size(max_time);
}

process() {
delay.set_time(base_time + modulation);
for (i = 0; i < samples; i++) {
output = delay.read_feed(input);
}
}

if you do anything wrong the assertions will trigger to show you what you messed up.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

I did start out by coding the algorithm I gave above to work with a simple constant delay, and it does -- it works perfectly fine with a constant delay. Looking over your code that you provided helped me find a solution though! At the top of the loop where I calculate the variable delay in samples I had:

delaySamples = baseDelayTime * sine() * SR

Then I changed it to this, which solved my problem:

delaySamples = ((baseDelayTime / 2) + (sine() * baseDelayTime / 2)) * SR

Works like a charm! :)
Thanks for the feedback.

Post

don't forget that you can't have a delay of zero if you read before feed. you have to add special code to handle that. you'll figure that out if you implement a comb filter / flanger.
Free plug-ins for Windows, MacOS and Linux. Xhip Synthesizer v8.0 and Xhip Effects Bundle v6.7.
The coder's credo: We believe our work is neither clever nor difficult; it is done because we thought it would be easy.
Work less; get more done.

Post

Yeah, that makes sense. I'll keep that in mind, thanks!

Post Reply

Return to “DSP and Plugin Development”