comparison DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoPluginJACK.cpp @ 3:84e66ea83026

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