Calculating oscillator frequencies

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

Post

I'm currently working on my first traditional synth plugin and I'm wondering about the most efficient way to deal with frequency changes to the oscillators & filters as it concerns buffer sizes.

During my first plugin (a non-traditional synth) I learned, thanks to @Urs on this forum, to run many smaller/tighter loops for each component rather than running through every component per sample...which greatly improved speed. But now I'm not sure how to do that with things like oscillator and filter frequencies which seem like they need to be computed every sample...or do they?

Should oscillator frequencies be updated every sample? Every 4, or 8? It seems that filter frequency could be a little more forgiving...maybe every 16 or 32 samples?

Or maybe calculate frequency every N samples and ramp during my loop? Or break the buffer into sub-buffers and step frequencies instead of ramping?

And of course, there are multiple factors that affect the oscillator's frequency...base note, portamento, lfos and other modulation sources.

Any guidance for efficiency would be greatly appreciated.

Post

Yes, you will gain a lot by running every operator in your synth in small bursts, eg 8 or 16 samples. More than that doesn't gain me so much, and you need to update your controlling values fairly often.
The performance win is probably due to cache trashing, both for data AND for code(!)
My roll out is currently at 8, with big param update handler called every 64 samps.

This means that all your parameters should be ramps that you increment every sample. You'll have dozens of snippets like
next_op = some_op * (some_base + some_change);
some_change += some_change_incr;

But of course there's more to it...

Oscil freq must be updated every sample, and you must use smooth frequency change waveforms.
(strongly worded because strongly felt ;) )

Otherwise you get small snags in the waveform, each of these is a wide-spectrum noise burst (at low amplitude), and many of those together make for a dirty thin metallic tinge to your overall sound.

This a well-known problem, so almost everybody ever run several low-pass filters later in the sound chain, shaping the dirty thin metallic sound into something muffled and smudgy.

So it gets complicated, and depends on the oscil: table-based, sine or other waveform.

The cleanest, least noisy waveform is a sinewave, so you want the controlling signals (eg your oscil frequency) to be as sine-like as possible when changing direction.

All user inputs (MIDI controllers, note values, or mouse or finger stumbling over screen pixels) come into your synth as sample-and-hold big jumps at long intervals, it looks as a cartoon nightmare staircase with very uneven steps. So what can you do with them?
  • (1) create linear segments. Set a time, say 100-1000 samples away, calculate the new internal synth param value, and step towards it each sample. Same code as above.
  • (2) do something about the corners of the line segments, maybe look at smoothstep, change them into sinewave segments, lots of options to make your code complicated.
  • (3) weep, because patch changes is now a huge mess.
This is basically the same problem as your buffer issues but worse. When you solve this, buffer handling is just the outermost loop around your synth code.

(1) is not *the* solution btw, often you don't want to move to a frequency or delay time etc in a straight line, you can use exponential curves too. More ways to complicate the controlling code.

(Here there's a high risk that I start evangelizing my own solution, the squinewave multiple-shape oscil that you can lift from Csound code right now, which I really should and will publish license-free somewhere soon, because I think almost every softsynth should kick out their sinetables and BLEP sine/saw/square and use that instead.
BUT you have your own plans and ideas, so it's much better to leave it here and just wish you good luck!)

Good luck, have fun!
/rasmus

Post

Thank you. This is all good and kind of what I was expecting...interpolators almost everywhere. I'm mostly concerned with making the expensive frequency calculations on every oscillator and filter every sample. Is there a cheaper way than calling std::pow() everywhere?

Post

Ah, yep. You get the exponential curve for free by multiplying every sample instead of adding. Or adding to the adding:
Just calculate freq start and end points using pow, then add to the phase *increment* every sample to get the right sweep. ie
oscout = sin(phase);
phase += phase_inc;
phase_inc += sweep_inc;

How expensive it gets depends on synth structure. If you only calculate freq when you get new note values, or user input, then it's no problem.
Sometimes you can also do a few calculations in GUI thread, before sending params into synth (but that's a dodgy path, should keep stuff properly separated (note to self)).

With more detail control it's good if you can do with pow calls say every 64 samples (param update cycle), then it should usually not be a problem unless you go wild with 100's of oscils.
Even then. My very old additive synth used to be waiting time, today it runs 2-5x realtime. That's with 500 constantly changing oscils in parallel, with AM and FM. (admittedly there's a lot of integer fixed-point math in there and not sure if any pow calls ever, maybe when generating sine tables)
I'm thinking about desktop computers now, phone synths may be more restricted.

For FM synth you may not need to calculate so often, the oscil setup drives frequency changes for you.
So, depends a lot.

Post Reply

Return to “DSP and Plugin Development”