Initial commit

main
Thor 1 year ago
commit 97a41f0ca3
  1. 2
      .gitignore
  2. 13
      Makefile
  3. 140
      main.cpp
  4. 8
      oscillator.cpp
  5. 70
      oscillator.h
  6. 107
      svf.h

2
.gitignore vendored

@ -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
}
};

107
svf.h

@ -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…
Cancel
Save