Last active
March 12, 2024 02:44
-
-
Save mrbid/8ee30afce4c6a9a016c97c03cf2d95fb to your computer and use it in GitHub Desktop.
Additive Oscillators with Cutoff & Resonance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| 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