forked from klang-modular/synthapp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
6.7 KiB
240 lines
6.7 KiB
#include <cstdlib>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
|
|
#include "portaudio.h"
|
|
#include "portmidi.h"
|
|
#include "porttime.h"
|
|
|
|
#include "oscillator.h"
|
|
#include "svf.h"
|
|
|
|
using namespace std;
|
|
|
|
#define SAMPLE_RATE 48000
|
|
#define NUM_OSCS 7
|
|
|
|
float noteToFreq(int note) {
|
|
return 440 * pow(2, (note - 69) / 12.0);
|
|
}
|
|
|
|
typedef struct {
|
|
Oscillator oscs[NUM_OSCS];
|
|
SVFx2 filter;
|
|
float time;
|
|
PmStream* pmStream;
|
|
} State;
|
|
|
|
// PortAudio callback for audio I/O
|
|
static int paCallback(
|
|
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 < NUM_OSCS; ++j) {
|
|
value += state->oscs[j].tick();
|
|
}
|
|
value /= NUM_OSCS;
|
|
value *= 0.5;
|
|
state->filter.setFrequency(0.21 + 0.20 * cos(M_PI * state->time));
|
|
value = state->filter.tick(value);
|
|
*out++ = value;
|
|
*out++ = value;
|
|
state->time += 1.0 / SAMPLE_RATE;
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// PortTimer callback for MIDI I/O
|
|
static void ptCallback(PtTimestamp timestamp, void* userData) {
|
|
State* state = (State*) userData;
|
|
PmEvent events[8];
|
|
int numEvents = Pm_Read(state->pmStream, events, 8);
|
|
for(int i = 0; i < numEvents; ++i) {
|
|
PmMessage msg = events[i].message;
|
|
|
|
int cmd = (msg >> 4) & 0xF;
|
|
int chan = msg & 0xF;
|
|
|
|
if(cmd == 0x9) {
|
|
int vel = (msg >> 16) & 0x7F;
|
|
if(vel > 0) {
|
|
int note = (msg >> 8) & 0x7F;
|
|
float centreFreq = noteToFreq(note);
|
|
float spreadCoeff = 0.004;
|
|
for(int osc = 0, pos = -3; pos <= 3; ++osc, ++pos) {
|
|
float freq = centreFreq * (1 + pos * spreadCoeff);
|
|
state->oscs[osc].phaseStep = (freq * 2 * M_PI) / SAMPLE_RATE;
|
|
}
|
|
state->time = 0;
|
|
}
|
|
|
|
}
|
|
|
|
cout << "MIDI:";
|
|
for(int j = 0; j < 24; j += 8) {
|
|
cout << " " << hex << ((msg >> j) & 0xFF);
|
|
|
|
}
|
|
cout << endl;
|
|
}
|
|
}
|
|
|
|
void handlePMError(PmError err) {
|
|
Pa_Terminate();
|
|
Pm_Terminate();
|
|
cerr << "PortMidi error" << endl;
|
|
cerr << "Error number: " << err << endl;
|
|
cerr << "Error message: " << Pm_GetErrorText(err) << endl;
|
|
exit(err);
|
|
}
|
|
|
|
void handlePAError(PaError err) {
|
|
Pa_Terminate();
|
|
Pm_Terminate();
|
|
cerr << "PortAudio error" << endl;
|
|
cerr << "Error number: " << err << endl;
|
|
cerr << "Error message: " << Pa_GetErrorText(err) << endl;
|
|
exit(err);
|
|
}
|
|
|
|
int main(void) {
|
|
static State state;
|
|
for(int i = 0; i < 7; ++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;
|
|
|
|
// Initialise PortMIDI and PortAudio libraries
|
|
PmError pmErr = Pm_Initialize();
|
|
if(pmErr != pmNoError) {
|
|
handlePMError(pmErr);
|
|
}
|
|
PaError paErr = Pa_Initialize();
|
|
if(paErr != paNoError) {
|
|
handlePAError(paErr);
|
|
}
|
|
|
|
int pmDeviceCount = Pm_CountDevices();
|
|
cout << "Select MIDI input device:" << endl;
|
|
for(int i = 0; i < pmDeviceCount; ++i) {
|
|
const PmDeviceInfo* deviceInfo = Pm_GetDeviceInfo(i);
|
|
if(!deviceInfo->input) {
|
|
continue;
|
|
}
|
|
cout << i << ". " << deviceInfo->name << endl;
|
|
}
|
|
int pmDeviceIndex;
|
|
for(;;) { // loop for input until valid choice is made
|
|
string input;
|
|
getline(cin, input);
|
|
try {
|
|
pmDeviceIndex = stoi(input);
|
|
if( pmDeviceIndex >= 0 &&
|
|
pmDeviceIndex < pmDeviceCount &&
|
|
Pm_GetDeviceInfo(pmDeviceIndex)->input) {
|
|
break;
|
|
}
|
|
} catch(...) { } // stoi() throws an exeption for invalid integers
|
|
cout << "Invalid selection" << endl;
|
|
}
|
|
|
|
PaHostApiIndex paDeviceCount = Pa_GetDeviceCount();
|
|
cout << "Select audio output device:" << endl;
|
|
for(int i = 0; i < paDeviceCount; ++i) {
|
|
const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(i);
|
|
if(deviceInfo->maxOutputChannels < 2) {
|
|
continue;
|
|
}
|
|
cout << i << ". " << deviceInfo->name << " (" << Pa_GetHostApiInfo(deviceInfo->hostApi)->name << ")" << endl;
|
|
}
|
|
PaHostApiIndex paDeviceIndex;
|
|
for(;;) { // loop for input until valid choice is made
|
|
string input;
|
|
getline(cin, input);
|
|
try {
|
|
paDeviceIndex = stoi(input);
|
|
if( paDeviceIndex >= 0 &&
|
|
paDeviceIndex < paDeviceCount &&
|
|
Pa_GetDeviceInfo(paDeviceIndex)->maxOutputChannels >= 2) {
|
|
break;
|
|
}
|
|
} catch(...) { } // stoi() throws an exeption for invalid integers
|
|
cout << "Invalid selection" << endl;
|
|
}
|
|
|
|
// Start MIDI timer and open MIDI input
|
|
Pt_Start(1, ptCallback, &state);
|
|
PmError pmError = Pm_OpenInput(
|
|
&state.pmStream,
|
|
pmDeviceIndex,
|
|
NULL,
|
|
8,
|
|
NULL,
|
|
NULL);
|
|
if(pmErr != pmNoError) {
|
|
handlePMError(pmErr);
|
|
}
|
|
|
|
// Configure/start audio output stream
|
|
PaStream *paStream;
|
|
PaStreamParameters paStreamParams;
|
|
paStreamParams.device = paDeviceIndex;
|
|
paStreamParams.channelCount = 2;
|
|
paStreamParams.sampleFormat = paFloat32;
|
|
paStreamParams.suggestedLatency = 0;
|
|
paStreamParams.hostApiSpecificStreamInfo = NULL;
|
|
paErr = Pa_OpenStream(
|
|
&paStream,
|
|
NULL, /* no input channels */
|
|
&paStreamParams, /* stereo output */
|
|
SAMPLE_RATE,
|
|
0, /* frames per buffer */
|
|
paNoFlag,
|
|
paCallback,
|
|
&state);
|
|
if(paErr != paNoError) {
|
|
handlePAError(paErr);
|
|
}
|
|
paErr = Pa_StartStream(paStream);
|
|
if(paErr != paNoError) {
|
|
handlePAError(paErr);
|
|
}
|
|
|
|
const PaStreamInfo *paStreamInfo = Pa_GetStreamInfo(paStream);
|
|
cout << "Latency: " << round(1000 * paStreamInfo->outputLatency) << " ms" << endl;
|
|
|
|
// Wait for Return key
|
|
cout << "Running - press Enter to exit" << endl;
|
|
getchar();
|
|
|
|
paErr = Pa_StopStream(paStream);
|
|
if(paErr != paNoError) {
|
|
handlePAError(paErr);
|
|
}
|
|
paErr = Pa_CloseStream(paStream);
|
|
if(paErr != paNoError) {
|
|
handlePAError(paErr);
|
|
}
|
|
pmErr = Pm_Close(state.pmStream);
|
|
if(pmErr != pmNoError) {
|
|
handlePMError(pmErr);
|
|
}
|
|
Pa_Terminate();
|
|
cout << "Terminating" << endl;
|
|
|
|
return 0;
|
|
} |