Math, oscillators and C (Updated)

So I’ve been working on my own VSTi for some time. It’s a hobby project, and I’m not thinking about releasing it to public (only as Freeware synth, as the VST SDK license does not allow me to publish the source). It’s one of those projects where you use your knowledge in programming with some hobby you have. My hobby is music, see here:

I’m also a software/web developer and my knowledge in languages are ranging from BASIC to C/C++ and from JavaScript to Java and of course PHP.

So this post is more like a question for an advice of fellow developers.

So, yes, my project – it’s basically a software synthesizer with loads of oscillators, FFT based filters and other stuff. Oscillators are one funny thing in music, you can take a simple sine and modulate it’s frequency with another frequency and get a FM synth, or just sum up few streams of sines and get an additive synth. But I want to go further – I want to make a PWM (pulse-width-modulated) synth. With square wave it looks something like this:

On the left side you see a regular square wave, but on the right it’s the same frequency square wave, but with a modulated pulse width. Math underneath the first one might be pretty simple, like this:

float gen_sine(float time, float freq){
  return sin(_pi * time * freq * 2.0);
}
float gen_square(float time, float freq){
  return (gen_sine(time, freq) > 0.0 ? 0.999999 : -0.99999);
}

Also there is an image (actually created with bezier curves) of sine wave:

Now the problem is that I cannot come up with the right algorithm to modulate the sine wave to look like the one on the right. And the same thing also implies to the square wave as it’s function depends on sine generator.

Next up: saw and triangle waves.

This waveform is actually the most versatile one, as changing the puse-width (or actually in this case it’s an angle) would render any saw waveform from descending saw to triangular wave (pictured left) to ascending saw (pictured right) .

float gen_tri(float time, float freq){
  float cycle = 1.0 / freq;
  return (1.0 - abs(fmod(time / cycle, 2.0) * 2.0));
}

So here’s the grand question – How? Can anybody help me with an algorithm to add the pulse-width/angle calculation. The catch here is that I’m trying to avoid any branching (use of if/else). Maybe I’m just over-optimizing it all, but these functions will be called 48k times a second * 128 key polyphony (128 different frequencies for MIDI notes) * 2 channels (stereo) – so that’s quite a lot of calculations per second.

Update 2011-08-29:

I came up with the right algorithm pulse width of square and angle of triangular waveform. First of all I’m not using sine function to calculate square wave and it looks like this:

float Oscilator::genSquare(float time, float freq, float width){
	float cycle = (float)1.0 / freq;
	float position = fmod(time, cycle);
	if (position >= ((cycle / 2.0) * width)){
		return (float)0.999999;
	} else {
		return (float)-0.999999;
	}
}

Triangular wave is a little more performance heavier than before, but you can change angles from descending saw, to triangular waveform, to ascending saw:

float Oscilator::genTri(float time, float freq, float angle){
	float cycle = 1 / freq;
	float position = fmod(time, cycle);
	float slope = (fmod(time, cycle) - ((cycle / 2.0) * angle));
	if (position >= ((cycle / 2.0) * angle)){
		return (float)1.0 - (2.0 * (slope / ((cycle / 2.0) * (2.0 - angle))));
	} else {
		return (float)(2.0 * slope) - 1.0;
	}
}

Thanks a lot to OpenOffice.org Calc, what else could be the best way to develop stuff without trial and error 🙂

Now I’m left with sine wave transformations, and I think I might be on the right path using half-cycles multiplied by angle/width (which is to be somewhere between 0.0 and 2.0, and 1.0 is the normal form)

Update pt.2

So I’ve done it – here is a sine function with variable pulse width (or duty cycle as it’s also called):

float Oscilator::genSine(float time, float freq, float width){
	float cycle = (float)1.0 / freq;
	float position = fmod(time, cycle);
	float pos_w = (cycle / 2.0) * width;
	float neg_w = (cycle / 2.0) * (2.0 - width);
	if (position >= pos_w){
		return (float)sin(2.0 * _pi * fmod(time + neg_w, cycle) * (freq / (neg_w * 2.0))) * -1.0;
	} else {
		return (float)sin(2.0 * _pi * position * (freq / (pos_w * 2.0)));
	}
	//return (float)sin(_pi * time * freq * 2.0);
}

The only problem, that still remains is the roughness of the sine wave edges, when it switches from positive phase to negative one. I’d like to smoother that out, but for now that’s enough.

Leave a comment

Your email address will not be published.