DPF

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

DistrhoUIDSSI.cpp (13879B)


      1 /*
      2  * DISTRHO Plugin Framework (DPF)
      3  * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
      4  *
      5  * Permission to use, copy, modify, and/or distribute this software for any purpose with
      6  * or without fee is hereby granted, provided that the above copyright notice and this
      7  * permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
     10  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
     11  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
     12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
     13  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
     14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include "DistrhoUIInternal.hpp"
     18 
     19 #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
     20 # error DSSI UIs do not support direct access!
     21 #endif
     22 
     23 #include "../extra/Sleep.hpp"
     24 
     25 #include <lo/lo.h>
     26 
     27 START_NAMESPACE_DISTRHO
     28 
     29 // --------------------------------------------------------------------------------------------------------------------
     30 
     31 #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT
     32 static constexpr const sendNoteFunc sendNoteCallback = nullptr;
     33 #endif
     34 
     35 // unused in DSSI, we only use external and standalone UIs
     36 static constexpr const setSizeFunc setSizeCallback = nullptr;
     37 
     38 // unsupported in DSSI
     39 static constexpr const fileRequestFunc fileRequestCallback = nullptr;
     40 
     41 #ifdef DPF_USING_LD_LINUX_WEBVIEW
     42 int dpf_webview_start(int argc, char* argv[]);
     43 #endif
     44 
     45 // --------------------------------------------------------------------------------------------------------------------
     46 
     47 
     48 struct OscData {
     49     lo_address  addr;
     50     const char* path;
     51     lo_server   server;
     52 
     53     OscData()
     54         : addr(nullptr),
     55           path(nullptr),
     56           server(nullptr) {}
     57 
     58     void idle() const
     59     {
     60         if (server == nullptr)
     61             return;
     62 
     63         while (lo_server_recv_noblock(server, 0) != 0) {}
     64     }
     65 
     66     void send_configure(const char* const key, const char* const value) const
     67     {
     68         char targetPath[std::strlen(path)+11];
     69         std::strcpy(targetPath, path);
     70         std::strcat(targetPath, "/configure");
     71         lo_send(addr, targetPath, "ss", key, value);
     72     }
     73 
     74     void send_control(const int32_t index, const float value) const
     75     {
     76         char targetPath[std::strlen(path)+9];
     77         std::strcpy(targetPath, path);
     78         std::strcat(targetPath, "/control");
     79         lo_send(addr, targetPath, "if", index, value);
     80     }
     81 
     82     void send_midi(uchar data[4]) const
     83     {
     84         char targetPath[std::strlen(path)+6];
     85         std::strcpy(targetPath, path);
     86         std::strcat(targetPath, "/midi");
     87         lo_send(addr, targetPath, "m", data);
     88     }
     89 
     90     void send_update(const char* const url) const
     91     {
     92         char targetPath[std::strlen(path)+8];
     93         std::strcpy(targetPath, path);
     94         std::strcat(targetPath, "/update");
     95         lo_send(addr, targetPath, "s", url);
     96     }
     97 
     98     void send_exiting() const
     99     {
    100         char targetPath[std::strlen(path)+9];
    101         std::strcpy(targetPath, path);
    102         std::strcat(targetPath, "/exiting");
    103         lo_send(addr, targetPath, "");
    104     }
    105 };
    106 
    107 // -----------------------------------------------------------------------
    108 
    109 class UIDssi : public DGL_NAMESPACE::IdleCallback
    110 {
    111 public:
    112     UIDssi(const OscData& oscData, const char* const uiTitle, const double sampleRate)
    113         : fUI(this, 0, sampleRate, nullptr,
    114               setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, fileRequestCallback),
    115           fHostClosed(false),
    116           fOscData(oscData)
    117     {
    118         fUI.setWindowTitle(uiTitle);
    119     }
    120 
    121     ~UIDssi()
    122     {
    123         if (fOscData.server != nullptr && ! fHostClosed)
    124             fOscData.send_exiting();
    125     }
    126 
    127     void exec_start()
    128     {
    129         fUI.exec(this);
    130     }
    131 
    132     void idleCallback() override
    133     {
    134         fOscData.idle();
    135 
    136         if (fHostClosed)
    137             return;
    138 
    139         fUI.exec_idle();
    140     }
    141 
    142     // -------------------------------------------------------------------
    143 
    144 #if DISTRHO_PLUGIN_WANT_STATE
    145     void dssiui_configure(const char* key, const char* value)
    146     {
    147         fUI.stateChanged(key, value);
    148     }
    149 #endif
    150 
    151     void dssiui_control(ulong index, float value)
    152     {
    153         fUI.parameterChanged(index, value);
    154     }
    155 
    156 #if DISTRHO_PLUGIN_WANT_PROGRAMS
    157     void dssiui_program(ulong bank, ulong program)
    158     {
    159         fUI.programLoaded(bank * 128 + program);
    160     }
    161 #endif
    162 
    163     void dssiui_samplerate(const double sampleRate)
    164     {
    165         fUI.setSampleRate(sampleRate, true);
    166     }
    167 
    168     void dssiui_show(const bool focus = false)
    169     {
    170         fUI.setWindowVisible(true);
    171 
    172         if (focus)
    173             fUI.focus();
    174     }
    175 
    176     void dssiui_hide()
    177     {
    178         fUI.setWindowVisible(false);
    179     }
    180 
    181     void dssiui_quit()
    182     {
    183         fHostClosed = true;
    184         fUI.quit();
    185     }
    186 
    187     // -------------------------------------------------------------------
    188 
    189 protected:
    190     void setParameterValue(const uint32_t rindex, const float value)
    191     {
    192         if (fOscData.server == nullptr)
    193             return;
    194 
    195         fOscData.send_control(rindex, value);
    196     }
    197 
    198     void setState(const char* const key, const char* const value)
    199     {
    200         if (fOscData.server == nullptr)
    201             return;
    202 
    203         fOscData.send_configure(key, value);
    204     }
    205 
    206 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    207     void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
    208     {
    209         if (fOscData.server == nullptr)
    210             return;
    211         if (channel > 0xF)
    212             return;
    213 
    214         uint8_t mdata[4] = {
    215             0,
    216             static_cast<uint8_t>(channel + (velocity != 0 ? 0x90 : 0x80)),
    217             note,
    218             velocity
    219         };
    220         fOscData.send_midi(mdata);
    221     }
    222 #endif
    223 
    224 private:
    225     UIExporter fUI;
    226     bool fHostClosed;
    227 
    228     const OscData& fOscData;
    229 
    230     // -------------------------------------------------------------------
    231     // Callbacks
    232 
    233     #define uiPtr ((UIDssi*)ptr)
    234 
    235     static void setParameterCallback(void* ptr, uint32_t rindex, float value)
    236     {
    237         uiPtr->setParameterValue(rindex, value);
    238     }
    239 
    240     static void setStateCallback(void* ptr, const char* key, const char* value)
    241     {
    242         uiPtr->setState(key, value);
    243     }
    244 
    245 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
    246     static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity)
    247     {
    248         uiPtr->sendNote(channel, note, velocity);
    249     }
    250 #endif
    251 
    252     #undef uiPtr
    253 };
    254 
    255 // -----------------------------------------------------------------------
    256 
    257 static OscData     gOscData;
    258 static const char* gUiTitle = nullptr;
    259 static UIDssi*     globalUI = nullptr;
    260 static double      sampleRate = 0.0;
    261 
    262 static void initUiIfNeeded()
    263 {
    264     if (globalUI != nullptr)
    265         return;
    266 
    267     if (sampleRate == 0.0)
    268         sampleRate = 44100.0;
    269 
    270     globalUI = new UIDssi(gOscData, gUiTitle, sampleRate);
    271 }
    272 
    273 // -----------------------------------------------------------------------
    274 
    275 int osc_debug_handler(const char* path, const char*, lo_arg**, int, lo_message, void*)
    276 {
    277     d_debug("osc_debug_handler(\"%s\")", path);
    278     return 0;
    279 
    280 #ifndef DEBUG
    281     // unused
    282     (void)path;
    283 #endif
    284 }
    285 
    286 void osc_error_handler(int num, const char* msg, const char* path)
    287 {
    288     d_stderr("osc_error_handler(%i, \"%s\", \"%s\")", num, msg, path);
    289 }
    290 
    291 #if DISTRHO_PLUGIN_WANT_STATE
    292 int osc_configure_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
    293 {
    294     const char* const key   = &argv[0]->s;
    295     const char* const value = &argv[1]->s;
    296     d_debug("osc_configure_handler(\"%s\", \"%s\")", key, value);
    297 
    298     initUiIfNeeded();
    299 
    300     globalUI->dssiui_configure(key, value);
    301 
    302     return 0;
    303 }
    304 #endif
    305 
    306 int osc_control_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
    307 {
    308     const int32_t rindex = argv[0]->i;
    309     const float   value  = argv[1]->f;
    310     d_debug("osc_control_handler(%i, %f)", rindex, value);
    311 
    312     int32_t index = rindex - DISTRHO_PLUGIN_NUM_INPUTS - DISTRHO_PLUGIN_NUM_OUTPUTS;
    313 
    314     // latency
    315 #if DISTRHO_PLUGIN_WANT_LATENCY
    316     index -= 1;
    317 #endif
    318 
    319     if (index < 0)
    320         return 0;
    321 
    322     initUiIfNeeded();
    323 
    324     globalUI->dssiui_control(index, value);
    325 
    326     return 0;
    327 }
    328 
    329 #if DISTRHO_PLUGIN_WANT_PROGRAMS
    330 int osc_program_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
    331 {
    332     const int32_t bank    = argv[0]->i;
    333     const int32_t program = argv[1]->f;
    334     d_debug("osc_program_handler(%i, %i)", bank, program);
    335 
    336     initUiIfNeeded();
    337 
    338     globalUI->dssiui_program(bank, program);
    339 
    340     return 0;
    341 }
    342 #endif
    343 
    344 int osc_sample_rate_handler(const char*, const char*, lo_arg** argv, int, lo_message, void*)
    345 {
    346     sampleRate = argv[0]->i;
    347     d_debug("osc_sample_rate_handler(%f)", sampleRate);
    348 
    349     if (globalUI != nullptr)
    350         globalUI->dssiui_samplerate(sampleRate);
    351 
    352     return 0;
    353 }
    354 
    355 int osc_show_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
    356 {
    357     d_debug("osc_show_handler()");
    358 
    359     initUiIfNeeded();
    360 
    361     globalUI->dssiui_show();
    362 
    363     return 0;
    364 }
    365 
    366 int osc_hide_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
    367 {
    368     d_debug("osc_hide_handler()");
    369 
    370     if (globalUI != nullptr)
    371         globalUI->dssiui_hide();
    372 
    373     return 0;
    374 }
    375 
    376 int osc_quit_handler(const char*, const char*, lo_arg**, int, lo_message, void*)
    377 {
    378     d_debug("osc_quit_handler()");
    379 
    380     if (globalUI != nullptr)
    381         globalUI->dssiui_quit();
    382 
    383     return 0;
    384 }
    385 
    386 END_NAMESPACE_DISTRHO
    387 
    388 // -----------------------------------------------------------------------
    389 
    390 int main(int argc, char* argv[])
    391 {
    392     USE_NAMESPACE_DISTRHO
    393 
    394    #ifdef DPF_USING_LD_LINUX_WEBVIEW
    395     if (argc >= 2 && std::strcmp(argv[1], "dpf-ld-linux-webview") == 0)
    396         return dpf_webview_start(argc - 1, argv + 1);
    397    #endif
    398 
    399     // dummy test mode
    400     if (argc == 1)
    401     {
    402         gUiTitle = "DSSI UI Test";
    403 
    404         initUiIfNeeded();
    405         globalUI->dssiui_show(true);
    406         globalUI->exec_start();
    407 
    408         delete globalUI;
    409         globalUI = nullptr;
    410 
    411         return 0;
    412     }
    413 
    414     if (argc != 5)
    415     {
    416         d_stderr("Usage: %s <osc-url> <plugin-dll> <plugin-label> <instance-name>", argv[0]);
    417         return 1;
    418     }
    419 
    420     const char* oscUrl  = argv[1];
    421     const char* uiTitle = argv[4];
    422 
    423     char* const oscHost = lo_url_get_hostname(oscUrl);
    424     char* const oscPort = lo_url_get_port(oscUrl);
    425     char* const oscPath = lo_url_get_path(oscUrl);
    426     size_t  oscPathSize = strlen(oscPath);
    427     lo_address  oscAddr = lo_address_new(oscHost, oscPort);
    428     lo_server oscServer = lo_server_new_with_proto(nullptr, LO_UDP, osc_error_handler);
    429 
    430     char* const oscServerPath = lo_server_get_url(oscServer);
    431 
    432     char pluginPath[strlen(oscServerPath)+oscPathSize];
    433     strcpy(pluginPath, oscServerPath);
    434     strcat(pluginPath, oscPath+1);
    435 
    436 #if DISTRHO_PLUGIN_WANT_STATE
    437     char oscPathConfigure[oscPathSize+11];
    438     strcpy(oscPathConfigure, oscPath);
    439     strcat(oscPathConfigure, "/configure");
    440     lo_server_add_method(oscServer, oscPathConfigure, "ss", osc_configure_handler, nullptr);
    441 #endif
    442 
    443     char oscPathControl[oscPathSize+9];
    444     strcpy(oscPathControl, oscPath);
    445     strcat(oscPathControl, "/control");
    446     lo_server_add_method(oscServer, oscPathControl, "if", osc_control_handler, nullptr);
    447 
    448     d_stdout("oscServerPath:  \"%s\"", oscServerPath);
    449     d_stdout("pluginPath:     \"%s\"", pluginPath);
    450     d_stdout("oscPathControl: \"%s\"", oscPathControl);
    451 
    452 #if DISTRHO_PLUGIN_WANT_PROGRAMS
    453     char oscPathProgram[oscPathSize+9];
    454     strcpy(oscPathProgram, oscPath);
    455     strcat(oscPathProgram, "/program");
    456     lo_server_add_method(oscServer, oscPathProgram, "ii", osc_program_handler, nullptr);
    457 #endif
    458 
    459     char oscPathSampleRate[oscPathSize+13];
    460     strcpy(oscPathSampleRate, oscPath);
    461     strcat(oscPathSampleRate, "/sample-rate");
    462     lo_server_add_method(oscServer, oscPathSampleRate, "i", osc_sample_rate_handler, nullptr);
    463 
    464     char oscPathShow[oscPathSize+6];
    465     strcpy(oscPathShow, oscPath);
    466     strcat(oscPathShow, "/show");
    467     lo_server_add_method(oscServer, oscPathShow, "", osc_show_handler, nullptr);
    468 
    469     char oscPathHide[oscPathSize+6];
    470     strcpy(oscPathHide, oscPath);
    471     strcat(oscPathHide, "/hide");
    472     lo_server_add_method(oscServer, oscPathHide, "", osc_hide_handler, nullptr);
    473 
    474     char oscPathQuit[oscPathSize+6];
    475     strcpy(oscPathQuit, oscPath);
    476     strcat(oscPathQuit, "/quit");
    477     lo_server_add_method(oscServer, oscPathQuit, "", osc_quit_handler, nullptr);
    478 
    479     lo_server_add_method(oscServer, nullptr, nullptr, osc_debug_handler, nullptr);
    480 
    481     gUiTitle = uiTitle;
    482 
    483     gOscData.addr   = oscAddr;
    484     gOscData.path   = oscPath;
    485     gOscData.server = oscServer;
    486     gOscData.send_update(pluginPath);
    487 
    488     // wait for init
    489     for (int i=0; i < 100; ++i)
    490     {
    491         lo_server_recv(oscServer);
    492 
    493         if (sampleRate != 0.0 || globalUI != nullptr)
    494             break;
    495 
    496         d_msleep(50);
    497     }
    498 
    499     int ret = 1;
    500 
    501     if (sampleRate != 0.0 || globalUI != nullptr)
    502     {
    503         initUiIfNeeded();
    504 
    505         globalUI->exec_start();
    506 
    507         delete globalUI;
    508         globalUI = nullptr;
    509 
    510         ret = 0;
    511     }
    512 
    513 #if DISTRHO_PLUGIN_WANT_STATE
    514     lo_server_del_method(oscServer, oscPathConfigure, "ss");
    515 #endif
    516     lo_server_del_method(oscServer, oscPathControl, "if");
    517 #if DISTRHO_PLUGIN_WANT_PROGRAMS
    518     lo_server_del_method(oscServer, oscPathProgram, "ii");
    519 #endif
    520     lo_server_del_method(oscServer, oscPathSampleRate, "i");
    521     lo_server_del_method(oscServer, oscPathShow, "");
    522     lo_server_del_method(oscServer, oscPathHide, "");
    523     lo_server_del_method(oscServer, oscPathQuit, "");
    524     lo_server_del_method(oscServer, nullptr, nullptr);
    525 
    526     std::free(oscServerPath);
    527     std::free(oscHost);
    528     std::free(oscPort);
    529     std::free(oscPath);
    530 
    531     lo_address_free(oscAddr);
    532     lo_server_free(oscServer);
    533 
    534     return ret;
    535 }