rojohound
Thanks a lot for the link. Very cool approach there. But it is basically prerendering sound for an octave and then triggers the according note-sound. A runtime calculation of the sound (to be able to modulate it), is not possible with it. Still, it is a cool addition to C2!
Animmaniac
I'm impressed! You are more lightweight in terms of processing, but generating good results. You're right with breaking the smaller spike, but considering the smaller cpu footprint it is probably acceptable.
The calculations used in the wavecycle have something special. After that talented guy made a first impressive demo, I insisted (yes, I can be that annoying) that all spikes in either direction should always behave convex (as in "bend to the outer sides"). And he did it. Even at extreme offsets it stays intact. Here are some screens taken from a scope while playing the osc (you see approx. 2 cycles):
wave at offset 0.0
wave at offset -0.5
wave at offset +0.5
wave at offset +0.95
This behaviour is the reason for that warm sound even when the harmonics kick in. It is done in two parts. The second part is called "sine approximation", but it is pure assembler code, so I can't tell how it is approximated:
streamin x; // -1...+1
streamout cos1x;
/* minimum harmonic distortion */
float c1=-6.280099538;
float c2=41.10687865;
float c3=-74.00457656;
float FMP25=-0.25;
float FP25=.25;
int abs=2147483647;
movaps xmm0,x;
mulps xmm0,FP25;
subps xmm0,FP25;
andps xmm0,abs;
addps xmm0,FMP25;
movaps xmm1,xmm0;
mulps xmm1,xmm1;
movaps xmm2,c3;
mulps xmm2,xmm1;
addps xmm2,c2;
mulps xmm2,xmm1;
addps xmm2,c1;
mulps xmm2,xmm0;
movaps cos1x,xmm2;[/code:289shwv0]
The first part, however, is in a C-like DSP code. These calculations are done for each sample point at sample rate (the rate can vary depending on the DAW, but most will use 44.1kHz):
[code:289shwv0]streamin f;
streamin m;
streamout wav;
float ramp;
float ma,mb,a,b,f2,mm1;
float abs; // bitmask
stage(0){
abs = 3.4e38|0.999999|0.1;
ramp = 1 - m - 2*f;
}
stage(2){
hop(32){ // some abreviations to speed up the iteration
f2 = 2*f;
mm1 = m - 1;
ma = 1/(1 + m);
mb = 1/(1 - m);
b = (1 - (abs&m))/(1 + (abs&m));
a = 1 - b;
}
ramp = ramp + f2;
ramp = ramp - 4&(ramp>2); // ramp (-2...+2)
wav = abs&ramp + mm1; // biased triangle (m-1...m+1)
wav = wav*ma&(wav>0) + wav*mb&(wav<0); // "PWM"-triangle (-1...+1)
wav = wav + (wav/(a*(abs&wav) + b) - wav)&((m*wav)>0); // rational mapping of long halfwave
}[/code:289shwv0]
stage(0) is like an initialize-procedure. It is only executed once per new note. stage(2) is executed on each sample point. hop(32) means that all enclosed code is only calculated every 32 sample points. The streamin f is an input which is a mixture of frequencies for each note playing. The streamin m is the input for the current modulation value. Streamout wav is the mixture of amplitude values for each note playing. Each ampersand (&) is a bitmask. It works a bit like conditionals, but with all bits set to 0 when false and all bits set to 1 when true. The result is then bitwise and'ed with the preceding variable.
At least at this point you probably see that I wouldn't have done this on my own...