Blaster was my first synthesizer, and when I went to upgrade it I realized that I had made some fundamental mistakes with the way I was handling the oscillators of the plugin. I think these changes are important, as it makes the synth work as expected, but it does really badly break backwards compatability. So in the interest of not breaking your old projects I decided to make the upgrade into a separate project.
This page is mostly for my own sanity, to discuss my mistakes and what I learned from them openly, and through that find inner peace or something.
Something is amiss
Let's look at Blaster. If you leave the waveform as a saw and turn the sub oscillator off, the result should be a plain saw wave. (there is some additional processing afterwards, a lowpass and some distortion, but the issue is still there when that's disabled).
Yeah that's not correct.
So what's going on?
The dramatically oversimplified way you make oscillators in JUCE, the plugin library I'm using, is you have a mathematical function that you use with an increasing "phase" value that advances at a speed decided by the frequency of the note played.
As mentioned, Blaster is my first synthesizer project, and so although it's not used in the final code the first oscillator I wrote was a basic sine wave oscillator. Here's the code:
x is the phase (this will be a trend for the rest of this writeup), and I just chuck it into std::sin, which is the C++ standard function for a sine wave. Easy peasy, let's look at the saw wave:
If you can spot the problem already you're very clever. This code canmake a correct saw wave, but only if x cycles over the range of 0 to pi.
So... does it?
Of course not! Actually the biggest part of the clean up was figuring out just what the heck the range of x even was. Looking up one level from where this is used gives.
Okay, so I'm taking the phase that I'm tracking in the oscillator and then multiplying it by twoPi. I remember settling on twoPi instead of one pi because it's what sounded right when I was putting the initial plugin together, one pi sounded an octave too high. That lack of understanding then coming back to haunt me now is the overarching theme of this project.
Follow up question, what's the range of that phase value?
according to this is goes from -1.0 to 1.0
So the phase goes from -1.0 to 1.0, then gets multiplied by twoPi, then the function is that divided by pi. My head is spinning and I would like a slice of pie.
Overcomplicating synthesis
You might have realized that all of the pi constants in this can be removed, and indeed that's what I did for blaster plus. Not knowing exactly what x is going into these functions lead to a bunch of extra math constants showing up and causing problems that are difficult to see at a glance.
To fix a problem like this, it's a good idea to start by making an assertion like this:
every oscillator function will be called with the range of -1.0 to 1.0
that means no multiplying by pi ahead of time. With that set in stone you can write the functions with the knowledge of exactly what the range of x is and that it will be consistent.
Sine is slightly more complicated, but everything else is much simpler, and more importantly there's no extra pis sneaking in from anywhere else.
We're not out of the woods yet though
If you've tried using the phase adjustment slider in Blaster you might have noticed that it doesn't work the way you might expect. The point of the adjustment is to be able to align your bass' phase, so changing the slider value should not effect the sound at all other than where in the phase cycle it starts. This is very obviously not the case.
The problem? Just a mistake in the code. The main oscillator is really three saw waves, and the phase adjustment was applied to all three, but then immediately getting overwritten by the detune value on two of the three oscillators.
oops
Conclusions
I hope that shines a bit of light on why this is a separate plugin instead of an update to Blaster. Changing the underlying math like this would make the save data on old projects sound totally different.
I think that being able to look back at Blaster's code and think it's obviously bad and wrong is secretly a good thing. It's proof that I'm learning and becoming a better developer as I work on more projects! Even if it is a little embarassing right now, I like to think that the road to being good at something is paved with mistakes like these.
Thanks for reading my ramble, until next time!