Mercurial > hg > pub > prymula > com
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoPluginJACK.cpp Mon Oct 16 21:53:34 2023 +0200 @@ -0,0 +1,1161 @@ +/* + * 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; +} + +// -----------------------------------------------------------------------