diff --git a/Makefile b/Makefile index 0f60342..f6c0963 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CPPFLAGS=-std=c++11 -Ofast -LDLIBS=-lstdc++ -lportaudio +LDLIBS=-lstdc++ -lportaudio -lportmidi all: main diff --git a/main.cpp b/main.cpp index 7113e73..d6cd617 100644 --- a/main.cpp +++ b/main.cpp @@ -3,6 +3,8 @@ #include #include "portaudio.h" +#include "portmidi.h" +#include "porttime.h" #include "oscillator.h" #include "svf.h" @@ -10,14 +12,20 @@ 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[8]; + Oscillator oscs[NUM_OSCS]; SVFx2 filter; float time; + PmStream* pmStream; } State; -static int audioCallback( +static int paCallback( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, @@ -30,12 +38,12 @@ static int audioCallback( for(int i = 0; i < framesPerBuffer; i++) { float value = 0; - for(int j = 0; j < 8; ++j) { + for(int j = 0; j < NUM_OSCS; ++j) { value += state->oscs[j].tick(); } - value *= 0.125; + value /= NUM_OSCS; value *= 0.5; - state->filter.setFrequency(0.21 + 0.20 * sin(M_PI * state->time)); + state->filter.setFrequency(0.21 + 0.20 * cos(M_PI * state->time)); value = state->filter.tick(value); *out++ = value; *out++ = value; @@ -45,17 +53,61 @@ static int audioCallback( return 0; } +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); } -static State state; int main(void) { - for(int i = 0; i < 8; ++i) { + 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; @@ -63,78 +115,121 @@ int main(void) { 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); + PmError pmErr = Pm_Initialize(); + if(pmErr != pmNoError) { + handlePMError(pmErr); + } + PaError paErr = Pa_Initialize(); + if(paErr != paNoError) { + handlePAError(paErr); } - PaHostApiIndex deviceCount = Pa_GetDeviceCount(); + 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(;;) { + string input; + getline(cin, input); + try { + pmDeviceIndex = stoi(input); + if(pmDeviceIndex >= 0 && pmDeviceIndex < pmDeviceCount) { + break; + } + } catch(...) { } + cout << "Invalid selection" << endl; + } + + PaHostApiIndex paDeviceCount = Pa_GetDeviceCount(); cout << "Select audio output device:" << endl; - for(int i = 0; i < deviceCount; ++i) { + 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; } - string input; - PaHostApiIndex deviceIndex; + PaHostApiIndex paDeviceIndex; for(;;) { + string input; getline(cin, input); try { - deviceIndex = stoi(input); - if(deviceIndex >= 0 && deviceIndex < deviceCount) { + paDeviceIndex = stoi(input); + if(paDeviceIndex >= 0 && paDeviceIndex < paDeviceCount) { break; } } catch(...) { } cout << "Invalid selection" << endl; } + Pt_Start(1, ptCallback, &state); + + PmError pmError = Pm_OpenInput( + &state.pmStream, + pmDeviceIndex, + NULL, + 256, + NULL, + NULL); + if(pmErr != pmNoError) { + handlePMError(pmErr); + } + /* 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, + 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 */ - &streamParams, /* stereo output */ + &paStreamParams, /* stereo output */ SAMPLE_RATE, - 0, /* frames per buffer */ + 0, /* frames per buffer */ paNoFlag, - audioCallback, + paCallback, &state); - if(err != paNoError) { - handlePAError(err); + if(paErr != paNoError) { + handlePAError(paErr); } - err = Pa_StartStream(stream); - if(err != paNoError) { - handlePAError(err); + paErr = Pa_StartStream(paStream); + if(paErr != paNoError) { + handlePAError(paErr); } - const PaStreamInfo *streamInfo = Pa_GetStreamInfo(stream); - cout << "Latency: " << round(1000 * streamInfo->outputLatency) << " ms" << endl; + 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(); - err = Pa_StopStream(stream); - if(err != paNoError) { - handlePAError(err); + paErr = Pa_StopStream(paStream); + if(paErr != paNoError) { + handlePAError(paErr); } - err = Pa_CloseStream(stream); - if(err != paNoError) { - handlePAError(err); + 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 err; + return 0; } \ No newline at end of file