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