1 /*
2  * DISTRHO Plugin Framework (DPF)
3  * Copyright (C) 2012-2020 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 #if DISTRHO_PLUGIN_HAS_UI
20 # include "DistrhoUIInternal.hpp"
21 #else
22 # include "../extra/Sleep.hpp"
23 #endif
24 
25 #include "jack/jack.h"
26 #include "jack/midiport.h"
27 #include "jack/transport.h"
28 
29 #ifndef DISTRHO_OS_WINDOWS
30 # include <signal.h>
31 #endif
32 
33 // -----------------------------------------------------------------------
34 
35 START_NAMESPACE_DISTRHO
36 
37 #if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_STATE
38 static const setStateFunc setStateCallback = nullptr;
39 #endif
40 #if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
41 static const writeMidiFunc writeMidiCallback = nullptr;
42 #endif
43 
44 // -----------------------------------------------------------------------
45 
46 static volatile bool gCloseSignalReceived = false;
47 
48 #ifdef DISTRHO_OS_WINDOWS
winSignalHandler(DWORD dwCtrlType)49 static BOOL WINAPI winSignalHandler(DWORD dwCtrlType) noexcept
50 {
51     if (dwCtrlType == CTRL_C_EVENT)
52     {
53         gCloseSignalReceived = true;
54         return TRUE;
55     }
56     return FALSE;
57 }
58 
initSignalHandler()59 static void initSignalHandler()
60 {
61     SetConsoleCtrlHandler(winSignalHandler, TRUE);
62 }
63 #else
closeSignalHandler(int)64 static void closeSignalHandler(int) noexcept
65 {
66     gCloseSignalReceived = true;
67 }
68 
initSignalHandler()69 static void initSignalHandler()
70 {
71     struct sigaction sig;
72     memset(&sig, 0, sizeof(sig));
73 
74     sig.sa_handler = closeSignalHandler;
75     sig.sa_flags   = SA_RESTART;
76     sigemptyset(&sig.sa_mask);
77     sigaction(SIGINT, &sig, nullptr);
78     sigaction(SIGTERM, &sig, nullptr);
79 }
80 #endif
81 
82 // -----------------------------------------------------------------------
83 
84 #if DISTRHO_PLUGIN_HAS_UI
85 // TODO
getDesktopScaleFactor()86 static double getDesktopScaleFactor() noexcept
87 {
88     return 1.0;
89 }
90 #endif
91 
92 // -----------------------------------------------------------------------
93 
94 #if DISTRHO_PLUGIN_HAS_UI
95 class PluginJack : public IdleCallback
96 #else
97 class PluginJack
98 #endif
99 {
100 public:
PluginJack(jack_client_t * const client)101     PluginJack(jack_client_t* const client)
102         : fPlugin(this, writeMidiCallback),
103 #if DISTRHO_PLUGIN_HAS_UI
104           fUI(this, 0,
105               nullptr, // edit param
106               setParameterValueCallback,
107               setStateCallback,
108               nullptr, // send note
109               setSizeCallback,
110               nullptr, // file request
111               nullptr, // bundle
112               fPlugin.getInstancePointer(),
113               getDesktopScaleFactor()),
114 #endif
115           fClient(client)
116     {
117 #if DISTRHO_PLUGIN_NUM_INPUTS > 0 || DISTRHO_PLUGIN_NUM_OUTPUTS > 0
118         char strBuf[0xff+1];
119         strBuf[0xff] = '\0';
120 
121 # if DISTRHO_PLUGIN_NUM_INPUTS > 0
122         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i)
123         {
124             std::snprintf(strBuf, 0xff, "in%i", i+1);
125             fPortAudioIns[i] = jack_port_register(fClient, strBuf, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
126         }
127 # endif
128 # if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
129         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
130         {
131             std::snprintf(strBuf, 0xff, "out%i", i+1);
132             fPortAudioOuts[i] = jack_port_register(fClient, strBuf, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
133         }
134 # endif
135 #endif
136 
137         fPortEventsIn = jack_port_register(fClient, "events-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0);
138 
139 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
140         fPortMidiOut = jack_port_register(fClient, "midi-out", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0);
141         fPortMidiOutBuffer = nullptr;
142 #endif
143 
144 #if DISTRHO_PLUGIN_WANT_PROGRAMS
145         if (fPlugin.getProgramCount() > 0)
146         {
147             fPlugin.loadProgram(0);
148 # if DISTRHO_PLUGIN_HAS_UI
149             fUI.programLoaded(0);
150 # endif
151         }
152 # if DISTRHO_PLUGIN_HAS_UI
153         fProgramChanged = -1;
154 # endif
155 #endif
156 
157         if (const uint32_t count = fPlugin.getParameterCount())
158         {
159             fLastOutputValues = new float[count];
160             std::memset(fLastOutputValues, 0, sizeof(float)*count);
161 
162 #if DISTRHO_PLUGIN_HAS_UI
163             fParametersChanged = new bool[count];
164             std::memset(fParametersChanged, 0, sizeof(bool)*count);
165 #endif
166 
167             for (uint32_t i=0; i < count; ++i)
168             {
169 #if DISTRHO_PLUGIN_HAS_UI
170                 if (! fPlugin.isParameterOutput(i))
171                     fUI.parameterChanged(i, fPlugin.getParameterValue(i));
172 #endif
173             }
174         }
175         else
176         {
177             fLastOutputValues = nullptr;
178 #if DISTRHO_PLUGIN_HAS_UI
179             fParametersChanged = nullptr;
180 #endif
181         }
182 
183         jack_set_buffer_size_callback(fClient, jackBufferSizeCallback, this);
184         jack_set_sample_rate_callback(fClient, jackSampleRateCallback, this);
185         jack_set_process_callback(fClient, jackProcessCallback, this);
186         jack_on_shutdown(fClient, jackShutdownCallback, this);
187 
188         fPlugin.activate();
189 
190         jack_activate(fClient);
191 
192 #if DISTRHO_PLUGIN_HAS_UI
193         if (const char* const name = jack_get_client_name(fClient))
194             fUI.setWindowTitle(name);
195         else
196             fUI.setWindowTitle(fPlugin.getName());
197 
198         fUI.exec(this);
199 #else
200         while (! gCloseSignalReceived)
201             d_sleep(1);
202 #endif
203     }
204 
~PluginJack()205     ~PluginJack()
206     {
207         if (fClient != nullptr)
208             jack_deactivate(fClient);
209 
210         if (fLastOutputValues != nullptr)
211         {
212             delete[] fLastOutputValues;
213             fLastOutputValues = nullptr;
214         }
215 
216 #if DISTRHO_PLUGIN_HAS_UI
217         if (fParametersChanged != nullptr)
218         {
219             delete[] fParametersChanged;
220             fParametersChanged = nullptr;
221         }
222 #endif
223 
224         fPlugin.deactivate();
225 
226         if (fClient == nullptr)
227             return;
228 
229 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
230         jack_port_unregister(fClient, fPortMidiOut);
231         fPortMidiOut = nullptr;
232 #endif
233 
234         jack_port_unregister(fClient, fPortEventsIn);
235         fPortEventsIn = nullptr;
236 
237 #if DISTRHO_PLUGIN_NUM_INPUTS > 0
238         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i)
239         {
240             jack_port_unregister(fClient, fPortAudioIns[i]);
241             fPortAudioIns[i] = nullptr;
242         }
243 #endif
244 
245 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
246         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
247         {
248             jack_port_unregister(fClient, fPortAudioOuts[i]);
249             fPortAudioOuts[i] = nullptr;
250         }
251 #endif
252 
253         jack_client_close(fClient);
254     }
255 
256     // -------------------------------------------------------------------
257 
258 protected:
259 #if DISTRHO_PLUGIN_HAS_UI
idleCallback()260     void idleCallback() override
261     {
262         if (gCloseSignalReceived)
263             return fUI.quit();
264 
265 # if DISTRHO_PLUGIN_WANT_PROGRAMS
266         if (fProgramChanged >= 0)
267         {
268             fUI.programLoaded(fProgramChanged);
269             fProgramChanged = -1;
270         }
271 # endif
272 
273         for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i)
274         {
275             if (fPlugin.isParameterOutput(i))
276             {
277                 const float value = fPlugin.getParameterValue(i);
278 
279                 if (d_isEqual(fLastOutputValues[i], value))
280                     continue;
281 
282                 fLastOutputValues[i] = value;
283                 fUI.parameterChanged(i, value);
284             }
285             else if (fParametersChanged[i])
286             {
287                 fParametersChanged[i] = false;
288                 fUI.parameterChanged(i, fPlugin.getParameterValue(i));
289             }
290         }
291 
292         fUI.exec_idle();
293     }
294 #endif
295 
jackBufferSize(const jack_nframes_t nframes)296     void jackBufferSize(const jack_nframes_t nframes)
297     {
298         fPlugin.setBufferSize(nframes, true);
299     }
300 
jackSampleRate(const jack_nframes_t nframes)301     void jackSampleRate(const jack_nframes_t nframes)
302     {
303         fPlugin.setSampleRate(nframes, true);
304     }
305 
jackProcess(const jack_nframes_t nframes)306     void jackProcess(const jack_nframes_t nframes)
307     {
308 #if DISTRHO_PLUGIN_NUM_INPUTS > 0
309         const float* audioIns[DISTRHO_PLUGIN_NUM_INPUTS];
310 
311         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i)
312             audioIns[i] = (const float*)jack_port_get_buffer(fPortAudioIns[i], nframes);
313 #else
314         static const float** audioIns = nullptr;
315 #endif
316 
317 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
318         float* audioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS];
319 
320         for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i)
321             audioOuts[i] = (float*)jack_port_get_buffer(fPortAudioOuts[i], nframes);
322 #else
323         static float** audioOuts = nullptr;
324 #endif
325 
326 #if DISTRHO_PLUGIN_WANT_TIMEPOS
327         jack_position_t pos;
328         fTimePosition.playing = (jack_transport_query(fClient, &pos) == JackTransportRolling);
329 
330         if (pos.unique_1 == pos.unique_2)
331         {
332             fTimePosition.frame = pos.frame;
333 
334             if (pos.valid & JackTransportBBT)
335             {
336                 fTimePosition.bbt.valid = true;
337 
338                 fTimePosition.bbt.bar  = pos.bar;
339                 fTimePosition.bbt.beat = pos.beat;
340                 fTimePosition.bbt.tick = pos.tick;
341                 fTimePosition.bbt.barStartTick = pos.bar_start_tick;
342 
343                 fTimePosition.bbt.beatsPerBar = pos.beats_per_bar;
344                 fTimePosition.bbt.beatType    = pos.beat_type;
345 
346                 fTimePosition.bbt.ticksPerBeat   = pos.ticks_per_beat;
347                 fTimePosition.bbt.beatsPerMinute = pos.beats_per_minute;
348             }
349             else
350                 fTimePosition.bbt.valid = false;
351         }
352         else
353         {
354             fTimePosition.bbt.valid = false;
355             fTimePosition.frame = 0;
356         }
357 
358         fPlugin.setTimePosition(fTimePosition);
359 #endif
360 
361         void* const midiBuf = jack_port_get_buffer(fPortEventsIn, nframes);
362 
363 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
364         fPortMidiOutBuffer = jack_port_get_buffer(fPortMidiOut, nframes);
365         jack_midi_clear_buffer(fPortMidiOutBuffer);
366 #endif
367 
368         if (const uint32_t eventCount = jack_midi_get_event_count(midiBuf))
369         {
370 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
371             uint32_t  midiEventCount = 0;
372             MidiEvent midiEvents[eventCount];
373 #endif
374             jack_midi_event_t jevent;
375 
376             for (uint32_t i=0; i < eventCount; ++i)
377             {
378                 if (jack_midi_event_get(&jevent, midiBuf, i) != 0)
379                     break;
380 
381                 // Check if message is control change on channel 1
382                 if (jevent.buffer[0] == 0xB0 && jevent.size == 3)
383                 {
384                     const uint8_t control = jevent.buffer[1];
385                     const uint8_t value   = jevent.buffer[2];
386 
387                     /* NOTE: This is not optimal, we're iterating all parameters on every CC message.
388                              Since the JACK standalone is more of a test tool, this will do for now. */
389                     for (uint32_t j=0, paramCount=fPlugin.getParameterCount(); j < paramCount; ++j)
390                     {
391                         if (fPlugin.isParameterOutput(j))
392                             continue;
393                         if (fPlugin.getParameterMidiCC(j) != control)
394                             continue;
395 
396                         const float scaled = static_cast<float>(value)/127.0f;
397                         const float fvalue = fPlugin.getParameterRanges(j).getUnnormalizedValue(scaled);
398                         fPlugin.setParameterValue(j, fvalue);
399 #if DISTRHO_PLUGIN_HAS_UI
400                         fParametersChanged[j] = true;
401 #endif
402                         break;
403                     }
404                 }
405 #if DISTRHO_PLUGIN_WANT_PROGRAMS
406                 // Check if message is program change on channel 1
407                 else if (jevent.buffer[0] == 0xC0 && jevent.size == 2)
408                 {
409                     const uint8_t program = jevent.buffer[1];
410 
411                     if (program < fPlugin.getProgramCount())
412                     {
413                         fPlugin.loadProgram(program);
414 # if DISTRHO_PLUGIN_HAS_UI
415                         fProgramChanged = program;
416 # endif
417                     }
418                 }
419 #endif
420 
421 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
422                 MidiEvent& midiEvent(midiEvents[midiEventCount++]);
423 
424                 midiEvent.frame = jevent.time;
425                 midiEvent.size  = jevent.size;
426 
427                 if (midiEvent.size > MidiEvent::kDataSize)
428                     midiEvent.dataExt = jevent.buffer;
429                 else
430                     std::memcpy(midiEvent.data, jevent.buffer, midiEvent.size);
431 #endif
432             }
433 
434 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
435             fPlugin.run(audioIns, audioOuts, nframes, midiEvents, midiEventCount);
436 #endif
437         }
438 #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
439         else
440         {
441             fPlugin.run(audioIns, audioOuts, nframes, nullptr, 0);
442         }
443 #else
444         fPlugin.run(audioIns, audioOuts, nframes);
445 #endif
446 
447 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
448         fPortMidiOutBuffer = nullptr;
449 #endif
450 
451         updateParameterTriggers();
452     }
453 
jackShutdown()454     void jackShutdown()
455     {
456         d_stderr("jack has shutdown, quitting now...");
457         fClient = nullptr;
458 #if DISTRHO_PLUGIN_HAS_UI
459         fUI.quit();
460 #endif
461     }
462 
463     // -------------------------------------------------------------------
464 
setParameterValue(const uint32_t index,const float value)465     void setParameterValue(const uint32_t index, const float value)
466     {
467         fPlugin.setParameterValue(index, value);
468     }
469 
470 #if DISTRHO_PLUGIN_WANT_STATE
setState(const char * const key,const char * const value)471     void setState(const char* const key, const char* const value)
472     {
473         fPlugin.setState(key, value);
474     }
475 #endif
476 
477 #if DISTRHO_PLUGIN_HAS_UI
setSize(const uint width,const uint height)478     void setSize(const uint width, const uint height)
479     {
480         fUI.setWindowSize(width, height);
481     }
482 #endif
483 
484 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
writeMidi(const MidiEvent & midiEvent)485     bool writeMidi(const MidiEvent& midiEvent)
486     {
487         DISTRHO_SAFE_ASSERT_RETURN(fPortMidiOutBuffer != nullptr, false);
488 
489         return jack_midi_event_write(fPortMidiOutBuffer,
490                                      midiEvent.frame,
491                                      midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data,
492                                      midiEvent.size) == 0;
493     }
494 #endif
495 
496     // NOTE: no trigger support for JACK, simulate it here
updateParameterTriggers()497     void updateParameterTriggers()
498     {
499         float defValue;
500 
501         for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i)
502         {
503             if ((fPlugin.getParameterHints(i) & kParameterIsTrigger) != kParameterIsTrigger)
504                 continue;
505 
506             defValue = fPlugin.getParameterRanges(i).def;
507 
508             if (d_isNotEqual(defValue, fPlugin.getParameterValue(i)))
509                 fPlugin.setParameterValue(i, defValue);
510         }
511     }
512 
513     // -------------------------------------------------------------------
514 
515 private:
516     PluginExporter fPlugin;
517 #if DISTRHO_PLUGIN_HAS_UI
518     UIExporter     fUI;
519 #endif
520 
521     jack_client_t* fClient;
522 
523 #if DISTRHO_PLUGIN_NUM_INPUTS > 0
524     jack_port_t* fPortAudioIns[DISTRHO_PLUGIN_NUM_INPUTS];
525 #endif
526 #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
527     jack_port_t* fPortAudioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS];
528 #endif
529     jack_port_t* fPortEventsIn;
530 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
531     jack_port_t* fPortMidiOut;
532     void*        fPortMidiOutBuffer;
533 #endif
534 #if DISTRHO_PLUGIN_WANT_TIMEPOS
535     TimePosition fTimePosition;
536 #endif
537 
538     // Temporary data
539     float* fLastOutputValues;
540 
541 #if DISTRHO_PLUGIN_HAS_UI
542     // Store DSP changes to send to UI
543     bool* fParametersChanged;
544 # if DISTRHO_PLUGIN_WANT_PROGRAMS
545     int fProgramChanged;
546 # endif
547 #endif
548 
549     // -------------------------------------------------------------------
550     // Callbacks
551 
552     #define thisPtr ((PluginJack*)ptr)
553 
jackBufferSizeCallback(jack_nframes_t nframes,void * ptr)554     static int jackBufferSizeCallback(jack_nframes_t nframes, void* ptr)
555     {
556         thisPtr->jackBufferSize(nframes);
557         return 0;
558     }
559 
jackSampleRateCallback(jack_nframes_t nframes,void * ptr)560     static int jackSampleRateCallback(jack_nframes_t nframes, void* ptr)
561     {
562         thisPtr->jackSampleRate(nframes);
563         return 0;
564     }
565 
jackProcessCallback(jack_nframes_t nframes,void * ptr)566     static int jackProcessCallback(jack_nframes_t nframes, void* ptr)
567     {
568         thisPtr->jackProcess(nframes);
569         return 0;
570     }
571 
jackShutdownCallback(void * ptr)572     static void jackShutdownCallback(void* ptr)
573     {
574         thisPtr->jackShutdown();
575     }
576 
setParameterValueCallback(void * ptr,uint32_t index,float value)577     static void setParameterValueCallback(void* ptr, uint32_t index, float value)
578     {
579         thisPtr->setParameterValue(index, value);
580     }
581 
582 #if DISTRHO_PLUGIN_WANT_STATE
setStateCallback(void * ptr,const char * key,const char * value)583     static void setStateCallback(void* ptr, const char* key, const char* value)
584     {
585         thisPtr->setState(key, value);
586     }
587 #endif
588 
589 #if DISTRHO_PLUGIN_HAS_UI
setSizeCallback(void * ptr,uint width,uint height)590     static void setSizeCallback(void* ptr, uint width, uint height)
591     {
592         thisPtr->setSize(width, height);
593     }
594 #endif
595 
596 #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
writeMidiCallback(void * ptr,const MidiEvent & midiEvent)597     static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent)
598     {
599         return thisPtr->writeMidi(midiEvent);
600     }
601 #endif
602 
603     #undef thisPtr
604 };
605 
606 END_NAMESPACE_DISTRHO
607 
608 // -----------------------------------------------------------------------
609 
main()610 int main()
611 {
612     USE_NAMESPACE_DISTRHO;
613 
614     jack_status_t  status = jack_status_t(0x0);
615     jack_client_t* client = jack_client_open(DISTRHO_PLUGIN_NAME, JackNoStartServer, &status);
616 
617     if (client == nullptr)
618     {
619         String errorString;
620 
621         if (status & JackFailure)
622             errorString += "Overall operation failed;\n";
623         if (status & JackInvalidOption)
624             errorString += "The operation contained an invalid or unsupported option;\n";
625         if (status & JackNameNotUnique)
626             errorString += "The desired client name was not unique;\n";
627         if (status & JackServerStarted)
628             errorString += "The JACK server was started as a result of this operation;\n";
629         if (status & JackServerFailed)
630             errorString += "Unable to connect to the JACK server;\n";
631         if (status & JackServerError)
632             errorString += "Communication error with the JACK server;\n";
633         if (status & JackNoSuchClient)
634             errorString += "Requested client does not exist;\n";
635         if (status & JackLoadFailure)
636             errorString += "Unable to load internal client;\n";
637         if (status & JackInitFailure)
638             errorString += "Unable to initialize client;\n";
639         if (status & JackShmFailure)
640             errorString += "Unable to access shared memory;\n";
641         if (status & JackVersionError)
642             errorString += "Client's protocol version does not match;\n";
643         if (status & JackBackendError)
644             errorString += "Backend Error;\n";
645         if (status & JackClientZombie)
646             errorString += "Client is being shutdown against its will;\n";
647 
648         if (errorString.isNotEmpty())
649         {
650             errorString[errorString.length()-2] = '.';
651             d_stderr("Failed to create jack client, reason was:\n%s", errorString.buffer());
652         }
653         else
654             d_stderr("Failed to create jack client, cannot continue!");
655 
656         return 1;
657     }
658 
659     USE_NAMESPACE_DISTRHO;
660 
661     initSignalHandler();
662 
663     d_lastBufferSize = jack_get_buffer_size(client);
664     d_lastSampleRate = jack_get_sample_rate(client);
665 #if DISTRHO_PLUGIN_HAS_UI
666     d_lastUiSampleRate = d_lastSampleRate;
667 #endif
668 
669     const PluginJack p(client);
670 
671     return 0;
672 }
673 
674 // -----------------------------------------------------------------------
675