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.8 KiB
240 lines
6.8 KiB
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include <cmath>
|
|
#include <mutex>
|
|
|
|
#include "globals.h"
|
|
#include "util.h"
|
|
|
|
#include "synthapp.h"
|
|
#include "synthframe.h"
|
|
|
|
using namespace std;
|
|
|
|
uint64_t synthNanos = 0;
|
|
uint64_t synthSamples = 0;
|
|
|
|
// PortAudio callback for audio I/O
|
|
bool firstPACallback = true;
|
|
static int paCallback(
|
|
const void *inputBuffer,
|
|
void *outputBuffer,
|
|
unsigned long framesPerBuffer,
|
|
const PaStreamCallbackTimeInfo* timeInfo,
|
|
PaStreamCallbackFlags statusFlags,
|
|
void *userData ) {
|
|
|
|
SynthApp *app = (SynthApp*) userData;
|
|
|
|
if(firstPACallback) {
|
|
printf("Buffer size is %ld frames\n", framesPerBuffer);
|
|
firstPACallback = false;
|
|
}
|
|
|
|
app->synthMutex.lock();
|
|
PmEvent events[16];
|
|
int numEvents = Pm_Read(app->pmStream, events, 16);
|
|
for(int i = 0; i < numEvents; ++i) {
|
|
PmMessage msg = events[i].message;
|
|
|
|
int type = (msg >> 4) & 0xF;
|
|
//int ch = msg & 0xF;
|
|
int ch = 0;
|
|
|
|
if(type == 0x8 || (type == 0x9 && Pm_MessageData2(msg) == 0)) { // Note Off
|
|
int note = Pm_MessageData1(msg);
|
|
app->synth.noteOff(ch, note);
|
|
} else if(type == 0x9) { // Note On
|
|
int note = Pm_MessageData1(msg);
|
|
int vel = Pm_MessageData2(msg);
|
|
app->synth.noteOn(ch, note, vel);
|
|
} else if(type == 0xB) { // Controller Message
|
|
int cc = Pm_MessageData1(msg);
|
|
int val = Pm_MessageData2(msg);
|
|
app->synth.control(ch, cc, val);
|
|
}
|
|
}
|
|
|
|
float *out = (float*) outputBuffer;
|
|
|
|
uint64_t started = nanos();
|
|
app->synth.tick(out, framesPerBuffer * 2);
|
|
uint64_t finished = nanos();
|
|
app->synthMutex.unlock();
|
|
synthNanos += finished - started;
|
|
synthSamples += framesPerBuffer;
|
|
|
|
if(synthSamples > 5 * SAMPLE_RATE) {
|
|
printf("%llu ns\n", synthNanos / synthSamples);
|
|
synthNanos = 0;
|
|
synthSamples = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
bool SynthApp::OnInit() {
|
|
// 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();
|
|
putchar('\n');
|
|
printf("Select MIDI input device:\n\n");
|
|
for(int i = 0; i < pmDeviceCount; ++i) {
|
|
const PmDeviceInfo* deviceInfo = Pm_GetDeviceInfo(i);
|
|
if(!deviceInfo->input) {
|
|
continue;
|
|
}
|
|
printf("%d. %s\n", i, deviceInfo->name);
|
|
}
|
|
putchar('\n');
|
|
int pmDeviceIndex;
|
|
for(;;) { // loop for input until valid choice is made
|
|
printf("Number: ");
|
|
char input[4];
|
|
fgets(input, 4, stdin);
|
|
//const char* input = "1";
|
|
try {
|
|
pmDeviceIndex = stoi(input);
|
|
if( pmDeviceIndex >= 0 &&
|
|
pmDeviceIndex < pmDeviceCount &&
|
|
Pm_GetDeviceInfo(pmDeviceIndex)->input) {
|
|
break;
|
|
}
|
|
} catch(...) { } // stoi() throws an exeption for invalid integers
|
|
printf("Invalid selection\n");
|
|
}
|
|
|
|
putchar('\n');
|
|
|
|
PaHostApiIndex paDeviceCount = Pa_GetDeviceCount();
|
|
printf("Select audio output device:\n\n");
|
|
for(int i = 0; i < paDeviceCount; ++i) {
|
|
const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(i);
|
|
if(deviceInfo->maxOutputChannels < 2) {
|
|
continue;
|
|
}
|
|
printf("%d. %s (%s)\n", i, deviceInfo->name, Pa_GetHostApiInfo(deviceInfo->hostApi)->name);
|
|
}
|
|
putchar('\n');
|
|
PaHostApiIndex paDeviceIndex;
|
|
for(;;) { // loop for input until valid choice is made
|
|
printf("Number: ");
|
|
char input[4];
|
|
fgets(input, 4, stdin);
|
|
//const char* input = "5";
|
|
try {
|
|
paDeviceIndex = stoi(input);
|
|
if( paDeviceIndex >= 0 &&
|
|
paDeviceIndex < paDeviceCount &&
|
|
Pa_GetDeviceInfo(paDeviceIndex)->maxOutputChannels >= 2) {
|
|
break;
|
|
}
|
|
} catch(...) { } // stoi() throws an exeption for invalid integers
|
|
printf("Invalid selection\n");
|
|
}
|
|
|
|
// Open MIDI input
|
|
pmErr = Pm_OpenInput(
|
|
&pmStream,
|
|
pmDeviceIndex,
|
|
NULL,
|
|
8,
|
|
NULL,
|
|
NULL);
|
|
if(pmErr != pmNoError) {
|
|
handlePMError(pmErr);
|
|
}
|
|
|
|
const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(paDeviceIndex);
|
|
|
|
putchar('\n');
|
|
printf("Default sample rate is %.0f Hz\n", deviceInfo->defaultSampleRate);
|
|
printf("Default low input latency is %.0f samples\n", deviceInfo->defaultSampleRate * deviceInfo->defaultLowOutputLatency);
|
|
putchar('\n');
|
|
|
|
// Configure/start audio output stream
|
|
PaStreamParameters paStreamParams;
|
|
paStreamParams.device = paDeviceIndex;
|
|
paStreamParams.channelCount = 2;
|
|
paStreamParams.sampleFormat = paFloat32;
|
|
paStreamParams.suggestedLatency = deviceInfo->defaultLowOutputLatency;
|
|
paStreamParams.hostApiSpecificStreamInfo = NULL;
|
|
paErr = Pa_OpenStream(
|
|
&paStream,
|
|
NULL, // no input channels
|
|
&paStreamParams, // stereo output
|
|
deviceInfo->defaultSampleRate,
|
|
0, // frames per buffer
|
|
paNoFlag,
|
|
paCallback,
|
|
this);
|
|
if(paErr != paNoError) {
|
|
handlePAError(paErr);
|
|
}
|
|
paErr = Pa_StartStream(paStream);
|
|
if(paErr != paNoError) {
|
|
handlePAError(paErr);
|
|
}
|
|
|
|
const PaStreamInfo *paStreamInfo = Pa_GetStreamInfo(paStream);
|
|
printf("Reported latency is %d frames\n", (int) round(SAMPLE_RATE * paStreamInfo->outputLatency));
|
|
|
|
wxFrame *frame = new SynthFrame();
|
|
frame->Show(true);
|
|
return true;
|
|
}
|
|
|
|
int SynthApp::OnExit() {
|
|
PaError paErr = Pa_StopStream(paStream);
|
|
if(paErr != paNoError) {
|
|
handlePAError(paErr);
|
|
}
|
|
paErr = Pa_CloseStream(paStream);
|
|
if(paErr != paNoError) {
|
|
handlePAError(paErr);
|
|
}
|
|
PmError pmErr = Pm_Close(pmStream);
|
|
if(pmErr != pmNoError) {
|
|
handlePMError(pmErr);
|
|
}
|
|
Pa_Terminate();
|
|
printf("\nTerminating\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
wxIMPLEMENT_APP(SynthApp);
|
|
|
|
#ifdef WIN32
|
|
int main(int argc, char** argv) {
|
|
HMODULE module = GetModuleHandle(NULL);
|
|
LPSTR lpCmdLine = (LPSTR) calloc(1, 1);
|
|
return WinMain(module, NULL, lpCmdLine, SW_SHOW);
|
|
}
|
|
#endif |