Skip to content

Instantly share code, notes, and snippets.

@mrbid
Last active March 12, 2024 02:44
Show Gist options
  • Select an option

  • Save mrbid/8ee30afce4c6a9a016c97c03cf2d95fb to your computer and use it in GitHub Desktop.

Select an option

Save mrbid/8ee30afce4c6a9a016c97c03cf2d95fb to your computer and use it in GitHub Desktop.
Additive Oscillators with Cutoff & Resonance
/*
James William Fletcher (github.com/mrbid)
April 2023
These are oscillators that have Cutoff and Resonance
as a pre/before-fact filter rather than a post/after-fact
filter.
We do this by constructing a Sawtooth or Squarewave from
additive synthesis of sinusoids. This allows us to get
an accurate cutoff frequency for the waveshape.
At the minimum cutoff frequency for Square or Sawtooth
you will get a sinewave.
Because each shape only really has about 30 sinusoidal
iterations that have audible significance - we blend
between each sinusoidal iteration to smoothen the gap/
transition between popping on a new harmonic.
Resonance uses a linear step to scale the highest frequency
sinusoids the most and the lowest frequency sinusoids
the least. This gives the resonance a fair representation
to the original concept used by post-filters.
There are no optimisations in this code, but to optimise I
would use a wavetable sin() function and a reciprocal
lookup-table for the sin/h part.
Why? I wanted hear how this would perform to decide if I
wanted to turn it into a new software tone generator
such as the Borg ER-3.
Square and Saw really are the only shapes worth rebuilding
harmonically like this using sinusoids. I think of the
TB-303 a lot when making this.
Encoding: 32-bit float
Byte-order: Little-endian (usually)
Sample Rate: 44100
Channels: 1 (mono)
Edit: I didn't add the blending between each cutoff/harmonic step.
The Borg ER-3 (https://github.com/mrbid/Borg-ER-3) now has the
harmonic step blending but lacks the resonance parameter which
didn't sound that great anyway.
When I say we I mean "me and you", you being the reader.
*/
#include <stdio.h>
#include <math.h>
inline float Hz(float hz)
{
return hz * 6.283185482f;
}
float getSquare(float phase, float cutoff, float resonance)
{
cutoff *= 2.f;
float yr = sinf(phase);
for(float h = 3.f; h < cutoff; h+=2.f)
yr += (sinf(phase*h)/h) * (1.f+(h*resonance));
return yr;
}
float getSawtooth(float phase, float cutoff, float resonance)
{
float yr = sinf(phase);
for(float h = 2.f; h <= cutoff; h+=1.f)
yr += (sinf(phase*h)/h) * (1.f+(h*resonance));
return yr;
}
int main()
{
FILE* f1 = fopen("out1.raw", "wb");
if(f1 == NULL){return -1;}
FILE* f2 = fopen("out2.raw", "wb");
if(f2 == NULL){return -2;}
FILE* f3 = fopen("out3.raw", "wb");
if(f3 == NULL){return -3;}
const float sr = 44100;
const float rsr = 1.f/sr;
const float len = sr*13;
const float pfrq = Hz(220.f);
const float pstp = pfrq * rsr;
float p = 0.f;
const float qfrq = Hz(0.2f);
const float qstp = qfrq * rsr;
float q = 0.f;
const float res = 20.f;
float o1,o2,o3;
for(unsigned int i = 0; i < len; i++)
{
p += pstp;
if(i < sr*1){o1 = getSquare(p, res, 0.f);}
else if(i < sr*2){o1 = getSquare(p, res, 0.01f);}
else if(i < sr*3){o1 = getSquare(p, res, 0.02f);}
else if(i < sr*4){o1 = getSquare(p, res, 0.03f);}
else if(i < sr*5){o1 = getSquare(p, res, 0.04f);}
else if(i < sr*6){o1 = getSquare(p, res, 0.05f);}
else if(i < sr*7){o1 = getSquare(p, res, 0.06f);}
else if(i < sr*8){o1 = getSquare(p, res, 0.05f);}
else if(i < sr*9){o1 = getSquare(p, res, 0.04f);}
else if(i < sr*10){o1 = getSquare(p, res, 0.03f);}
else if(i < sr*11){o1 = getSquare(p, res, 0.02f);}
else if(i < sr*12){o1 = getSquare(p, res, 0.01f);}
else if(i < sr*13){o1 = getSquare(p, res, 0.f);}
q += qstp;
float fm = sinf(q) * 0.1f;
o2 = getSquare(p*fm, res, fm);
fm = getSawtooth(q, res, 0.f) * 0.1f;
o3 = getSquare(p*fm, res, fm);
o1 *= 0.25f; // amplitude/volume scaling
o2 *= 0.25f; // amplitude/volume scaling
o3 *= 0.25f; // amplitude/volume scaling
if(fwrite(&o1, 1, sizeof(float), f1) != sizeof(float)){printf("write error\n"); return -4;}
if(fwrite(&o2, 1, sizeof(float), f2) != sizeof(float)){printf("write error\n"); return -5;}
if(fwrite(&o3, 1, sizeof(float), f3) != sizeof(float)){printf("write error\n"); return -6;}
}
fclose(f1);
fclose(f2);
fclose(f3);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment