forked from klang-modular/synthapp
commit
97a41f0ca3
6 changed files with 340 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||||||
|
main |
||||||
|
*.o |
@ -0,0 +1,13 @@ |
|||||||
|
CPPFLAGS=-std=c++11 -Ofast
|
||||||
|
LDLIBS=-lstdc++ -lportaudio
|
||||||
|
|
||||||
|
all: main |
||||||
|
|
||||||
|
clean : |
||||||
|
rm -f main *.o
|
||||||
|
|
||||||
|
main : main.o oscillator.o |
||||||
|
|
||||||
|
main.o: svf.h |
||||||
|
|
||||||
|
oscillator.o: oscillator.h |
@ -0,0 +1,140 @@ |
|||||||
|
#include <cstdlib> |
||||||
|
#include <cmath> |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
#include "portaudio.h" |
||||||
|
|
||||||
|
#include "oscillator.h" |
||||||
|
#include "svf.h" |
||||||
|
|
||||||
|
using namespace std; |
||||||
|
|
||||||
|
#define SAMPLE_RATE 48000 |
||||||
|
|
||||||
|
typedef struct { |
||||||
|
Oscillator oscs[8]; |
||||||
|
SVFx2 filter; |
||||||
|
float time; |
||||||
|
} State; |
||||||
|
|
||||||
|
static int audioCallback( |
||||||
|
const void *inputBuffer, |
||||||
|
void *outputBuffer, |
||||||
|
unsigned long framesPerBuffer, |
||||||
|
const PaStreamCallbackTimeInfo* timeInfo, |
||||||
|
PaStreamCallbackFlags statusFlags, |
||||||
|
void *userData ) { |
||||||
|
|
||||||
|
State *state = (State*) userData; |
||||||
|
float *out = (float*)outputBuffer; |
||||||
|
|
||||||
|
for(int i = 0; i < framesPerBuffer; i++) { |
||||||
|
float value = 0; |
||||||
|
for(int j = 0; j < 8; ++j) { |
||||||
|
value += state->oscs[j].tick(); |
||||||
|
} |
||||||
|
value *= 0.125; |
||||||
|
value *= 0.5; |
||||||
|
state->filter.setFrequency(0.21 + 0.20 * sin(M_PI * state->time)); |
||||||
|
value = state->filter.tick(value); |
||||||
|
*out++ = value; |
||||||
|
*out++ = value; |
||||||
|
state->time += 1.0 / SAMPLE_RATE; |
||||||
|
|
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
void handlePAError(PaError err) { |
||||||
|
Pa_Terminate(); |
||||||
|
cerr << "PortAudio error" << endl; |
||||||
|
cerr << "Error number: " << err << endl; |
||||||
|
cerr << "Error message: " << Pa_GetErrorText(err) << endl; |
||||||
|
exit(err); |
||||||
|
} |
||||||
|
|
||||||
|
static State state; |
||||||
|
int main(void) { |
||||||
|
for(int i = 0; i < 8; ++i) { |
||||||
|
float freq = 440 + i; |
||||||
|
state.oscs[i].phaseStep = (freq * 2 * M_PI) / SAMPLE_RATE; |
||||||
|
state.oscs[i].phase = PIx2 * (float) rand() / RAND_MAX; |
||||||
|
} |
||||||
|
state.filter.setMode(SVFx2::MODE_LP); |
||||||
|
state.filter.setQ(1); |
||||||
|
state.time = 0; |
||||||
|
|
||||||
|
PaStream *stream; |
||||||
|
PaError err; |
||||||
|
|
||||||
|
err = Pa_Initialize(); |
||||||
|
if(err != paNoError) { |
||||||
|
handlePAError(err); |
||||||
|
} |
||||||
|
|
||||||
|
PaHostApiIndex deviceCount = Pa_GetDeviceCount(); |
||||||
|
cout << "Select audio output device:" << endl; |
||||||
|
for(int i = 0; i < deviceCount; ++i) { |
||||||
|
const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(i); |
||||||
|
if(deviceInfo->maxOutputChannels < 2) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
cout << i << ". " << deviceInfo->name << " (" << Pa_GetHostApiInfo(deviceInfo->hostApi)->name << ")" << endl; |
||||||
|
} |
||||||
|
string input; |
||||||
|
PaHostApiIndex deviceIndex; |
||||||
|
for(;;) { |
||||||
|
getline(cin, input); |
||||||
|
try { |
||||||
|
deviceIndex = stoi(input); |
||||||
|
if(deviceIndex >= 0 && deviceIndex < deviceCount) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} catch(...) { } |
||||||
|
cout << "Invalid selection" << endl;
|
||||||
|
} |
||||||
|
|
||||||
|
/* Open an audio I/O stream. */ |
||||||
|
PaStreamParameters streamParams; |
||||||
|
streamParams.device = deviceIndex; |
||||||
|
streamParams.channelCount = 2; |
||||||
|
streamParams.sampleFormat = paFloat32; |
||||||
|
streamParams.suggestedLatency = 0; |
||||||
|
streamParams.hostApiSpecificStreamInfo = NULL; |
||||||
|
err = Pa_OpenStream( |
||||||
|
&stream, |
||||||
|
NULL, /* no input channels */ |
||||||
|
&streamParams, /* stereo output */ |
||||||
|
SAMPLE_RATE, |
||||||
|
0, /* frames per buffer */ |
||||||
|
paNoFlag, |
||||||
|
audioCallback, |
||||||
|
&state); |
||||||
|
if(err != paNoError) { |
||||||
|
handlePAError(err); |
||||||
|
} |
||||||
|
|
||||||
|
err = Pa_StartStream(stream); |
||||||
|
if(err != paNoError) {
|
||||||
|
handlePAError(err); |
||||||
|
} |
||||||
|
|
||||||
|
const PaStreamInfo *streamInfo = Pa_GetStreamInfo(stream); |
||||||
|
cout << "Latency: " << round(1000 * streamInfo->outputLatency) << " ms" << endl; |
||||||
|
|
||||||
|
/* Wait for Return key */ |
||||||
|
getchar(); |
||||||
|
|
||||||
|
err = Pa_StopStream(stream); |
||||||
|
if(err != paNoError) { |
||||||
|
handlePAError(err); |
||||||
|
} |
||||||
|
err = Pa_CloseStream(stream); |
||||||
|
if(err != paNoError) { |
||||||
|
handlePAError(err); |
||||||
|
} |
||||||
|
Pa_Terminate(); |
||||||
|
cout << "Terminating" << endl; |
||||||
|
|
||||||
|
return err; |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
#include "oscillator.h" |
||||||
|
|
||||||
|
Oscillator::Oscillator() { |
||||||
|
mode = MODE_SAW; |
||||||
|
phase = 0; |
||||||
|
phaseStep = (440 * 2 * M_PI) / 44100; |
||||||
|
value = 0;
|
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
/* PolyBLEP oscillator inspired by: https://www.metafunction.co.uk/post/all-about-digital-oscillators-part-2-blits-bleps
|
||||||
|
* Further info about BLEP at: https://pbat.ch/sndkit/blep/
|
||||||
|
*/ |
||||||
|
|
||||||
|
#include <math.h> |
||||||
|
|
||||||
|
#define PIx2 (2 * M_PI) |
||||||
|
|
||||||
|
class Oscillator { |
||||||
|
public: |
||||||
|
enum Mode { MODE_SINE, MODE_SAW, MODE_SQUARE }; |
||||||
|
|
||||||
|
Mode mode; |
||||||
|
float phase; |
||||||
|
float phaseStep; |
||||||
|
float value; |
||||||
|
|
||||||
|
Oscillator(); |
||||||
|
|
||||||
|
float polyBlep(float t) { |
||||||
|
double dt = phaseStep / PIx2; |
||||||
|
|
||||||
|
// t-t^2/2 +1/2
|
||||||
|
// 0 < t <= 1
|
||||||
|
// discontinuities between 0 & 1
|
||||||
|
if (t < dt) { |
||||||
|
t /= dt; |
||||||
|
return t + t - t * t - 1.0; |
||||||
|
} |
||||||
|
|
||||||
|
// t^2/2 +t +1/2
|
||||||
|
// -1 <= t <= 0
|
||||||
|
// discontinuities between -1 & 0
|
||||||
|
else if (t > 1.0 - dt) { |
||||||
|
t = (t - 1.0) / dt; |
||||||
|
return t * t + t + t + 1.0; |
||||||
|
} |
||||||
|
|
||||||
|
// no discontinuities
|
||||||
|
// 0 otherwise
|
||||||
|
else return 0.0; |
||||||
|
} |
||||||
|
|
||||||
|
// This class provides a band-limited oscillator
|
||||||
|
float tick() { |
||||||
|
float t = phase / PIx2; // Define half phase
|
||||||
|
|
||||||
|
if (mode == MODE_SINE) { |
||||||
|
value = sin(phase); // No harmonics in sine so no aliasing!! No Poly BLEPs needed!
|
||||||
|
} else if (mode == MODE_SAW) { |
||||||
|
value = (2.0 * phase / PIx2) - 1.0; // Render naive waveshape
|
||||||
|
value -= polyBlep(t); // Layer output of Poly BLEP on top
|
||||||
|
} else if (mode == MODE_SQUARE) { |
||||||
|
if (phase < M_PI) {
|
||||||
|
value = 1.0; // Flip
|
||||||
|
} else {
|
||||||
|
value = -1.0; // Flop
|
||||||
|
} |
||||||
|
value += polyBlep(t); // Layer output of Poly BLEP on top (flip)
|
||||||
|
value -= polyBlep(fmod(t + 0.5, 1.0)); // Layer output of Poly BLEP on top (flop)
|
||||||
|
} |
||||||
|
|
||||||
|
phase += phaseStep; |
||||||
|
if(phase >= PIx2) { |
||||||
|
phase -= PIx2; |
||||||
|
} |
||||||
|
|
||||||
|
return value; // Output
|
||||||
|
} |
||||||
|
}; |
@ -0,0 +1,107 @@ |
|||||||
|
/* CEM3320/Oberheim/Phrophet-style filter as described here: https://arxiv.org/pdf/2111.05592.pdf */ |
||||||
|
|
||||||
|
#include <math.h> |
||||||
|
|
||||||
|
inline float clamp(float x, float a, float b) { |
||||||
|
return fminf(b, fmaxf(a, x)); |
||||||
|
} |
||||||
|
|
||||||
|
class SVF { |
||||||
|
protected: |
||||||
|
float as1, as2; |
||||||
|
float kdiv; |
||||||
|
float kK, kQ; |
||||||
|
|
||||||
|
inline void updateCoeffs() { |
||||||
|
kdiv = 1 + kK/kQ + kK*kK; |
||||||
|
} |
||||||
|
|
||||||
|
public: |
||||||
|
typedef struct { |
||||||
|
float hp; |
||||||
|
float bp; |
||||||
|
float lp; |
||||||
|
float br; |
||||||
|
float ap; |
||||||
|
} Output; |
||||||
|
|
||||||
|
SVF() { |
||||||
|
as1 = 0; |
||||||
|
as2 = 0; |
||||||
|
kK = tan(M_PI_4); |
||||||
|
kQ = M_SQRT1_2; |
||||||
|
updateCoeffs(); |
||||||
|
} |
||||||
|
|
||||||
|
void setFrequency(float f) { |
||||||
|
kK = tan(M_PI_2 * clamp(f, 0, 1)); |
||||||
|
updateCoeffs(); |
||||||
|
} |
||||||
|
|
||||||
|
void setQ(float q) { |
||||||
|
kQ = q; |
||||||
|
updateCoeffs(); |
||||||
|
} |
||||||
|
|
||||||
|
Output tick(float as) { |
||||||
|
Output out; |
||||||
|
out.hp = (as - (1/kQ + kK) * as1 - as2) / kdiv; |
||||||
|
float au = out.hp * kK; |
||||||
|
out.bp = au + as1; |
||||||
|
as1 = au + out.bp; |
||||||
|
au = out.bp * kK; |
||||||
|
out.lp = au + as2; |
||||||
|
as2 = au + out.lp; |
||||||
|
float abr = out.hp + out.lp; |
||||||
|
float aap = out.hp+out.lp + (1/kQ) * out.bp; |
||||||
|
return out; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
class SVFx2 { |
||||||
|
public: |
||||||
|
enum Mode { MODE_HP, MODE_BP, MODE_LP, MODE_BR, MODE_AP }; |
||||||
|
|
||||||
|
private: |
||||||
|
Mode mode; |
||||||
|
SVF first, second; |
||||||
|
|
||||||
|
public: |
||||||
|
SVFx2() { |
||||||
|
this->mode = MODE_LP; |
||||||
|
} |
||||||
|
|
||||||
|
void setMode(Mode mode) { |
||||||
|
this->mode = mode; |
||||||
|
} |
||||||
|
|
||||||
|
void setFrequency(float f) { |
||||||
|
first.setFrequency(f); |
||||||
|
second.setFrequency(f); |
||||||
|
} |
||||||
|
|
||||||
|
void setQ(float q) { |
||||||
|
first.setQ(q); |
||||||
|
second.setQ(q); |
||||||
|
} |
||||||
|
|
||||||
|
float tick(float as) { |
||||||
|
SVF::Output in = first.tick(as); |
||||||
|
switch(mode) { |
||||||
|
case MODE_HP: |
||||||
|
return second.tick(in.hp).hp; |
||||||
|
|
||||||
|
case MODE_BP: |
||||||
|
return second.tick(in.bp).bp; |
||||||
|
|
||||||
|
case MODE_LP: |
||||||
|
return second.tick(in.lp).lp; |
||||||
|
|
||||||
|
case MODE_BR: |
||||||
|
return second.tick(in.br).br; |
||||||
|
|
||||||
|
case MODE_AP: |
||||||
|
return second.tick(in.ap).ap; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
Loading…
Reference in new issue