DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

DistrhoPluginJACK.cpp (37499B)


      1 /*
      2  * DISTRHO Plugin Framework (DPF)
      3  * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
      4  *
      5  * This program is free software; you can redistribute it and/or
      6  * modify it under the terms of the GNU Lesser General Public
      7  * License as published by the Free Software Foundation.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     12  * GNU Lesser General Public License for more details.
     13  *
     14  * For a full copy of the license see the LGPL.txt file
     15  */
     16 
     17 #include "DistrhoPluginInternal.hpp"
     18 
     19 #ifndef STATIC_BUILD
     20 # include "../DistrhoPluginUtils.hpp"
     21 #endif
     22 
     23 #if DISTRHO_PLUGIN_HAS_UI
     24 # include "DistrhoUIInternal.hpp"
     25 # include "../extra/RingBuffer.hpp"
     26 #else
     27 # include "../extra/Sleep.hpp"
     28 #endif
     29 
     30 #ifdef DPF_RUNTIME_TESTING
     31 # include "../extra/Thread.hpp"
     32 #endif
     33 
     34 #if defined(HAVE_JACK) && defined(STATIC_BUILD) && !defined(DISTRHO_OS_WASM)
     35 # define JACKBRIDGE_DIRECT
     36 #endif
     37 
     38 #include "jackbridge/JackBridge.cpp"
     39 #include "lv2/lv2.h"
     40 
     41 #ifdef DISTRHO_OS_MAC
     42 # define Point CocoaPoint
     43 # include <CoreFoundation/CoreFoundation.h>
     44 # undef Point
     45 #endif
     46 
     47 #ifndef DISTRHO_OS_WINDOWS
     48 # include <signal.h>
     49 # include <unistd.h>
     50 #endif
     51 
     52 #ifdef __SSE2_MATH__
     53 # include <xmmintrin.h>
     54 #endif
     55 
     56 #ifndef JACK_METADATA_ORDER
     57 # define JACK_METADATA_ORDER "http://jackaudio.org/metadata/order"
     58 #endif
     59 
     60 #ifndef JACK_METADATA_PRETTY_NAME
     61 # define JACK_METADATA_PRETTY_NAME "http://jackaudio.org/metadata/pretty-name"
     62 #endif
     63 
     64 #ifndef JACK_METADATA_PORT_GROUP
     65 # define JACK_METADATA_PORT_GROUP "http://jackaudio.org/metadata/port-group"
     66 #endif
     67 
     68 #ifndef JACK_METADATA_SIGNAL_TYPE
     69 # define JACK_METADATA_SIGNAL_TYPE "http://jackaudio.org/metadata/signal-type"
     70 #endif
     71 
     72 // -----------------------------------------------------------------------
     73 
     74 START_NAMESPACE_DISTRHO
     75 
     76 #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_MIDI_INPUT
     77 static const sendNoteFunc sendNoteCallback = nullptr;
     78 #endif
     79 #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_STATE
     80 static const setStateFunc setStateCallback = nullptr;
     81 #endif
     82 #if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
     83 static const writeMidiFunc writeMidiCallback = nullptr;
     84 #endif
     85 #if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST
     86 static const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr;
     87 #endif
     88 
     89 #ifdef DPF_USING_LD_LINUX_WEBVIEW
     90 int dpf_webview_start(int argc, char* argv[]);
     91 #endif
     92 
     93 // -----------------------------------------------------------------------
     94 
     95 static volatile bool gCloseSignalReceived = false;
     96 
     97 #ifdef DISTRHO_OS_WINDOWS
     98 static BOOL WINAPI winSignalHandler(DWORD dwCtrlType) noexcept
     99 {
    100     if (dwCtrlType == CTRL_C_EVENT)
    101     {
    102         gCloseSignalReceived = true;
    103         return TRUE;
    104     }
    105     return FALSE;
    106 }
    107 
    108 static void initSignalHandler()
    109 {
    110     SetConsoleCtrlHandler(winSignalHandler, TRUE);
    111 }
    112 #else
    113 static void closeSignalHandler(int) noexcept
    114 {
    115     gCloseSignalReceived = true;
    116 }
    117 
    118 static void initSignalHandler()
    119 {
    120     struct sigaction sig;
    121     memset(&sig, 0, sizeof(sig));
    122 
    123     sig.sa_handler = closeSignalHandler;
    124     sig.sa_flags   = SA_RESTART;
    125     sigemptyset(&sig.sa_mask);
    126     sigaction(SIGINT, &sig, nullptr);
    127     sigaction(SIGTERM, &sig, nullptr);
    128 }
    129 #endif
    130 
    131 // -----------------------------------------------------------------------
    132 
    133 #if DISTRHO_PLUGIN_HAS_UI
    134 class PluginJack : public DGL_NAMESPACE::IdleCallback
    135 #else
    136 class PluginJack
    137 #endif
    138 {
    139 public:
    140     PluginJack(jack_client_t* const client, const uintptr_t winId)
    141         : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, nullptr),
    142 #if DISTRHO_PLUGIN_HAS_UI
    143           fUI(this,
    144               winId,
    145               d_nextSampleRate,
    146               nullptr, // edit param
    147               setParameterValueCallback,
    148               setStateCallback,
    149               sendNoteCallback,
    150               nullptr, // window size
    151               nullptr, // file request
    152               nullptr, // bundle
    153               fPlugin.getInstancePointer(),
    154               0.0),
    155 #endif
    156           fClient(client)
    157     {
    158 #if DISTRHO_PLUGIN_NUM_INPUTS > 0 || DISTRHO_PLUGIN_NUM_OUTPUTS > 0
    159 # if DISTRHO_PLUGIN_NUM_INPUTS > 0
    160         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i)
    161         {
    162             const AudioPort& port(fPlugin.getAudioPort(true, i));
    163             ulong hints = JackPortIsInput;
    164             if (port.hints & kAudioPortIsCV)
    165                 hints |= JackPortIsControlVoltage;
    166             fPortAudioIns[i] = jackbridge_port_register(fClient, port.symbol, JACK_DEFAULT_AUDIO_TYPE, hints, 0);
    167             setAudioPortMetadata(port, fPortAudioIns[i], i);
    168         }
    169 # endif
    170 # if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
    171         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
    172         {
    173             const AudioPort& port(fPlugin.getAudioPort(false, i));
    174             ulong hints = JackPortIsOutput;
    175             if (port.hints & kAudioPortIsCV)
    176                 hints |= JackPortIsControlVoltage;
    177             fPortAudioOuts[i] = jackbridge_port_register(fClient, port.symbol, JACK_DEFAULT_AUDIO_TYPE, hints, 0);
    178             setAudioPortMetadata(port, fPortAudioOuts[i], DISTRHO_PLUGIN_NUM_INPUTS+i);
    179         }
    180 # endif
    181 #endif
    182 
    183         fPortEventsIn = jackbridge_port_register(fClient, "events-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
    184 
    185 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    186         fPortMidiOut = jackbridge_port_register(fClient, "midi-out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
    187         fPortMidiOutBuffer = nullptr;
    188 #endif
    189 
    190 #if DISTRHO_PLUGIN_WANT_PROGRAMS
    191         if (fPlugin.getProgramCount() > 0)
    192         {
    193             fPlugin.loadProgram(0);
    194 # if DISTRHO_PLUGIN_HAS_UI
    195             fUI.programLoaded(0);
    196 # endif
    197         }
    198 # if DISTRHO_PLUGIN_HAS_UI
    199         fProgramChanged = -1;
    200 # endif
    201 #endif
    202 
    203         if (const uint32_t count = fPlugin.getParameterCount())
    204         {
    205             fLastOutputValues = new float[count];
    206             std::memset(fLastOutputValues, 0, sizeof(float)*count);
    207 
    208 #if DISTRHO_PLUGIN_HAS_UI
    209             fParametersChanged = new bool[count];
    210             std::memset(fParametersChanged, 0, sizeof(bool)*count);
    211 #endif
    212 
    213             for (uint32_t i=0; i < count; ++i)
    214             {
    215 #if DISTRHO_PLUGIN_HAS_UI
    216                 if (! fPlugin.isParameterOutput(i))
    217                     fUI.parameterChanged(i, fPlugin.getParameterValue(i));
    218 #endif
    219             }
    220         }
    221         else
    222         {
    223             fLastOutputValues = nullptr;
    224 #if DISTRHO_PLUGIN_HAS_UI
    225             fParametersChanged = nullptr;
    226 #endif
    227         }
    228 
    229         jackbridge_set_thread_init_callback(fClient, jackThreadInitCallback, this);
    230         jackbridge_set_buffer_size_callback(fClient, jackBufferSizeCallback, this);
    231         jackbridge_set_sample_rate_callback(fClient, jackSampleRateCallback, this);
    232         jackbridge_set_process_callback(fClient, jackProcessCallback, this);
    233         jackbridge_on_shutdown(fClient, jackShutdownCallback, this);
    234 
    235         fPlugin.activate();
    236 
    237         jackbridge_activate(fClient);
    238 
    239         std::fflush(stdout);
    240 
    241        #if DISTRHO_PLUGIN_HAS_UI
    242         String title(fPlugin.getMaker());
    243 
    244         if (title.isNotEmpty())
    245             title += ": ";
    246 
    247         if (const char* const name = jackbridge_get_client_name(fClient))
    248             title += name;
    249         else
    250             title += fPlugin.getName();
    251 
    252         fUI.setWindowTitle(title);
    253         fUI.exec(this);
    254        #else
    255         while (! gCloseSignalReceived)
    256             d_sleep(1);
    257 
    258         // unused
    259         (void)winId;
    260        #endif
    261     }
    262 
    263     ~PluginJack()
    264     {
    265         if (fClient != nullptr)
    266             jackbridge_deactivate(fClient);
    267 
    268         if (fLastOutputValues != nullptr)
    269         {
    270             delete[] fLastOutputValues;
    271             fLastOutputValues = nullptr;
    272         }
    273 
    274 #if DISTRHO_PLUGIN_HAS_UI
    275         if (fParametersChanged != nullptr)
    276         {
    277             delete[] fParametersChanged;
    278             fParametersChanged = nullptr;
    279         }
    280 #endif
    281 
    282         fPlugin.deactivate();
    283 
    284         if (fClient == nullptr)
    285             return;
    286 
    287 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    288         jackbridge_port_unregister(fClient, fPortMidiOut);
    289         fPortMidiOut = nullptr;
    290 #endif
    291 
    292         jackbridge_port_unregister(fClient, fPortEventsIn);
    293         fPortEventsIn = nullptr;
    294 
    295 #if DISTRHO_PLUGIN_NUM_INPUTS > 0
    296         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i)
    297         {
    298             jackbridge_port_unregister(fClient, fPortAudioIns[i]);
    299             fPortAudioIns[i] = nullptr;
    300         }
    301 #endif
    302 
    303 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
    304         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
    305         {
    306             jackbridge_port_unregister(fClient, fPortAudioOuts[i]);
    307             fPortAudioOuts[i] = nullptr;
    308         }
    309 #endif
    310 
    311         jackbridge_client_close(fClient);
    312     }
    313 
    314     // -------------------------------------------------------------------
    315 
    316 protected:
    317 #if DISTRHO_PLUGIN_HAS_UI
    318     void idleCallback() override
    319     {
    320         if (gCloseSignalReceived)
    321             return fUI.quit();
    322 
    323 # if DISTRHO_PLUGIN_WANT_PROGRAMS
    324         if (fProgramChanged >= 0)
    325         {
    326             fUI.programLoaded(fProgramChanged);
    327             fProgramChanged = -1;
    328         }
    329 # endif
    330 
    331         for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i)
    332         {
    333             if (fPlugin.isParameterOutput(i))
    334             {
    335                 const float value = fPlugin.getParameterValue(i);
    336 
    337                 if (d_isEqual(fLastOutputValues[i], value))
    338                     continue;
    339 
    340                 fLastOutputValues[i] = value;
    341                 fUI.parameterChanged(i, value);
    342             }
    343             else if (fParametersChanged[i])
    344             {
    345                 fParametersChanged[i] = false;
    346                 fUI.parameterChanged(i, fPlugin.getParameterValue(i));
    347             }
    348         }
    349 
    350         fUI.exec_idle();
    351     }
    352 #endif
    353 
    354     void jackBufferSize(const jack_nframes_t nframes)
    355     {
    356         fPlugin.setBufferSize(nframes, true);
    357     }
    358 
    359     void jackSampleRate(const jack_nframes_t nframes)
    360     {
    361         fPlugin.setSampleRate(nframes, true);
    362     }
    363 
    364     void jackProcess(const jack_nframes_t nframes)
    365     {
    366 #if DISTRHO_PLUGIN_NUM_INPUTS > 0
    367         const float* audioIns[DISTRHO_PLUGIN_NUM_INPUTS];
    368 
    369         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i)
    370             audioIns[i] = (const float*)jackbridge_port_get_buffer(fPortAudioIns[i], nframes);
    371 #else
    372         static const float** audioIns = nullptr;
    373 #endif
    374 
    375 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
    376         float* audioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS];
    377 
    378         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
    379             audioOuts[i] = (float*)jackbridge_port_get_buffer(fPortAudioOuts[i], nframes);
    380 #else
    381         static float** audioOuts = nullptr;
    382 #endif
    383 
    384 #if DISTRHO_PLUGIN_WANT_TIMEPOS
    385         jack_position_t pos;
    386         fTimePosition.playing = (jackbridge_transport_query(fClient, &pos) == JackTransportRolling);
    387 
    388         if (pos.unique_1 == pos.unique_2)
    389         {
    390             fTimePosition.frame = pos.frame;
    391 
    392             if (pos.valid & JackPositionBBT)
    393             {
    394                 fTimePosition.bbt.valid = true;
    395 
    396                 fTimePosition.bbt.bar  = pos.bar;
    397                 fTimePosition.bbt.beat = pos.beat;
    398                 fTimePosition.bbt.tick = pos.tick;
    399 #ifdef JACK_TICK_DOUBLE
    400                 if (pos.valid & JackTickDouble)
    401                     fTimePosition.bbt.tick = pos.tick_double;
    402                 else
    403 #endif
    404                     fTimePosition.bbt.tick = pos.tick;
    405                 fTimePosition.bbt.barStartTick = pos.bar_start_tick;
    406 
    407                 fTimePosition.bbt.beatsPerBar = pos.beats_per_bar;
    408                 fTimePosition.bbt.beatType    = pos.beat_type;
    409 
    410                 fTimePosition.bbt.ticksPerBeat   = pos.ticks_per_beat;
    411                 fTimePosition.bbt.beatsPerMinute = pos.beats_per_minute;
    412             }
    413             else
    414                 fTimePosition.bbt.valid = false;
    415         }
    416         else
    417         {
    418             fTimePosition.bbt.valid = false;
    419             fTimePosition.frame = 0;
    420         }
    421 
    422         fPlugin.setTimePosition(fTimePosition);
    423 #endif
    424 
    425         updateParameterTriggers();
    426 
    427 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    428         fPortMidiOutBuffer = jackbridge_port_get_buffer(fPortMidiOut, nframes);
    429         jackbridge_midi_clear_buffer(fPortMidiOutBuffer);
    430 #endif
    431 
    432 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    433         uint32_t  midiEventCount = 0;
    434         MidiEvent midiEvents[512];
    435 
    436 # if DISTRHO_PLUGIN_HAS_UI
    437         while (fNotesRingBuffer.isDataAvailableForReading())
    438         {
    439             uint8_t midiData[3];
    440             if (! fNotesRingBuffer.readCustomData(midiData, 3))
    441                 break;
    442 
    443             MidiEvent& midiEvent(midiEvents[midiEventCount++]);
    444             midiEvent.frame = 0;
    445             midiEvent.size  = 3;
    446             std::memcpy(midiEvent.data, midiData, 3);
    447 
    448             if (midiEventCount == 512)
    449                 break;
    450         }
    451 # endif
    452 #else
    453         static const uint32_t midiEventCount = 0;
    454 #endif
    455 
    456         void* const midiInBuf = jackbridge_port_get_buffer(fPortEventsIn, nframes);
    457 
    458         if (const uint32_t eventCount = std::min(512u - midiEventCount, jackbridge_midi_get_event_count(midiInBuf)))
    459         {
    460             jack_midi_event_t jevent;
    461 
    462             for (uint32_t i=0; i < eventCount; ++i)
    463             {
    464                 if (! jackbridge_midi_event_get(&jevent, midiInBuf, i))
    465                     break;
    466 
    467                 // Check if message is control change on channel 1
    468                 if (jevent.buffer[0] == 0xB0 && jevent.size == 3)
    469                 {
    470                     const uint8_t control = jevent.buffer[1];
    471                     const uint8_t value   = jevent.buffer[2];
    472 
    473                     /* NOTE: This is not optimal, we're iterating all parameters on every CC message.
    474                              Since the JACK standalone is more of a test tool, this will do for now. */
    475                     for (uint32_t j=0, paramCount=fPlugin.getParameterCount(); j < paramCount; ++j)
    476                     {
    477                         if (fPlugin.isParameterOutput(j))
    478                             continue;
    479                         if (fPlugin.getParameterMidiCC(j) != control)
    480                             continue;
    481 
    482                         const float scaled = static_cast<float>(value)/127.0f;
    483                         const float fvalue = fPlugin.getParameterRanges(j).getUnnormalizedValue(scaled);
    484                         fPlugin.setParameterValue(j, fvalue);
    485 #if DISTRHO_PLUGIN_HAS_UI
    486                         fParametersChanged[j] = true;
    487 #endif
    488                         break;
    489                     }
    490                 }
    491 #if DISTRHO_PLUGIN_WANT_PROGRAMS
    492                 // Check if message is program change on channel 1
    493                 else if (jevent.buffer[0] == 0xC0 && jevent.size == 2)
    494                 {
    495                     const uint8_t program = jevent.buffer[1];
    496 
    497                     if (program < fPlugin.getProgramCount())
    498                     {
    499                         fPlugin.loadProgram(program);
    500 # if DISTRHO_PLUGIN_HAS_UI
    501                         fProgramChanged = program;
    502 # endif
    503                     }
    504                 }
    505 #endif
    506 
    507 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    508                 MidiEvent& midiEvent(midiEvents[midiEventCount++]);
    509 
    510                 midiEvent.frame = jevent.time;
    511                 midiEvent.size  = static_cast<uint32_t>(jevent.size);
    512 
    513                 if (midiEvent.size > MidiEvent::kDataSize)
    514                     midiEvent.dataExt = jevent.buffer;
    515                 else
    516                     std::memcpy(midiEvent.data, jevent.buffer, midiEvent.size);
    517 #endif
    518             }
    519         }
    520 
    521 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    522         fPlugin.run(audioIns, audioOuts, nframes, midiEvents, midiEventCount);
    523 #else
    524         fPlugin.run(audioIns, audioOuts, nframes);
    525 #endif
    526 
    527 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    528         fPortMidiOutBuffer = nullptr;
    529 #endif
    530     }
    531 
    532     void jackShutdown()
    533     {
    534         d_stderr("jack has shutdown, quitting now...");
    535         fClient = nullptr;
    536 #if DISTRHO_PLUGIN_HAS_UI
    537         fUI.quit();
    538 #endif
    539     }
    540 
    541     // -------------------------------------------------------------------
    542 
    543 #if DISTRHO_PLUGIN_HAS_UI
    544     void setParameterValue(const uint32_t index, const float value)
    545     {
    546         fPlugin.setParameterValue(index, value);
    547     }
    548 
    549 # if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    550     void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
    551     {
    552         uint8_t midiData[3];
    553         midiData[0] = (velocity != 0 ? 0x90 : 0x80) | channel;
    554         midiData[1] = note;
    555         midiData[2] = velocity;
    556         fNotesRingBuffer.writeCustomData(midiData, 3);
    557         fNotesRingBuffer.commitWrite();
    558     }
    559 # endif
    560 
    561 # if DISTRHO_PLUGIN_WANT_STATE
    562     void setState(const char* const key, const char* const value)
    563     {
    564         fPlugin.setState(key, value);
    565     }
    566 # endif
    567 #endif // DISTRHO_PLUGIN_HAS_UI
    568 
    569     // NOTE: no trigger support for JACK, simulate it here
    570     void updateParameterTriggers()
    571     {
    572         float defValue;
    573 
    574         for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i)
    575         {
    576             if ((fPlugin.getParameterHints(i) & kParameterIsTrigger) != kParameterIsTrigger)
    577                 continue;
    578 
    579             defValue = fPlugin.getParameterRanges(i).def;
    580 
    581             if (d_isNotEqual(defValue, fPlugin.getParameterValue(i)))
    582                 fPlugin.setParameterValue(i, defValue);
    583         }
    584     }
    585 
    586     // -------------------------------------------------------------------
    587 
    588 private:
    589     PluginExporter fPlugin;
    590 #if DISTRHO_PLUGIN_HAS_UI
    591     UIExporter     fUI;
    592 #endif
    593 
    594     jack_client_t* fClient;
    595 
    596 #if DISTRHO_PLUGIN_NUM_INPUTS > 0
    597     jack_port_t* fPortAudioIns[DISTRHO_PLUGIN_NUM_INPUTS];
    598 #endif
    599 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
    600     jack_port_t* fPortAudioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS];
    601 #endif
    602     jack_port_t* fPortEventsIn;
    603 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    604     jack_port_t* fPortMidiOut;
    605     void*        fPortMidiOutBuffer;
    606 #endif
    607 #if DISTRHO_PLUGIN_WANT_TIMEPOS
    608     TimePosition fTimePosition;
    609 #endif
    610 
    611     // Temporary data
    612     float* fLastOutputValues;
    613 
    614 #if DISTRHO_PLUGIN_HAS_UI
    615     // Store DSP changes to send to UI
    616     bool* fParametersChanged;
    617 # if DISTRHO_PLUGIN_WANT_PROGRAMS
    618     int fProgramChanged;
    619 # endif
    620 # if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    621     SmallStackRingBuffer fNotesRingBuffer;
    622 # endif
    623 #endif
    624 
    625     void setAudioPortMetadata(const AudioPort& port, jack_port_t* const jackport, const uint32_t index)
    626     {
    627         DISTRHO_SAFE_ASSERT_RETURN(jackport != nullptr,);
    628 
    629         const jack_uuid_t uuid = jackbridge_port_uuid(jackport);
    630 
    631         if (uuid == JACK_UUID_EMPTY_INITIALIZER)
    632             return;
    633 
    634         jackbridge_set_property(fClient, uuid, JACK_METADATA_PRETTY_NAME, port.name, "text/plain");
    635 
    636         {
    637             char strBuf[0xff];
    638             snprintf(strBuf, 0xff - 2, "%u", index);
    639             strBuf[0xff - 1] = '\0';
    640             jackbridge_set_property(fClient, uuid, JACK_METADATA_ORDER, strBuf, "http://www.w3.org/2001/XMLSchema#integer");
    641         }
    642 
    643         if (port.groupId != kPortGroupNone)
    644         {
    645             const PortGroupWithId& portGroup(fPlugin.getPortGroupById(port.groupId));
    646             jackbridge_set_property(fClient, uuid, JACK_METADATA_PORT_GROUP, portGroup.name, "text/plain");
    647         }
    648 
    649         if (port.hints & kAudioPortIsCV)
    650         {
    651             jackbridge_set_property(fClient, uuid, JACK_METADATA_SIGNAL_TYPE, "CV", "text/plain");
    652         }
    653         else
    654         {
    655             jackbridge_set_property(fClient, uuid, JACK_METADATA_SIGNAL_TYPE, "AUDIO", "text/plain");
    656             return;
    657         }
    658 
    659         // set cv ranges
    660         const bool cvPortScaled = port.hints & kCVPortHasScaledRange;
    661 
    662         if (port.hints & kCVPortHasBipolarRange)
    663         {
    664             if (cvPortScaled)
    665             {
    666                 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-5", "http://www.w3.org/2001/XMLSchema#integer");
    667                 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "5", "http://www.w3.org/2001/XMLSchema#integer");
    668             }
    669             else
    670             {
    671                 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-1", "http://www.w3.org/2001/XMLSchema#integer");
    672                 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "1", "http://www.w3.org/2001/XMLSchema#integer");
    673             }
    674         }
    675         else if (port.hints & kCVPortHasNegativeUnipolarRange)
    676         {
    677             if (cvPortScaled)
    678             {
    679                 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-10", "http://www.w3.org/2001/XMLSchema#integer");
    680                 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "0", "http://www.w3.org/2001/XMLSchema#integer");
    681             }
    682             else
    683             {
    684                 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "-1", "http://www.w3.org/2001/XMLSchema#integer");
    685                 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "0", "http://www.w3.org/2001/XMLSchema#integer");
    686             }
    687         }
    688         else if (port.hints & kCVPortHasPositiveUnipolarRange)
    689         {
    690             if (cvPortScaled)
    691             {
    692                 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "0", "http://www.w3.org/2001/XMLSchema#integer");
    693                 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "10", "http://www.w3.org/2001/XMLSchema#integer");
    694             }
    695             else
    696             {
    697                 jackbridge_set_property(fClient, uuid, LV2_CORE__minimum, "0", "http://www.w3.org/2001/XMLSchema#integer");
    698                 jackbridge_set_property(fClient, uuid, LV2_CORE__maximum, "1", "http://www.w3.org/2001/XMLSchema#integer");
    699             }
    700         }
    701     }
    702 
    703     // -------------------------------------------------------------------
    704     // Callbacks
    705 
    706     #define thisPtr ((PluginJack*)ptr)
    707 
    708     static void jackThreadInitCallback(void*)
    709     {
    710        #if defined(__SSE2_MATH__)
    711         _mm_setcsr(_mm_getcsr() | 0x8040);
    712        #elif defined(__aarch64__)
    713         uint64_t c;
    714         __asm__ __volatile__("mrs %0, fpcr          \n"
    715                              "orr %0, %0, #0x1000000\n"
    716                              "msr fpcr, %0          \n"
    717                              "isb                   \n"
    718                              : "=r"(c) :: "memory");
    719        #elif defined(__arm__) && !defined(__SOFTFP__)
    720         uint32_t c;
    721         __asm__ __volatile__("vmrs %0, fpscr         \n"
    722                              "orr  %0, %0, #0x1000000\n"
    723                              "vmsr fpscr, %0         \n"
    724                              : "=r"(c) :: "memory");
    725        #endif
    726     }
    727 
    728     static int jackBufferSizeCallback(jack_nframes_t nframes, void* ptr)
    729     {
    730         thisPtr->jackBufferSize(nframes);
    731         return 0;
    732     }
    733 
    734     static int jackSampleRateCallback(jack_nframes_t nframes, void* ptr)
    735     {
    736         thisPtr->jackSampleRate(nframes);
    737         return 0;
    738     }
    739 
    740     static int jackProcessCallback(jack_nframes_t nframes, void* ptr)
    741     {
    742         thisPtr->jackProcess(nframes);
    743         return 0;
    744     }
    745 
    746     static void jackShutdownCallback(void* ptr)
    747     {
    748         thisPtr->jackShutdown();
    749     }
    750 
    751 #if DISTRHO_PLUGIN_HAS_UI
    752     static void setParameterValueCallback(void* ptr, uint32_t index, float value)
    753     {
    754         thisPtr->setParameterValue(index, value);
    755     }
    756 
    757 # if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    758     static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity)
    759     {
    760         thisPtr->sendNote(channel, note, velocity);
    761     }
    762 # endif
    763 
    764 # if DISTRHO_PLUGIN_WANT_STATE
    765     static void setStateCallback(void* ptr, const char* key, const char* value)
    766     {
    767         thisPtr->setState(key, value);
    768     }
    769 # endif
    770 #endif // DISTRHO_PLUGIN_HAS_UI
    771 
    772 #if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST
    773     bool requestParameterValueChange(const uint32_t index, const float value)
    774     {
    775         DISTRHO_SAFE_ASSERT_RETURN(index < fPlugin.getParameterCount(), false);
    776 
    777         fPlugin.setParameterValue(index, value);
    778 # if DISTRHO_PLUGIN_HAS_UI
    779         fParametersChanged[index] = true;
    780 # endif
    781         return true;
    782     }
    783 
    784     static bool requestParameterValueChangeCallback(void* ptr, const uint32_t index, const float value)
    785     {
    786         return thisPtr->requestParameterValueChange(index, value);
    787     }
    788 #endif
    789 
    790 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    791     bool writeMidi(const MidiEvent& midiEvent)
    792     {
    793         DISTRHO_SAFE_ASSERT_RETURN(fPortMidiOutBuffer != nullptr, false);
    794 
    795         return jackbridge_midi_event_write(fPortMidiOutBuffer,
    796                                            midiEvent.frame,
    797                                            midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data,
    798                                            midiEvent.size);
    799     }
    800 
    801     static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent)
    802     {
    803         return thisPtr->writeMidi(midiEvent);
    804     }
    805 #endif
    806 
    807     #undef thisPtr
    808 };
    809 
    810 // -----------------------------------------------------------------------
    811 
    812 #ifdef DPF_RUNTIME_TESTING
    813 class PluginProcessTestingThread : public Thread
    814 {
    815     PluginExporter& plugin;
    816 
    817 public:
    818     PluginProcessTestingThread(PluginExporter& p) : plugin(p) {}
    819 
    820 protected:
    821     void run() override
    822     {
    823         plugin.setBufferSize(256, true);
    824         plugin.activate();
    825 
    826         float buffer[256];
    827         const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS > 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1];
    828         float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS > 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1];
    829         for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
    830             inputs[i] = buffer;
    831         for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
    832             outputs[i] = buffer;
    833 
    834         while (! shouldThreadExit())
    835         {
    836            #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    837             plugin.run(inputs, outputs, 128, nullptr, 0);
    838            #else
    839             plugin.run(inputs, outputs, 128);
    840            #endif
    841             d_msleep(100);
    842         }
    843 
    844         plugin.deactivate();
    845     }
    846 };
    847 
    848 bool runSelfTests()
    849 {
    850     // simple plugin creation first
    851     {
    852         d_nextBufferSize = 512;
    853         d_nextSampleRate = 44100.0;
    854         PluginExporter plugin(nullptr, nullptr, nullptr, nullptr);
    855         d_nextBufferSize = 0;
    856         d_nextSampleRate = 0.0;
    857     }
    858 
    859     // keep values for all tests now
    860     d_nextBufferSize = 512;
    861     d_nextSampleRate = 44100.0;
    862 
    863     // simple processing
    864     {
    865         d_nextPluginIsSelfTest = true;
    866         PluginExporter plugin(nullptr, nullptr, nullptr, nullptr);
    867         d_nextPluginIsSelfTest = false;
    868 
    869        #if DISTRHO_PLUGIN_HAS_UI
    870         UIExporter ui(nullptr, 0, plugin.getSampleRate(),
    871                       nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    872                       plugin.getInstancePointer(), 0.0);
    873         ui.showAndFocus();
    874        #endif
    875 
    876         plugin.activate();
    877         plugin.deactivate();
    878         plugin.setBufferSize(128, true);
    879         plugin.setSampleRate(48000, true);
    880         plugin.activate();
    881 
    882         float buffer[128] = {};
    883         const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS > 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1];
    884         float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS > 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1];
    885         for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i)
    886             inputs[i] = buffer;
    887         for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
    888             outputs[i] = buffer;
    889 
    890        #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    891         plugin.run(inputs, outputs, 128, nullptr, 0);
    892        #else
    893         plugin.run(inputs, outputs, 128);
    894        #endif
    895 
    896         plugin.deactivate();
    897 
    898        #if DISTRHO_PLUGIN_HAS_UI
    899         ui.plugin_idle();
    900        #endif
    901     }
    902 
    903     return true;
    904 
    905     // multi-threaded processing with UI
    906     {
    907         PluginExporter pluginA(nullptr, nullptr, nullptr, nullptr);
    908         PluginExporter pluginB(nullptr, nullptr, nullptr, nullptr);
    909         PluginExporter pluginC(nullptr, nullptr, nullptr, nullptr);
    910         PluginProcessTestingThread procTestA(pluginA);
    911         PluginProcessTestingThread procTestB(pluginB);
    912         PluginProcessTestingThread procTestC(pluginC);
    913         procTestA.startThread();
    914         procTestB.startThread();
    915         procTestC.startThread();
    916 
    917         // wait 2s
    918         d_sleep(2);
    919 
    920         // stop the 2nd instance now
    921         procTestB.stopThread(5000);
    922 
    923        #if DISTRHO_PLUGIN_HAS_UI
    924         // start UI in the middle of this
    925         {
    926             UIExporter uiA(nullptr, 0, pluginA.getSampleRate(),
    927                            nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    928                            pluginA.getInstancePointer(), 0.0);
    929             UIExporter uiB(nullptr, 0, pluginA.getSampleRate(),
    930                            nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    931                            pluginB.getInstancePointer(), 0.0);
    932             UIExporter uiC(nullptr, 0, pluginA.getSampleRate(),
    933                            nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
    934                            pluginC.getInstancePointer(), 0.0);
    935 
    936             // show UIs
    937             uiB.showAndFocus();
    938             uiA.showAndFocus();
    939             uiC.showAndFocus();
    940 
    941             // idle for 3s
    942             for (int i=0; i<30; i++)
    943             {
    944                 uiC.plugin_idle();
    945                 uiB.plugin_idle();
    946                 uiA.plugin_idle();
    947                 d_msleep(100);
    948             }
    949         }
    950        #endif
    951 
    952         procTestA.stopThread(5000);
    953         procTestC.stopThread(5000);
    954     }
    955 
    956     return true;
    957 }
    958 #endif // DPF_RUNTIME_TESTING
    959 
    960 END_NAMESPACE_DISTRHO
    961 
    962 // -----------------------------------------------------------------------
    963 
    964 int main(int argc, char* argv[])
    965 {
    966     USE_NAMESPACE_DISTRHO;
    967 
    968     initSignalHandler();
    969 
    970    #ifndef STATIC_BUILD
    971     // find plugin bundle
    972     static String bundlePath;
    973     if (bundlePath.isEmpty())
    974     {
    975         String tmpPath(getBinaryFilename());
    976         tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP));
    977       #if defined(DISTRHO_OS_MAC)
    978         if (tmpPath.endsWith("/MacOS"))
    979         {
    980             tmpPath.truncate(tmpPath.rfind('/'));
    981             if (tmpPath.endsWith("/Contents"))
    982             {
    983                 tmpPath.truncate(tmpPath.rfind('/'));
    984                 bundlePath = tmpPath;
    985                 d_nextBundlePath = bundlePath.buffer();
    986             }
    987         }
    988       #else
    989        #ifdef DISTRHO_OS_WINDOWS
    990         const DWORD attr = GetFileAttributesA(tmpPath + DISTRHO_OS_SEP_STR "resources");
    991         if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
    992        #else
    993         if (access(tmpPath + DISTRHO_OS_SEP_STR "resources", F_OK) == 0)
    994        #endif
    995         {
    996             bundlePath = tmpPath;
    997             d_nextBundlePath = bundlePath.buffer();
    998         }
    999       #endif
   1000     }
   1001    #endif
   1002 
   1003    #ifdef DPF_USING_LD_LINUX_WEBVIEW
   1004     if (argc >= 2 && std::strcmp(argv[1], "dpf-ld-linux-webview") == 0)
   1005         return dpf_webview_start(argc - 1, argv + 1);
   1006    #endif
   1007 
   1008     if (argc == 2 && std::strcmp(argv[1], "selftest") == 0)
   1009     {
   1010        #ifdef DPF_RUNTIME_TESTING
   1011         return runSelfTests() ? 0 : 1;
   1012        #else
   1013         d_stderr2("Code was built without DPF_RUNTIME_TESTING macro enabled, selftest option is not available");
   1014         return 1;
   1015        #endif
   1016     }
   1017 
   1018    #if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI
   1019     /* the code below is based on
   1020      * https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/
   1021      */
   1022     bool hasConsole = false;
   1023 
   1024     HANDLE consoleHandleOut, consoleHandleError;
   1025 
   1026     if (AttachConsole(ATTACH_PARENT_PROCESS))
   1027     {
   1028         // Redirect unbuffered STDOUT to the console
   1029         consoleHandleOut = GetStdHandle(STD_OUTPUT_HANDLE);
   1030         if (consoleHandleOut != INVALID_HANDLE_VALUE)
   1031         {
   1032             freopen("CONOUT$", "w", stdout);
   1033             setvbuf(stdout, NULL, _IONBF, 0);
   1034         }
   1035 
   1036         // Redirect unbuffered STDERR to the console
   1037         consoleHandleError = GetStdHandle(STD_ERROR_HANDLE);
   1038         if (consoleHandleError != INVALID_HANDLE_VALUE)
   1039         {
   1040             freopen("CONOUT$", "w", stderr);
   1041             setvbuf(stderr, NULL, _IONBF, 0);
   1042         }
   1043 
   1044         hasConsole = true;
   1045     }
   1046    #endif
   1047 
   1048     jack_status_t  status = jack_status_t(0x0);
   1049     jack_client_t* client = jackbridge_client_open(DISTRHO_PLUGIN_NAME, JackNoStartServer, &status);
   1050 
   1051    #ifdef HAVE_JACK
   1052     #define STANDALONE_NAME "JACK client"
   1053    #else
   1054     #define STANDALONE_NAME "Native audio driver"
   1055    #endif
   1056 
   1057     if (client == nullptr)
   1058     {
   1059         String errorString;
   1060 
   1061         if (status & JackFailure)
   1062             errorString += "Overall operation failed;\n";
   1063         if (status & JackInvalidOption)
   1064             errorString += "The operation contained an invalid or unsupported option;\n";
   1065         if (status & JackNameNotUnique)
   1066             errorString += "The desired client name was not unique;\n";
   1067         if (status & JackServerStarted)
   1068             errorString += "The JACK server was started as a result of this operation;\n";
   1069         if (status & JackServerFailed)
   1070             errorString += "Unable to connect to the JACK server;\n";
   1071         if (status & JackServerError)
   1072             errorString += "Communication error with the JACK server;\n";
   1073         if (status & JackNoSuchClient)
   1074             errorString += "Requested client does not exist;\n";
   1075         if (status & JackLoadFailure)
   1076             errorString += "Unable to load internal client;\n";
   1077         if (status & JackInitFailure)
   1078             errorString += "Unable to initialize client;\n";
   1079         if (status & JackShmFailure)
   1080             errorString += "Unable to access shared memory;\n";
   1081         if (status & JackVersionError)
   1082             errorString += "Client's protocol version does not match;\n";
   1083         if (status & JackBackendError)
   1084             errorString += "Backend Error;\n";
   1085         if (status & JackClientZombie)
   1086             errorString += "Client is being shutdown against its will;\n";
   1087         if (status & JackBridgeNativeFailed)
   1088             errorString += "Native audio driver was unable to start;\n";
   1089 
   1090         if (errorString.isNotEmpty())
   1091         {
   1092             errorString[errorString.length()-2] = '.';
   1093             d_stderr("Failed to create the " STANDALONE_NAME ", reason was:\n%s", errorString.buffer());
   1094         }
   1095         else
   1096             d_stderr("Failed to create the " STANDALONE_NAME ", cannot continue!");
   1097 
   1098        #if defined(DISTRHO_OS_MAC)
   1099         CFStringRef errorTitleRef = CFStringCreateWithCString(nullptr,
   1100            DISTRHO_PLUGIN_NAME ": Error", kCFStringEncodingUTF8);
   1101         CFStringRef errorStringRef = CFStringCreateWithCString(nullptr,
   1102            String("Failed to create " STANDALONE_NAME ", reason was:\n" + errorString).buffer(), kCFStringEncodingUTF8);
   1103 
   1104         CFUserNotificationDisplayAlert(0, kCFUserNotificationCautionAlertLevel,
   1105            nullptr, nullptr, nullptr,
   1106            errorTitleRef, errorStringRef,
   1107            nullptr, nullptr, nullptr, nullptr);
   1108        #elif defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI
   1109         // make sure message box is high-dpi aware
   1110         if (const HMODULE user32 = LoadLibrary("user32.dll"))
   1111         {
   1112             typedef BOOL(WINAPI* SPDA)(void);
   1113            #if defined(__GNUC__) && (__GNUC__ >= 9)
   1114            # pragma GCC diagnostic push
   1115            # pragma GCC diagnostic ignored "-Wcast-function-type"
   1116            #endif
   1117             const SPDA SetProcessDPIAware = (SPDA)GetProcAddress(user32, "SetProcessDPIAware");
   1118            #if defined(__GNUC__) && (__GNUC__ >= 9)
   1119            # pragma GCC diagnostic pop
   1120            #endif
   1121             if (SetProcessDPIAware)
   1122                 SetProcessDPIAware();
   1123             FreeLibrary(user32);
   1124         }
   1125 
   1126         const String win32error = "Failed to create " STANDALONE_NAME ", reason was:\n" + errorString;
   1127         MessageBoxA(nullptr, win32error.buffer(), "", MB_ICONERROR);
   1128        #endif
   1129 
   1130         return 1;
   1131     }
   1132 
   1133     d_nextBufferSize = jackbridge_get_buffer_size(client);
   1134     d_nextSampleRate = jackbridge_get_sample_rate(client);
   1135     d_nextCanRequestParameterValueChanges = true;
   1136 
   1137     uintptr_t winId = 0;
   1138    #if DISTRHO_PLUGIN_HAS_UI
   1139     if (argc == 3 && std::strcmp(argv[1], "embed") == 0)
   1140         winId = static_cast<uintptr_t>(std::atoll(argv[2]));
   1141    #endif
   1142 
   1143     const PluginJack p(client, winId);
   1144 
   1145    #if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI
   1146     /* the code below is based on
   1147      * https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/
   1148      */
   1149 
   1150     // Send "enter" to release application from the console
   1151     // This is a hack, but if not used the console doesn't know the application has
   1152     // returned. The "enter" key only sent if the console window is in focus.
   1153     if (hasConsole && (GetConsoleWindow() == GetForegroundWindow() || SetFocus(GetConsoleWindow()) != nullptr))
   1154     {
   1155         INPUT ip;
   1156         // Set up a generic keyboard event.
   1157         ip.type = INPUT_KEYBOARD;
   1158         ip.ki.wScan = 0; // hardware scan code for key
   1159         ip.ki.time = 0;
   1160         ip.ki.dwExtraInfo = 0;
   1161 
   1162         // Send the "Enter" key
   1163         ip.ki.wVk = 0x0D; // virtual-key code for the "Enter" key
   1164         ip.ki.dwFlags = 0; // 0 for key press
   1165         SendInput(1, &ip, sizeof(INPUT));
   1166 
   1167         // Release the "Enter" key
   1168         ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
   1169         SendInput(1, &ip, sizeof(INPUT));
   1170     }
   1171    #endif
   1172 
   1173     return 0;
   1174 }
   1175 
   1176 // -----------------------------------------------------------------------