Mercurial > hg > pub > prymula > com
view DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoPluginJACK.cpp @ 3:84e66ea83026
DPF-Prymula-audioplugins-0.231015-2
author | prymula <prymula76@outlook.com> |
---|---|
date | Mon, 16 Oct 2023 21:53:34 +0200 |
parents | |
children |
line wrap: on
line source
/* * DISTRHO Plugin Framework (DPF) * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * For a full copy of the license see the LGPL.txt file */ #include "DistrhoPluginInternal.hpp" #ifndef STATIC_BUILD # include "../DistrhoPluginUtils.hpp" #endif #if DISTRHO_PLUGIN_HAS_UI # include "DistrhoUIInternal.hpp" # include "../extra/RingBuffer.hpp" #else # include "../extra/Sleep.hpp" #endif #ifdef DPF_RUNTIME_TESTING # include "../extra/Thread.hpp" #endif #if defined(HAVE_JACK) && defined(STATIC_BUILD) && !defined(DISTRHO_OS_WASM) # define JACKBRIDGE_DIRECT #endif #include "jackbridge/JackBridge.cpp" #include "lv2/lv2.h" #ifdef DISTRHO_OS_MAC # define Point CocoaPoint # include <CoreFoundation/CoreFoundation.h> # undef Point #endif #ifndef DISTRHO_OS_WINDOWS # include <signal.h> # include <unistd.h> #endif #ifdef __SSE2_MATH__ # include <xmmintrin.h> #endif #ifndef JACK_METADATA_ORDER # define JACK_METADATA_ORDER "http://jackaudio.org/metadata/order" #endif #ifndef JACK_METADATA_PRETTY_NAME # define JACK_METADATA_PRETTY_NAME "http://jackaudio.org/metadata/pretty-name" #endif #ifndef JACK_METADATA_PORT_GROUP # define JACK_METADATA_PORT_GROUP "http://jackaudio.org/metadata/port-group" #endif #ifndef JACK_METADATA_SIGNAL_TYPE # define JACK_METADATA_SIGNAL_TYPE "http://jackaudio.org/metadata/signal-type" #endif // ----------------------------------------------------------------------- START_NAMESPACE_DISTRHO #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_MIDI_INPUT static const sendNoteFunc sendNoteCallback = nullptr; #endif #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_STATE static const setStateFunc setStateCallback = nullptr; #endif #if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT static const writeMidiFunc writeMidiCallback = nullptr; #endif #if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST static const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; #endif // ----------------------------------------------------------------------- static volatile bool gCloseSignalReceived = false; #ifdef DISTRHO_OS_WINDOWS static BOOL WINAPI winSignalHandler(DWORD dwCtrlType) noexcept { if (dwCtrlType == CTRL_C_EVENT) { gCloseSignalReceived = true; return TRUE; } return FALSE; } static void initSignalHandler() { SetConsoleCtrlHandler(winSignalHandler, TRUE); } #else static void closeSignalHandler(int) noexcept { gCloseSignalReceived = true; } static void initSignalHandler() { struct sigaction sig; memset(&sig, 0, sizeof(sig)); sig.sa_handler = closeSignalHandler; sig.sa_flags = SA_RESTART; sigemptyset(&sig.sa_mask); sigaction(SIGINT, &sig, nullptr); sigaction(SIGTERM, &sig, nullptr); } #endif // ----------------------------------------------------------------------- #if DISTRHO_PLUGIN_HAS_UI class PluginJack : public DGL_NAMESPACE::IdleCallback #else class PluginJack #endif { public: PluginJack(jack_client_t* const client, const uintptr_t winId) : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, nullptr), #if DISTRHO_PLUGIN_HAS_UI fUI(this, winId, d_nextSampleRate, nullptr, // edit param setParameterValueCallback, setStateCallback, sendNoteCallback, nullptr, // window size nullptr, // file request nullptr, // bundle fPlugin.getInstancePointer(), 0.0), #endif fClient(client) { #if DISTRHO_PLUGIN_NUM_INPUTS > 0 || DISTRHO_PLUGIN_NUM_OUTPUTS > 0 # if DISTRHO_PLUGIN_NUM_INPUTS > 0 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) { const AudioPort& port(fPlugin.getAudioPort(true, i)); ulong hints = JackPortIsInput; if (port.hints & kAudioPortIsCV) hints |= JackPortIsControlVoltage; fPortAudioIns[i] = jackbridge_port_register(fClient, port.symbol, JACK_DEFAULT_AUDIO_TYPE, hints, 0); setAudioPortMetadata(port, fPortAudioIns[i], i); } # endif # if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) { const AudioPort& port(fPlugin.getAudioPort(false, i)); ulong hints = JackPortIsOutput; if (port.hints & kAudioPortIsCV) hints |= JackPortIsControlVoltage; fPortAudioOuts[i] = jackbridge_port_register(fClient, port.symbol, JACK_DEFAULT_AUDIO_TYPE, hints, 0); setAudioPortMetadata(port, fPortAudioOuts[i], DISTRHO_PLUGIN_NUM_INPUTS+i); } # endif #endif fPortEventsIn = jackbridge_port_register(fClient, "events-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT fPortMidiOut = jackbridge_port_register(fClient, "midi-out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); fPortMidiOutBuffer = nullptr; #endif #if DISTRHO_PLUGIN_WANT_PROGRAMS if (fPlugin.getProgramCount() > 0) { fPlugin.loadProgram(0); # if DISTRHO_PLUGIN_HAS_UI fUI.programLoaded(0); # endif } # if DISTRHO_PLUGIN_HAS_UI fProgramChanged = -1; # endif #endif if (const uint32_t count = fPlugin.getParameterCount()) { fLastOutputValues = new float[count]; std::memset(fLastOutputValues, 0, sizeof(float)*count); #if DISTRHO_PLUGIN_HAS_UI fParametersChanged = new bool[count]; std::memset(fParametersChanged, 0, sizeof(bool)*count); #endif for (uint32_t i=0; i < count; ++i) { #if DISTRHO_PLUGIN_HAS_UI if (! fPlugin.isParameterOutput(i)) fUI.parameterChanged(i, fPlugin.getParameterValue(i)); #endif } } else { fLastOutputValues = nullptr; #if DISTRHO_PLUGIN_HAS_UI fParametersChanged = nullptr; #endif } jackbridge_set_thread_init_callback(fClient, jackThreadInitCallback, this); jackbridge_set_buffer_size_callback(fClient, jackBufferSizeCallback, this); jackbridge_set_sample_rate_callback(fClient, jackSampleRateCallback, this); jackbridge_set_process_callback(fClient, jackProcessCallback, this); jackbridge_on_shutdown(fClient, jackShutdownCallback, this); fPlugin.activate(); jackbridge_activate(fClient); std::fflush(stdout); #if DISTRHO_PLUGIN_HAS_UI if (const char* const name = jackbridge_get_client_name(fClient)) fUI.setWindowTitle(name); else fUI.setWindowTitle(fPlugin.getName()); fUI.exec(this); #else while (! gCloseSignalReceived) d_sleep(1); // unused (void)winId; #endif } ~PluginJack() { if (fClient != nullptr) jackbridge_deactivate(fClient); if (fLastOutputValues != nullptr) { delete[] fLastOutputValues; fLastOutputValues = nullptr; } #if DISTRHO_PLUGIN_HAS_UI if (fParametersChanged != nullptr) { delete[] fParametersChanged; fParametersChanged = nullptr; } #endif fPlugin.deactivate(); if (fClient == nullptr) return; #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT jackbridge_port_unregister(fClient, fPortMidiOut); fPortMidiOut = nullptr; #endif jackbridge_port_unregister(fClient, fPortEventsIn); fPortEventsIn = nullptr; #if DISTRHO_PLUGIN_NUM_INPUTS > 0 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) { jackbridge_port_unregister(fClient, fPortAudioIns[i]); fPortAudioIns[i] = nullptr; } #endif #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) { jackbridge_port_unregister(fClient, fPortAudioOuts[i]); fPortAudioOuts[i] = nullptr; } #endif jackbridge_client_close(fClient); } // ------------------------------------------------------------------- protected: #if DISTRHO_PLUGIN_HAS_UI void idleCallback() override { if (gCloseSignalReceived) return fUI.quit(); # if DISTRHO_PLUGIN_WANT_PROGRAMS if (fProgramChanged >= 0) { fUI.programLoaded(fProgramChanged); fProgramChanged = -1; } # endif for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i) { if (fPlugin.isParameterOutput(i)) { const float value = fPlugin.getParameterValue(i); if (d_isEqual(fLastOutputValues[i], value)) continue; fLastOutputValues[i] = value; fUI.parameterChanged(i, value); } else if (fParametersChanged[i]) { fParametersChanged[i] = false; fUI.parameterChanged(i, fPlugin.getParameterValue(i)); } } fUI.exec_idle(); } #endif void jackBufferSize(const jack_nframes_t nframes) { fPlugin.setBufferSize(nframes, true); } void jackSampleRate(const jack_nframes_t nframes) { fPlugin.setSampleRate(nframes, true); } void jackProcess(const jack_nframes_t nframes) { #if DISTRHO_PLUGIN_NUM_INPUTS > 0 const float* audioIns[DISTRHO_PLUGIN_NUM_INPUTS]; for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) audioIns[i] = (const float*)jackbridge_port_get_buffer(fPortAudioIns[i], nframes); #else static const float** audioIns = nullptr; #endif #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 float* audioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS]; for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) audioOuts[i] = (float*)jackbridge_port_get_buffer(fPortAudioOuts[i], nframes); #else static float** audioOuts = nullptr; #endif #if DISTRHO_PLUGIN_WANT_TIMEPOS jack_position_t pos; fTimePosition.playing = (jackbridge_transport_query(fClient, &pos) == JackTransportRolling); if (pos.unique_1 == pos.unique_2) { fTimePosition.frame = pos.frame; if (pos.valid & JackPositionBBT) { fTimePosition.bbt.valid = true; fTimePosition.bbt.bar = pos.bar; fTimePosition.bbt.beat = pos.beat; fTimePosition.bbt.tick = pos.tick; #ifdef JACK_TICK_DOUBLE if (pos.valid & JackTickDouble) fTimePosition.bbt.tick = pos.tick_double; else #endif fTimePosition.bbt.tick = pos.tick; fTimePosition.bbt.barStartTick = pos.bar_start_tick; fTimePosition.bbt.beatsPerBar = pos.beats_per_bar; fTimePosition.bbt.beatType = pos.beat_type; fTimePosition.bbt.ticksPerBeat = pos.ticks_per_beat; fTimePosition.bbt.beatsPerMinute = pos.beats_per_minute; } else fTimePosition.bbt.valid = false; } else { fTimePosition.bbt.valid = false; fTimePosition.frame = 0; } fPlugin.setTimePosition(fTimePosition); #endif updateParameterTriggers(); #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT fPortMidiOutBuffer = jackbridge_port_get_buffer(fPortMidiOut, nframes); jackbridge_midi_clear_buffer(fPortMidiOutBuffer); #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT uint32_t midiEventCount = 0; MidiEvent midiEvents[512]; # if DISTRHO_PLUGIN_HAS_UI while (fNotesRingBuffer.isDataAvailableForReading()) { uint8_t midiData[3]; if (! fNotesRingBuffer.readCustomData(midiData, 3)) break; MidiEvent& midiEvent(midiEvents[midiEventCount++]); midiEvent.frame = 0; midiEvent.size = 3; std::memcpy(midiEvent.data, midiData, 3); if (midiEventCount == 512) break; } # endif #else static const uint32_t midiEventCount = 0; #endif void* const midiInBuf = jackbridge_port_get_buffer(fPortEventsIn, nframes); if (const uint32_t eventCount = std::min(512u - midiEventCount, jackbridge_midi_get_event_count(midiInBuf))) { jack_midi_event_t jevent; for (uint32_t i=0; i < eventCount; ++i) { if (! jackbridge_midi_event_get(&jevent, midiInBuf, i)) break; // Check if message is control change on channel 1 if (jevent.buffer[0] == 0xB0 && jevent.size == 3) { const uint8_t control = jevent.buffer[1]; const uint8_t value = jevent.buffer[2]; /* NOTE: This is not optimal, we're iterating all parameters on every CC message. Since the JACK standalone is more of a test tool, this will do for now. */ for (uint32_t j=0, paramCount=fPlugin.getParameterCount(); j < paramCount; ++j) { if (fPlugin.isParameterOutput(j)) continue; if (fPlugin.getParameterMidiCC(j) != control) continue; const float scaled = static_cast<float>(value)/127.0f; const float fvalue = fPlugin.getParameterRanges(j).getUnnormalizedValue(scaled); fPlugin.setParameterValue(j, fvalue); #if DISTRHO_PLUGIN_HAS_UI fParametersChanged[j] = true; #endif break; } } #if DISTRHO_PLUGIN_WANT_PROGRAMS // Check if message is program change on channel 1 else if (jevent.buffer[0] == 0xC0 && jevent.size == 2) { const uint8_t program = jevent.buffer[1]; if (program < fPlugin.getProgramCount()) { fPlugin.loadProgram(program); # if DISTRHO_PLUGIN_HAS_UI fProgramChanged = program; # endif } } #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT MidiEvent& midiEvent(midiEvents[midiEventCount++]); midiEvent.frame = jevent.time; midiEvent.size = static_cast<uint32_t>(jevent.size); if (midiEvent.size > MidiEvent::kDataSize) midiEvent.dataExt = jevent.buffer; else std::memcpy(midiEvent.data, jevent.buffer, midiEvent.size); #endif } } #if DISTRHO_PLUGIN_WANT_MIDI_INPUT fPlugin.run(audioIns, audioOuts, nframes, midiEvents, midiEventCount); #else fPlugin.run(audioIns, audioOuts, nframes); #endif #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT fPortMidiOutBuffer = nullptr; #endif } void jackShutdown() { d_stderr("jack has shutdown, quitting now..."); fClient = nullptr; #if DISTRHO_PLUGIN_HAS_UI fUI.quit(); #endif } // ------------------------------------------------------------------- #if DISTRHO_PLUGIN_HAS_UI void setParameterValue(const uint32_t index, const float value) { fPlugin.setParameterValue(index, value); } # if DISTRHO_PLUGIN_WANT_MIDI_INPUT void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) { uint8_t midiData[3]; midiData[0] = (velocity != 0 ? 0x90 : 0x80) | channel; midiData[1] = note; midiData[2] = velocity; fNotesRingBuffer.writeCustomData(midiData, 3); fNotesRingBuffer.commitWrite(); } # endif # if DISTRHO_PLUGIN_WANT_STATE void setState(const char* const key, const char* const value) { fPlugin.setState(key, value); } # endif #endif // DISTRHO_PLUGIN_HAS_UI // NOTE: no trigger support for JACK, simulate it here void updateParameterTriggers() { float defValue; for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i) { if ((fPlugin.getParameterHints(i) & kParameterIsTrigger) != kParameterIsTrigger) continue; defValue = fPlugin.getParameterRanges(i).def; if (d_isNotEqual(defValue, fPlugin.getParameterValue(i))) fPlugin.setParameterValue(i, defValue); } } // ------------------------------------------------------------------- private: PluginExporter fPlugin; #if DISTRHO_PLUGIN_HAS_UI UIExporter fUI; #endif jack_client_t* fClient; #if DISTRHO_PLUGIN_NUM_INPUTS > 0 jack_port_t* fPortAudioIns[DISTRHO_PLUGIN_NUM_INPUTS]; #endif #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 jack_port_t* fPortAudioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS]; #endif jack_port_t* fPortEventsIn; #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT jack_port_t* fPortMidiOut; void* fPortMidiOutBuffer; #endif #if DISTRHO_PLUGIN_WANT_TIMEPOS TimePosition fTimePosition; #endif // Temporary data float* fLastOutputValues; #if DISTRHO_PLUGIN_HAS_UI // Store DSP changes to send to UI bool* fParametersChanged; # if DISTRHO_PLUGIN_WANT_PROGRAMS int fProgramChanged; # endif # if DISTRHO_PLUGIN_WANT_MIDI_INPUT SmallStackRingBuffer fNotesRingBuffer; # endif #endif void setAudioPortMetadata(const AudioPort& port, jack_port_t* const jackport, const uint32_t index) { DISTRHO_SAFE_ASSERT_RETURN(jackport != nullptr,); const jack_uuid_t uuid = jackbridge_port_uuid(jackport); if (uuid == JACK_UUID_EMPTY_INITIALIZER) return; jackbridge_set_property(fClient, uuid, JACK_METADATA_PRETTY_NAME, port.name, "text/plain"); { char strBuf[0xff]; snprintf(strBuf, 0xff - 2, "%u", index); strBuf[0xff - 1] = '\0'; jackbridge_set_property(fClient, uuid, JACK_METADATA_ORDER, strBuf, "http://www.w3.org/2001/XMLSchema#integer"); } if (port.groupId != kPortGroupNone) { const PortGroupWithId& portGroup(fPlugin.getPortGroupById(port.groupId)); jackbridge_set_property(fClient, uuid, JACK_METADATA_PORT_GROUP, portGroup.name, "text/plain"); } if (port.hints & kAudioPortIsCV) { jackbridge_set_property(fClient, uuid, JACK_METADATA_SIGNAL_TYPE, "CV", "text/plain"); } else { jackbridge_set_property(fClient, uuid, JACK_METADATA_SIGNAL_TYPE, "AUDIO", "text/plain"); return; } // set cv ranges const bool cvPortScaled = port.hints & kCVPortHasScaledRange; if (port.hints & kCVPortHasBipolarRange) { if (cvPortScaled) { jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-5", "http://www.w3.org/2001/XMLSchema#integer"); jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "5", "http://www.w3.org/2001/XMLSchema#integer"); } else { jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-1", "http://www.w3.org/2001/XMLSchema#integer"); jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "1", "http://www.w3.org/2001/XMLSchema#integer"); } } else if (port.hints & kCVPortHasNegativeUnipolarRange) { if (cvPortScaled) { jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-10", "http://www.w3.org/2001/XMLSchema#integer"); jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "0", "http://www.w3.org/2001/XMLSchema#integer"); } else { jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-1", "http://www.w3.org/2001/XMLSchema#integer"); jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "0", "http://www.w3.org/2001/XMLSchema#integer"); } } else if (port.hints & kCVPortHasPositiveUnipolarRange) { if (cvPortScaled) { jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "0", "http://www.w3.org/2001/XMLSchema#integer"); jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "10", "http://www.w3.org/2001/XMLSchema#integer"); } else { jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "0", "http://www.w3.org/2001/XMLSchema#integer"); jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "1", "http://www.w3.org/2001/XMLSchema#integer"); } } } // ------------------------------------------------------------------- // Callbacks #define thisPtr ((PluginJack*)ptr) static void jackThreadInitCallback(void*) { #if defined(__SSE2_MATH__) _mm_setcsr(_mm_getcsr() | 0x8040); #elif defined(__aarch64__) uint64_t c; __asm__ __volatile__("mrs %0, fpcr \n" "orr %0, %0, #0x1000000\n" "msr fpcr, %0 \n" "isb \n" : "=r"(c) :: "memory"); #elif defined(__arm__) && !defined(__SOFTFP__) uint32_t c; __asm__ __volatile__("vmrs %0, fpscr \n" "orr %0, %0, #0x1000000\n" "vmsr fpscr, %0 \n" : "=r"(c) :: "memory"); #endif } static int jackBufferSizeCallback(jack_nframes_t nframes, void* ptr) { thisPtr->jackBufferSize(nframes); return 0; } static int jackSampleRateCallback(jack_nframes_t nframes, void* ptr) { thisPtr->jackSampleRate(nframes); return 0; } static int jackProcessCallback(jack_nframes_t nframes, void* ptr) { thisPtr->jackProcess(nframes); return 0; } static void jackShutdownCallback(void* ptr) { thisPtr->jackShutdown(); } #if DISTRHO_PLUGIN_HAS_UI static void setParameterValueCallback(void* ptr, uint32_t index, float value) { thisPtr->setParameterValue(index, value); } # if DISTRHO_PLUGIN_WANT_MIDI_INPUT static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) { thisPtr->sendNote(channel, note, velocity); } # endif # if DISTRHO_PLUGIN_WANT_STATE static void setStateCallback(void* ptr, const char* key, const char* value) { thisPtr->setState(key, value); } # endif #endif // DISTRHO_PLUGIN_HAS_UI #if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST bool requestParameterValueChange(const uint32_t index, const float value) { DISTRHO_SAFE_ASSERT_RETURN(index < fPlugin.getParameterCount(), false); fPlugin.setParameterValue(index, value); # if DISTRHO_PLUGIN_HAS_UI fParametersChanged[index] = true; # endif return true; } static bool requestParameterValueChangeCallback(void* ptr, const uint32_t index, const float value) { return thisPtr->requestParameterValueChange(index, value); } #endif #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT bool writeMidi(const MidiEvent& midiEvent) { DISTRHO_SAFE_ASSERT_RETURN(fPortMidiOutBuffer != nullptr, false); return jackbridge_midi_event_write(fPortMidiOutBuffer, midiEvent.frame, midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data, midiEvent.size); } static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent) { return thisPtr->writeMidi(midiEvent); } #endif #undef thisPtr }; // ----------------------------------------------------------------------- #ifdef DPF_RUNTIME_TESTING class PluginProcessTestingThread : public Thread { PluginExporter& plugin; public: PluginProcessTestingThread(PluginExporter& p) : plugin(p) {} protected: void run() override { plugin.setBufferSize(256, true); plugin.activate(); float buffer[256]; const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS > 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1]; float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS > 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1]; for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) inputs[i] = buffer; for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) outputs[i] = buffer; while (! shouldThreadExit()) { #if DISTRHO_PLUGIN_WANT_MIDI_INPUT plugin.run(inputs, outputs, 128, nullptr, 0); #else plugin.run(inputs, outputs, 128); #endif d_msleep(100); } plugin.deactivate(); } }; bool runSelfTests() { // simple plugin creation first { d_nextBufferSize = 512; d_nextSampleRate = 44100.0; PluginExporter plugin(nullptr, nullptr, nullptr, nullptr); d_nextBufferSize = 0; d_nextSampleRate = 0.0; } // keep values for all tests now d_nextBufferSize = 512; d_nextSampleRate = 44100.0; // simple processing { d_nextPluginIsSelfTest = true; PluginExporter plugin(nullptr, nullptr, nullptr, nullptr); d_nextPluginIsSelfTest = false; #if DISTRHO_PLUGIN_HAS_UI UIExporter ui(nullptr, 0, plugin.getSampleRate(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, plugin.getInstancePointer(), 0.0); ui.showAndFocus(); #endif plugin.activate(); plugin.deactivate(); plugin.setBufferSize(128, true); plugin.setSampleRate(48000, true); plugin.activate(); float buffer[128] = {}; const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS > 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1]; float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS > 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1]; for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) inputs[i] = buffer; for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) outputs[i] = buffer; #if DISTRHO_PLUGIN_WANT_MIDI_INPUT plugin.run(inputs, outputs, 128, nullptr, 0); #else plugin.run(inputs, outputs, 128); #endif plugin.deactivate(); #if DISTRHO_PLUGIN_HAS_UI ui.plugin_idle(); #endif } return true; // multi-threaded processing with UI { PluginExporter pluginA(nullptr, nullptr, nullptr, nullptr); PluginExporter pluginB(nullptr, nullptr, nullptr, nullptr); PluginExporter pluginC(nullptr, nullptr, nullptr, nullptr); PluginProcessTestingThread procTestA(pluginA); PluginProcessTestingThread procTestB(pluginB); PluginProcessTestingThread procTestC(pluginC); procTestA.startThread(); procTestB.startThread(); procTestC.startThread(); // wait 2s d_sleep(2); // stop the 2nd instance now procTestB.stopThread(5000); #if DISTRHO_PLUGIN_HAS_UI // start UI in the middle of this { UIExporter uiA(nullptr, 0, pluginA.getSampleRate(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, pluginA.getInstancePointer(), 0.0); UIExporter uiB(nullptr, 0, pluginA.getSampleRate(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, pluginB.getInstancePointer(), 0.0); UIExporter uiC(nullptr, 0, pluginA.getSampleRate(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, pluginC.getInstancePointer(), 0.0); // show UIs uiB.showAndFocus(); uiA.showAndFocus(); uiC.showAndFocus(); // idle for 3s for (int i=0; i<30; i++) { uiC.plugin_idle(); uiB.plugin_idle(); uiA.plugin_idle(); d_msleep(100); } } #endif procTestA.stopThread(5000); procTestC.stopThread(5000); } return true; } #endif // DPF_RUNTIME_TESTING END_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- int main(int argc, char* argv[]) { USE_NAMESPACE_DISTRHO; initSignalHandler(); #ifndef STATIC_BUILD // find plugin bundle static String bundlePath; if (bundlePath.isEmpty()) { String tmpPath(getBinaryFilename()); tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP)); #if defined(DISTRHO_OS_MAC) if (tmpPath.endsWith("/MacOS")) { tmpPath.truncate(tmpPath.rfind('/')); if (tmpPath.endsWith("/Contents")) { tmpPath.truncate(tmpPath.rfind('/')); bundlePath = tmpPath; d_nextBundlePath = bundlePath.buffer(); } } #else #ifdef DISTRHO_OS_WINDOWS const DWORD attr = GetFileAttributesA(tmpPath + DISTRHO_OS_SEP_STR "resources"); if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) #else if (access(tmpPath + DISTRHO_OS_SEP_STR "resources", F_OK) == 0) #endif { bundlePath = tmpPath; d_nextBundlePath = bundlePath.buffer(); } #endif } #endif if (argc == 2 && std::strcmp(argv[1], "selftest") == 0) { #ifdef DPF_RUNTIME_TESTING return runSelfTests() ? 0 : 1; #else d_stderr2("Code was built without DPF_RUNTIME_TESTING macro enabled, selftest option is not available"); return 1; #endif } #if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI /* the code below is based on * https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/ */ bool hasConsole = false; HANDLE consoleHandleOut, consoleHandleError; if (AttachConsole(ATTACH_PARENT_PROCESS)) { // Redirect unbuffered STDOUT to the console consoleHandleOut = GetStdHandle(STD_OUTPUT_HANDLE); if (consoleHandleOut != INVALID_HANDLE_VALUE) { freopen("CONOUT$", "w", stdout); setvbuf(stdout, NULL, _IONBF, 0); } // Redirect unbuffered STDERR to the console consoleHandleError = GetStdHandle(STD_ERROR_HANDLE); if (consoleHandleError != INVALID_HANDLE_VALUE) { freopen("CONOUT$", "w", stderr); setvbuf(stderr, NULL, _IONBF, 0); } hasConsole = true; } #endif jack_status_t status = jack_status_t(0x0); jack_client_t* client = jackbridge_client_open(DISTRHO_PLUGIN_NAME, JackNoStartServer, &status); #ifdef HAVE_JACK #define STANDALONE_NAME "JACK client" #else #define STANDALONE_NAME "Native audio driver" #endif if (client == nullptr) { String errorString; if (status & JackFailure) errorString += "Overall operation failed;\n"; if (status & JackInvalidOption) errorString += "The operation contained an invalid or unsupported option;\n"; if (status & JackNameNotUnique) errorString += "The desired client name was not unique;\n"; if (status & JackServerStarted) errorString += "The JACK server was started as a result of this operation;\n"; if (status & JackServerFailed) errorString += "Unable to connect to the JACK server;\n"; if (status & JackServerError) errorString += "Communication error with the JACK server;\n"; if (status & JackNoSuchClient) errorString += "Requested client does not exist;\n"; if (status & JackLoadFailure) errorString += "Unable to load internal client;\n"; if (status & JackInitFailure) errorString += "Unable to initialize client;\n"; if (status & JackShmFailure) errorString += "Unable to access shared memory;\n"; if (status & JackVersionError) errorString += "Client's protocol version does not match;\n"; if (status & JackBackendError) errorString += "Backend Error;\n"; if (status & JackClientZombie) errorString += "Client is being shutdown against its will;\n"; if (status & JackBridgeNativeFailed) errorString += "Native audio driver was unable to start;\n"; if (errorString.isNotEmpty()) { errorString[errorString.length()-2] = '.'; d_stderr("Failed to create the " STANDALONE_NAME ", reason was:\n%s", errorString.buffer()); } else d_stderr("Failed to create the " STANDALONE_NAME ", cannot continue!"); #if defined(DISTRHO_OS_MAC) CFStringRef errorTitleRef = CFStringCreateWithCString(nullptr, DISTRHO_PLUGIN_NAME ": Error", kCFStringEncodingUTF8); CFStringRef errorStringRef = CFStringCreateWithCString(nullptr, String("Failed to create " STANDALONE_NAME ", reason was:\n" + errorString).buffer(), kCFStringEncodingUTF8); CFUserNotificationDisplayAlert(0, kCFUserNotificationCautionAlertLevel, nullptr, nullptr, nullptr, errorTitleRef, errorStringRef, nullptr, nullptr, nullptr, nullptr); #elif defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI // make sure message box is high-dpi aware if (const HMODULE user32 = LoadLibrary("user32.dll")) { typedef BOOL(WINAPI* SPDA)(void); #if defined(__GNUC__) && (__GNUC__ >= 9) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wcast-function-type" #endif const SPDA SetProcessDPIAware = (SPDA)GetProcAddress(user32, "SetProcessDPIAware"); #if defined(__GNUC__) && (__GNUC__ >= 9) # pragma GCC diagnostic pop #endif if (SetProcessDPIAware) SetProcessDPIAware(); FreeLibrary(user32); } const String win32error = "Failed to create " STANDALONE_NAME ", reason was:\n" + errorString; MessageBoxA(nullptr, win32error.buffer(), "", MB_ICONERROR); #endif return 1; } d_nextBufferSize = jackbridge_get_buffer_size(client); d_nextSampleRate = jackbridge_get_sample_rate(client); d_nextCanRequestParameterValueChanges = true; uintptr_t winId = 0; #if DISTRHO_PLUGIN_HAS_UI if (argc == 3 && std::strcmp(argv[1], "embed") == 0) winId = static_cast<uintptr_t>(std::atoll(argv[2])); #endif const PluginJack p(client, winId); #if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI /* the code below is based on * https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/ */ // Send "enter" to release application from the console // This is a hack, but if not used the console doesn't know the application has // returned. The "enter" key only sent if the console window is in focus. if (hasConsole && (GetConsoleWindow() == GetForegroundWindow() || SetFocus(GetConsoleWindow()) != nullptr)) { INPUT ip; // Set up a generic keyboard event. ip.type = INPUT_KEYBOARD; ip.ki.wScan = 0; // hardware scan code for key ip.ki.time = 0; ip.ki.dwExtraInfo = 0; // Send the "Enter" key ip.ki.wVk = 0x0D; // virtual-key code for the "Enter" key ip.ki.dwFlags = 0; // 0 for key press SendInput(1, &ip, sizeof(INPUT)); // Release the "Enter" key ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release SendInput(1, &ip, sizeof(INPUT)); } #endif return 0; } // -----------------------------------------------------------------------