From b99e707680c5bb3a8d31b1edd31a8e4a26e36096 Mon Sep 17 00:00:00 2001 From: Chris Cannam Date: Thu, 21 Oct 2004 12:17:11 +0000 Subject: [PATCH] * Renamed vstsynth to vsthost and made a few fixes to it (e.g. dejittering) --- Makefile.in | 6 +- dssi-vst-server.cpp | 160 +++++++++++++++++++++--------------- dssi-vst.cpp | 18 ++-- remotevstclient.cpp | 4 +- remotevstclient.h | 2 +- vstsynth.cpp => vsthost.cpp | 133 +++++++++++++++++++----------- 6 files changed, 192 insertions(+), 131 deletions(-) rename vstsynth.cpp => vsthost.cpp (77%) diff --git a/Makefile.in b/Makefile.in index 11a6be9..ac2e313 100644 --- a/Makefile.in +++ b/Makefile.in @@ -121,7 +121,7 @@ SPEC_SRCS = $(dssi-vst-server_exe_SPEC_SRCS) $(dssi-vst-scanner_exe_ ### Generic autoconf targets -all: apploader $(SUBDIRS) $(DLLS:%=%.so) $(EXES:%=%.so) vstsynth libremoteplugin.a dssi-vst.so +all: apploader $(SUBDIRS) $(DLLS:%=%.so) $(EXES:%=%.so) vsthost libremoteplugin.a dssi-vst.so apploader: apploader.in sed -e 's,@bindir\@,$(bindir),g' -e 's,@winelibdir\@,.,g' $(SRCDIR)/apploader.in >$@ || $(RM) $@ @@ -182,8 +182,8 @@ $(dssi-vst-scanner_exe_MODULE).so: $(dssi-vst-scanner_exe_MODULE).dbg.o $(dssi-v ### Client application -vstsynth: vstsynth.cpp remoteplugin.h remotepluginclient.h remotevstclient.o libremoteplugin.a - g++ -g3 -I/usr/local/include -L/usr/local/lib -Wall vstsynth.cpp remotevstclient.o -o vstsynth -L. -lremoteplugin -ljack -lasound +vsthost: vsthost.cpp remoteplugin.h remotepluginclient.h remotevstclient.o libremoteplugin.a + g++ -g3 -I/usr/local/include -L/usr/local/lib -Wall vsthost.cpp remotevstclient.o -o vsthost -L. -lremoteplugin -ljack -lasound libremoteplugin.a: remotepluginclient.o remotepluginserver.o rdwrops.o paths.o ar r $@ $^ diff --git a/dssi-vst-server.cpp b/dssi-vst-server.cpp index a707dde..58d419f 100644 --- a/dssi-vst-server.cpp +++ b/dssi-vst-server.cpp @@ -15,6 +15,8 @@ #include #include +#include + #define WIN32_LEAN_AND_MEAN #include @@ -43,7 +45,7 @@ static bool ready = false; static int bufferSize = 0; static int sampleRate = 0; -static RemotePluginDebugLevel debugLevel = RemotePluginDebugEvents; +static RemotePluginDebugLevel debugLevel = RemotePluginDebugSetup; using namespace std; @@ -466,6 +468,17 @@ hostCallback(AEffect *plugin, long opcode, long index, DWORD WINAPI AudioThreadMain(LPVOID parameter) { + int policy = SCHED_FIFO; + struct sched_param param; + + param.sched_priority = 1; + + int result = sched_setscheduler(0, policy, ¶m); + + if (result < 0) { + perror("Failed to set realtime priority for audio thread"); + } + while (1) { try { remoteVSTServerInstance->dispatch(); @@ -499,6 +512,7 @@ WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdline, int cmdshow) { char *libname = 0; char *fileInfo = 0; + bool tryGui = false, haveGui = true; cout << "DSSI VST plugin server v" << RemotePluginVersion << endl; cout << "Copyright (c) 2004 Chris Cannam - Fervent Software" << endl; @@ -508,6 +522,10 @@ WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdline, int cmdshow) if (cmdline) { int offset = 0; if (cmdline[0] == '"' || cmdline[0] == '\'') offset = 1; + if (!strncmp(&cmdline[offset], "-g ", 3)) { + tryGui = true; + offset += 3; + } for (int ci = offset; cmdline[ci]; ++ci) { if (cmdline[ci] == ',') { libname = strndup(cmdline + offset, ci - offset); @@ -627,14 +645,16 @@ WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdline, int cmdshow) cerr << "dssi-vst-server[1]: plugin is a VST" << endl; } -#ifdef SHOW_GUI - if (!(plugin->flags & effFlagsHasEditor)) { - cerr << "dssi-vst-server: ERROR: Plugin has no GUI (required)" << endl; - return 1; - } else if (debugLevel > 0) { - cerr << "dssi-vst-server[1]: plugin has a GUI" << endl; + if (tryGui) { + if (!(plugin->flags & effFlagsHasEditor)) { + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: Plugin has no GUI" << endl; + } + haveGui = false; + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: plugin has a GUI" << endl; + } } -#endif if (!plugin->flags & effFlagsCanReplacing) { cerr << "dssi-vst-server: ERROR: Plugin does not support processReplacing (required)" @@ -655,72 +675,76 @@ WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdline, int cmdshow) return 1; } -// This needs to be an argument: -#ifdef SHOW_GUI - - cout << "Initialising Windows subsystem... "; - if (debugLevel > 0) cout << endl; - - WNDCLASSEX wclass; - wclass.cbSize = sizeof(WNDCLASSEX); - wclass.style = 0; - wclass.lpfnWndProc = MainProc; - wclass.cbClsExtra = 0; - wclass.cbWndExtra = 0; - wclass.hInstance = hInst; - wclass.hIcon = LoadIcon(hInst, APPLICATION_CLASS_NAME); - wclass.hCursor = LoadCursor(0, IDI_APPLICATION); - wclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); - wclass.lpszMenuName = "MENU_DSSI_VST"; - wclass.lpszClassName = APPLICATION_CLASS_NAME; - wclass.hIconSm = 0; - - if (!RegisterClassEx(&wclass)) { - cerr << "dssi-vst-server: ERROR: Failed to register Windows application class!\n" << endl; - return 1; - } else if (debugLevel > 0) { - cerr << "dssi-vst-server[1]: registered Windows application class \"" << APPLICATION_CLASS_NAME << "\"" << endl; - } - - hWnd = CreateWindow - (APPLICATION_CLASS_NAME, remoteVSTServerInstance->getName().c_str(), - WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - 0, 0, hInst, 0); - if (!hWnd) { - cerr << "dssi-vst-server: ERROR: Failed to create window!\n" << endl; - return 1; - } else if (debugLevel > 0) { - cerr << "dssi-vst-server[1]: created main window" << endl; - } + if (tryGui) { + + cout << "Initialising Windows subsystem... "; + if (debugLevel > 0) cout << endl; + + WNDCLASSEX wclass; + wclass.cbSize = sizeof(WNDCLASSEX); + wclass.style = 0; + wclass.lpfnWndProc = MainProc; + wclass.cbClsExtra = 0; + wclass.cbWndExtra = 0; + wclass.hInstance = hInst; + wclass.hIcon = LoadIcon(hInst, APPLICATION_CLASS_NAME); + wclass.hCursor = LoadCursor(0, IDI_APPLICATION); + wclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); + wclass.lpszMenuName = "MENU_DSSI_VST"; + wclass.lpszClassName = APPLICATION_CLASS_NAME; + wclass.hIconSm = 0; + + if (!RegisterClassEx(&wclass)) { + cerr << "dssi-vst-server: ERROR: Failed to register Windows application class!\n" << endl; + return 1; + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: registered Windows application class \"" << APPLICATION_CLASS_NAME << "\"" << endl; + } + + hWnd = CreateWindow + (APPLICATION_CLASS_NAME, remoteVSTServerInstance->getName().c_str(), + WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + 0, 0, hInst, 0); + if (!hWnd) { + cerr << "dssi-vst-server: ERROR: Failed to create window!\n" << endl; + return 1; + } else if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: created main window" << endl; + } - plugin->dispatcher(plugin, effEditOpen, 0, 0, hWnd, 0); - Rect *rect = 0; - plugin->dispatcher(plugin, effEditGetRect, 0, 0, &rect, 0); - if (!rect) { - cerr << "dssi-vst-server: ERROR: Plugin failed to report window size\n" << endl; - return 1; - } + if (!haveGui) { + cerr << "Should be showing message here" << endl; + } else { - // Seems we need to provide space in here for the titlebar and frame, - // even though we don't know how big they'll be! How crap. - SetWindowPos(hWnd, 0, 0, 0, - rect->right - rect->left + 6, - rect->bottom - rect->top + 25, - SWP_NOACTIVATE | SWP_NOMOVE | - SWP_NOOWNERZORDER | SWP_NOZORDER); + plugin->dispatcher(plugin, effEditOpen, 0, 0, hWnd, 0); + Rect *rect = 0; + plugin->dispatcher(plugin, effEditGetRect, 0, 0, &rect, 0); + if (!rect) { + cerr << "dssi-vst-server: ERROR: Plugin failed to report window size\n" << endl; + return 1; + } - if (debugLevel > 0) { - cerr << "dssi-vst-server[1]: sized window" << endl; - } + // Seems we need to provide space in here for the titlebar and frame, + // even though we don't know how big they'll be! How crap. + SetWindowPos(hWnd, 0, 0, 0, + rect->right - rect->left + 6, + rect->bottom - rect->top + 25, + SWP_NOACTIVATE | SWP_NOMOVE | + SWP_NOOWNERZORDER | SWP_NOZORDER); + + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: sized window" << endl; + } + } - ShowWindow(hWnd, SW_SHOWNORMAL); - UpdateWindow(hWnd); + ShowWindow(hWnd, SW_SHOWNORMAL); + UpdateWindow(hWnd); - if (debugLevel > 0) { - cerr << "dssi-vst-server[1]: showed window" << endl; + if (debugLevel > 0) { + cerr << "dssi-vst-server[1]: showed window" << endl; + } } -#endif cout << "done" << endl; diff --git a/dssi-vst.cpp b/dssi-vst.cpp index 6b8e728..a2858d8 100644 --- a/dssi-vst.cpp +++ b/dssi-vst.cpp @@ -242,33 +242,33 @@ DSSIVSTPluginInstance::deactivate() void DSSIVSTPluginInstance::connectPort(unsigned long port, LADSPA_Data *location) { - std::cerr << "connectPort(" << port << "," << location << ")" << std::endl; +// std::cerr << "connectPort(" << port << "," << location << ")" << std::endl; if (!m_ok) return; if (port < m_controlPortCount) { - std::cerr << "(control port)" << std::endl; +// std::cerr << "(control port)" << std::endl; m_controlPorts[port] = location; return; } port -= m_controlPortCount; if (port < m_audioInCount) { - std::cerr << "(audio in port)" << std::endl; +// std::cerr << "(audio in port)" << std::endl; m_audioIns[port] = location; return; } port -= m_audioInCount; if (port < m_audioOutCount) { - std::cerr << "(audio out port)" << std::endl; +// std::cerr << "(audio out port)" << std::endl; m_audioOuts[port] = location; return; } port -= m_audioOutCount; if (port < 1) { // latency - std::cerr << "(latency output port)" << std::endl; +// std::cerr << "(latency output port)" << std::endl; m_latencyOut = location; if (m_latencyOut) *m_latencyOut = 0; return; @@ -434,7 +434,7 @@ DSSIVSTPlugin::DSSIVSTPlugin() ldesc->Maker = strdup(rec.vendorName.c_str()); ldesc->Copyright = strdup(ldesc->Maker); - std::cerr << "Plugin name: " << ldesc->Name << std::endl; +// std::cerr << "Plugin name: " << ldesc->Name << std::endl; int parameters = rec.parameters; int inputs = rec.inputs; @@ -537,9 +537,9 @@ DSSI_Descriptor * DSSIVSTPlugin::queryDescriptor(unsigned long index) { if (index < m_descriptors.size()) { - std::cerr << "DSSIVSTPlugin::queryDescriptor: index is " << index - << ", returning " << m_descriptors[index].second->LADSPA_Plugin->Name - << std::endl; +// std::cerr << "DSSIVSTPlugin::queryDescriptor: index is " << index +// << ", returning " << m_descriptors[index].second->LADSPA_Plugin->Name +// << std::endl; return m_descriptors[index].second; } else { return 0; diff --git a/remotevstclient.cpp b/remotevstclient.cpp index afb5858..d60e6cf 100644 --- a/remotevstclient.cpp +++ b/remotevstclient.cpp @@ -18,11 +18,13 @@ #include "rdwrops.h" #include "paths.h" -RemoteVSTClient::RemoteVSTClient(std::string dllName) : +RemoteVSTClient::RemoteVSTClient(std::string dllName, bool showGUI) : RemotePluginClient() { pid_t child; std::string arg = dllName + "," + getFileIdentifiers(); + if (showGUI) arg = "-g " + arg; + const char *argStr = arg.c_str(); // We want to run the dssi-vst-server script, which runs wine diff --git a/remotevstclient.h b/remotevstclient.h index c348ec1..02ac0c1 100644 --- a/remotevstclient.h +++ b/remotevstclient.h @@ -14,7 +14,7 @@ class RemoteVSTClient : public RemotePluginClient { public: // may throw a string exception - RemoteVSTClient(std::string dllName); + RemoteVSTClient(std::string dllName, bool showGUI = false); virtual ~RemoteVSTClient(); diff --git a/vstsynth.cpp b/vsthost.cpp similarity index 77% rename from vstsynth.cpp rename to vsthost.cpp index 0118df4..84e595a 100644 --- a/vstsynth.cpp +++ b/vsthost.cpp @@ -22,10 +22,14 @@ static RemotePluginClient *plugin = 0; +#define MIDI_DECODED_SIZE 3 #define MIDI_BUFFER_SIZE 3072 -static unsigned char alsaDecoderBuffer[MIDI_BUFFER_SIZE]; -static size_t alsaDecoderIndex = 0; +static unsigned char midiStreamBuffer[(MIDI_BUFFER_SIZE + 1) * MIDI_DECODED_SIZE]; +static struct timeval midiTimeBuffer[MIDI_BUFFER_SIZE]; +static int midiFrameOffsets[MIDI_BUFFER_SIZE]; +static int midiReadIndex = 0, midiWriteIndex = 0; + static snd_midi_event_t *alsaDecoder = 0; static pthread_mutex_t pluginMutex = PTHREAD_MUTEX_INITIALIZER; @@ -59,9 +63,9 @@ bail(int sig) // -- this means we don't get a mutex lock in closeJack either if (sig != 0) { - fprintf(stderr, "vstsynth: signal %d received, exiting\n", sig); + fprintf(stderr, "vsthost: signal %d received, exiting\n", sig); } else { - fprintf(stderr, "vstsynth: bailing out\n"); + fprintf(stderr, "vsthost: bailing out\n"); } if (jackData.client) { @@ -123,13 +127,14 @@ alsaSeqCallback(snd_seq_t *alsaSeqHandle) if (!ready) return; - if (alsaDecoderIndex > MIDI_BUFFER_SIZE - 10) return; - do { if (snd_seq_event_input(alsaSeqHandle, &ev) > 0) { - pthread_mutex_lock(&pluginMutex); + if (midiReadIndex == midiWriteIndex + 1) { + fprintf(stderr, "WARNING: MIDI stream buffer overflow\n"); + continue; + } if (ev->type == SND_SEQ_EVENT_NOTEON) { @@ -151,17 +156,20 @@ alsaSeqCallback(snd_seq_t *alsaSeqHandle) long count = snd_midi_event_decode (alsaDecoder, - alsaDecoderBuffer + alsaDecoderIndex, - MIDI_BUFFER_SIZE - alsaDecoderIndex, + midiStreamBuffer + (midiWriteIndex * MIDI_DECODED_SIZE), + (MIDI_BUFFER_SIZE - midiWriteIndex) * MIDI_DECODED_SIZE, ev); if (count > 0 && count <= 3) { - alsaDecoderIndex += count; while (count < 3) { - alsaDecoderBuffer[alsaDecoderIndex++] = '\0'; + midiStreamBuffer[midiWriteIndex * MIDI_DECODED_SIZE + count] + = '\0'; ++count; } + + gettimeofday(midiTimeBuffer + midiWriteIndex, NULL); + midiWriteIndex = (midiWriteIndex + 1) % MIDI_BUFFER_SIZE; } else if (count > 3) { fprintf(stderr, "WARNING: MIDI event of type %d" @@ -173,23 +181,21 @@ alsaSeqCallback(snd_seq_t *alsaSeqHandle) fprintf(stderr, "WARNING: MIDI decoder error %ld" " for event type %d\n", count, ev->type); } - - pthread_mutex_unlock(&pluginMutex); } } while (snd_seq_event_input_pending(alsaSeqHandle, 0) > 0); } int -openAlsaSeq(const char *synthName) +openAlsaSeq(const char *pluginName) { int portid; char alsaName[75]; - if (synthName[0]) { - sprintf(alsaName, "%s VSTi", synthName); + if (pluginName[0]) { + sprintf(alsaName, "%s VST", pluginName); } else { - sprintf(alsaName, "VST Synth"); + sprintf(alsaName, "VST Host"); } alsaSeqHandle = 0; @@ -222,18 +228,17 @@ openAlsaSeq(const char *synthName) int jackProcess(jack_nframes_t nframes, void *arg) { + int ri = midiReadIndex, wi = midiWriteIndex; if (nframes != jackData.buffer_size) { - //!!! erk -- this is apparently legal, though it will never - // happen with current JACK versions. nframes can be anywhere - // in the range 0 -> buffersize + // This is apparently legal, though it will never happen with + // current JACK versions. In theory nframes can be anywhere + // in the range 0 -> buffersize. Let's not handle that yet. fprintf(stderr, "ERROR: Internal JACK error: process() called with incorrect buffer size (was %d, should be %d)\n", nframes, jackData.buffer_size); return 0; } if (sizeof(float) != sizeof(jack_default_audio_sample_t)) { - // require this for easy interaction with the synth - //!!! eliminate this dependency fprintf(stderr, "ERROR: The JACK audio sample type is not \"float\"; can't proceed\n"); bail(0); } @@ -247,20 +252,57 @@ jackProcess(jack_nframes_t nframes, void *arg) (jackData.output_ports[i], jackData.buffer_size); } - if (!ready) { + if (!ready || pthread_mutex_trylock(&pluginMutex)) { for (int i = 0; i < jackData.output_count; ++i) { memset(jackData.output_buffers[i], 0, jackData.buffer_size * sizeof(float)); } return 0; } - pthread_mutex_lock(&pluginMutex); + struct timeval tv, diff; + long framediff; + jack_nframes_t rate; + + gettimeofday(&tv, NULL); + rate = jack_get_sample_rate(jackData.client); + + while (ri != wi) { + + diff.tv_sec = tv.tv_sec - midiTimeBuffer[ri].tv_sec; + + if (tv.tv_usec < midiTimeBuffer[ri].tv_usec) { + --diff.tv_sec; + diff.tv_usec = tv.tv_usec + 1000000 - midiTimeBuffer[ri].tv_usec; + } else { + diff.tv_usec = tv.tv_usec - midiTimeBuffer[ri].tv_usec; + } - if (alsaDecoderIndex > 0) { - plugin->sendMIDIData(alsaDecoderBuffer, 0, alsaDecoderIndex); - alsaDecoderIndex = 0; + framediff = + diff.tv_sec * rate + + ((diff.tv_usec / 1000) * rate) / 1000 + + ((diff.tv_usec - 1000 * (diff.tv_usec / 1000)) * rate) / 1000000; + + if (framediff >= (long)nframes) framediff = nframes - 1; + else if (framediff < 0) framediff = 0; + + midiFrameOffsets[ri] = (int)(nframes - framediff - 1); + ri = (ri + 1) % MIDI_BUFFER_SIZE; } - + + if (midiReadIndex > wi) { + plugin->sendMIDIData(midiStreamBuffer + midiReadIndex * MIDI_DECODED_SIZE, + midiFrameOffsets + midiReadIndex, + MIDI_BUFFER_SIZE - midiReadIndex); + midiReadIndex = 0; + } + + if (midiReadIndex < wi) { + plugin->sendMIDIData(midiStreamBuffer + midiReadIndex * MIDI_DECODED_SIZE, + midiFrameOffsets + midiReadIndex, + wi - midiReadIndex); + midiReadIndex = wi; + } + plugin->process(jackData.input_buffers, jackData.output_buffers); pthread_mutex_unlock(&pluginMutex); @@ -276,16 +318,16 @@ shutdownJack(void *arg) } int -openJack(const char *synthName) +openJack(const char *pluginName) { const char **ports = 0; char jackName[26]; char tmpbuf[21]; int i = 0, j = 0; - for (i = 0; i < 20 && synthName[i]; ++i) { - if (isalpha(synthName[i])) { - tmpbuf[j] = tolower(synthName[i]); + for (i = 0; i < 20 && pluginName[i]; ++i) { + if (isalpha(pluginName[i])) { + tmpbuf[j] = tolower(pluginName[i]); ++j; } } @@ -396,7 +438,7 @@ closeJack() void usage() { - fprintf(stderr, "Usage: vstsynth [-d] (n may be 0, 1, 2 or 3 for debug level)\n"); + fprintf(stderr, "Usage: vsthost [-n] [-d] \n -n No GUI\n -d Debug level (0, 1, 2 or 3)\n"); exit(2); } @@ -405,15 +447,18 @@ main(int argc, char **argv) { char *dllname = 0; int debugLevel = 0; + bool gui = true; int npfd; struct pollfd *pfd; while (1) { - int c = getopt(argc, argv, "d:"); + int c = getopt(argc, argv, "nd:"); if (c == -1) break; - else if (c == 'd') { + else if (c == 'n') { + gui = false; + } else if (c == 'd') { int dl = atoi(optarg); if (dl >= 0 && dl < 4) debugLevel = dl; else { @@ -443,23 +488,13 @@ main(int argc, char **argv) jackData.client = 0; try { - plugin = new RemoteVSTClient(dllname); + plugin = new RemoteVSTClient(dllname, gui); } catch (std::string e) { perror(e.c_str()); bail(0); } - std::string synthName = plugin->getName(); - - printf("Parameters:\n"); - for (int i = 0; i < plugin->getParameterCount(); ++i) { - printf(" %d. %s\n", i, plugin->getParameterName(i).c_str()); - } - - printf("Programs:\n"); - for (int i = 0; i < plugin->getProgramCount(); ++i) { - printf(" %d. %s\n", i, plugin->getProgramName(i).c_str()); - } + std::string pluginName = plugin->getName(); // prevent child threads from wanting to handle signals sigset_t _signals; @@ -476,12 +511,12 @@ main(int argc, char **argv) bool hasMIDI = plugin->hasMIDIInput(); if (hasMIDI) { - if (openAlsaSeq(synthName.c_str())) { + if (openAlsaSeq(pluginName.c_str())) { plugin->warn("Failed to connect to ALSA sequencer MIDI interface"); bail(0); } } - if (openJack(synthName.c_str())) { + if (openJack(pluginName.c_str())) { plugin->warn("Failed to connect to JACK audio server (jackd not running?)"); bail(0); }