Also, I linear interpolate between adjacent waveforms. That way as you play up the keyboard, you don't notice a sudden drop off in the high harmonics as the oscillators switch between waveforms.
This works, but there's a problem when doing pitch bends over, say, an octave or so. This is a situation you could run into using portamento. The danger when bending up is that you'll get aliasing when the underlying waveform's harmonic content goes above Nyquist. When bending down, the danger is that the waveform will sound dull as the waveforms harmonic content is well below Nyquist.
So ideally you'd remap the waveform on a per-sample basis instead of just mapping it once when a note is triggered. I'm trying to think of the most efficient way to do this.
Here's an approach I'm thinking about.
First, when a note is triggered, you calculate the phase increment the oscillator will be using by converting the note into a frequency and dividing that by the sample rate.
You also calculate an index into your mip mapped waveforms to decide which one will be used. I'm assuming one waveform per octave.
Then for each sample, you add the FM input value to that index, clipping the index when it goes below zero or above or equal to the waveform count.
Finally, use the FM value to modulate the phase increment.
It would look something like this:
Code: Select all
float Process(float fm)
{
float w = waveIndex + fm;
if(w < 0)
{
w = 0;
}
else if(w > waveCount - 1)
{
w = waveCount - 1;
}
float f = pow(2, fm);
phaseAccumulator += phaseIncrement * f;
// Read waveform data and linear interpolate between them...
}
We'd use the fractional value of w to linear interpolate with the next waveform above the current one. Here it's helpful to have an extra waveform in the map at the top end. In other words the actual waveform count is waveCount + 1.
Anyway, it still seems like a lot of work to do on a per-sample basis, so I dunno...