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