2 * Carla LV2 Single Plugin
3 * Copyright (C) 2017-2022 Filipe Coelho <falktx@falktx.com>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of
8 * the License, or any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * For a full copy of the GNU General Public License see the doc/GPL.txt file.
19 # error This file should not be compiled if not building bridge
22 #include "engine/CarlaEngineInternal.hpp"
23 #include "CarlaPlugin.hpp"
25 #include "CarlaBackendUtils.hpp"
26 #include "CarlaEngineUtils.hpp"
27 #include "CarlaLv2Utils.hpp"
28 #include "CarlaUtils.h"
30 #include "water/files/File.h"
33 # include "carla_juce/carla_juce.h"
37 void Lv2PluginBaseClass
<CARLA_BACKEND_NAMESPACE::EngineTimeInfo
>::clearTimeData() noexcept
39 fLastPositionData
.clear();
43 // --------------------------------------------------------------------------------------------------------------------
45 CARLA_BACKEND_START_NAMESPACE
47 class CarlaEngineSingleLV2
: public CarlaEngine
,
48 public Lv2PluginBaseClass
<EngineTimeInfo
>
51 CarlaEngineSingleLV2(const double sampleRate
,
52 const char* const bundlePath
,
53 const LV2_Feature
* const* const features
)
54 : Lv2PluginBaseClass
<EngineTimeInfo
>(sampleRate
, features
),
60 CARLA_SAFE_ASSERT_RETURN(pData
->curPluginCount
== 0,)
61 CARLA_SAFE_ASSERT_RETURN(pData
->plugins
== nullptr,);
63 if (! loadedInProperHost())
67 CarlaString
binaryDir(bundlePath
);
68 binaryDir
+= CARLA_OS_SEP_STR
"bin" CARLA_OS_SEP_STR
;
70 CarlaString
resourceDir(bundlePath
);
71 resourceDir
+= CARLA_OS_SEP_STR
"res" CARLA_OS_SEP_STR
;
73 pData
->bufferSize
= fBufferSize
;
74 pData
->sampleRate
= sampleRate
;
75 pData
->initTime(nullptr);
77 pData
->options
.processMode
= ENGINE_PROCESS_MODE_BRIDGE
;
78 pData
->options
.transportMode
= ENGINE_TRANSPORT_MODE_PLUGIN
;
79 pData
->options
.forceStereo
= false;
80 pData
->options
.preferPluginBridges
= false;
81 pData
->options
.preferUiBridges
= false;
84 if (pData
->options
.resourceDir
!= nullptr)
85 delete[] pData
->options
.resourceDir
;
86 if (pData
->options
.binaryDir
!= nullptr)
87 delete[] pData
->options
.binaryDir
;
89 pData
->options
.binaryDir
= binaryDir
.dup();
90 pData
->options
.resourceDir
= resourceDir
.dup();
92 setCallback(_engine_callback
, this);
95 const File
pluginFile(File::getSpecialLocation(File::currentExecutableFile
).withFileExtension("xml"));
97 if (! loadProject(pluginFile
.getFullPathName().toRawUTF8(), true))
99 carla_stderr2("Failed to init plugin, possible reasons: %s", getLastError());
103 CARLA_SAFE_ASSERT_RETURN(pData
->curPluginCount
== 1,)
105 fPlugin
= pData
->plugins
[0].plugin
;
106 CARLA_SAFE_ASSERT_RETURN(fPlugin
.get() != nullptr,);
107 CARLA_SAFE_ASSERT_RETURN(fPlugin
->isEnabled(),);
109 fPorts
.hasUI
= false;
110 fPorts
.usesTime
= true;
111 fPorts
.numAudioIns
= fPlugin
->getAudioInCount();
112 fPorts
.numAudioOuts
= fPlugin
->getAudioOutCount();
113 fPorts
.numCVIns
= fPlugin
->getCVInCount();
114 fPorts
.numCVOuts
= fPlugin
->getCVOutCount();
115 fPorts
.numMidiIns
= fPlugin
->getMidiInCount();
116 fPorts
.numMidiOuts
= fPlugin
->getMidiOutCount();
117 fPorts
.numParams
= fPlugin
->getParameterCount();
121 for (uint32_t i
=0; i
< fPorts
.numParams
; ++i
)
123 fPorts
.paramsLast
[i
] = fPlugin
->getParameterValue(i
);
124 fPorts
.paramsOut
[i
] = fPlugin
->isParameterOutput(i
);
128 ~CarlaEngineSingleLV2()
130 if (fPlugin
.get() != nullptr && fIsActive
)
131 fPlugin
->setActive(false, false, false);
137 bool hasPlugin() noexcept
139 return fPlugin
.get() != nullptr;
142 // ----------------------------------------------------------------------------------------------------------------
145 void lv2_activate() noexcept
147 CARLA_SAFE_ASSERT_RETURN(! fIsActive
,);
151 fPlugin
->setActive(true, false, false);
155 void lv2_deactivate() noexcept
157 CARLA_SAFE_ASSERT_RETURN(fIsActive
,);
160 fPlugin
->setActive(false, false, false);
163 void lv2_run(const uint32_t frames
)
165 //const PendingRtEventsRunner prt(this, frames);
167 if (! lv2_pre_run(frames
))
169 updateParameterOutputs();
173 if (fPorts
.numMidiIns
> 0)
175 uint32_t engineEventIndex
= 0;
176 carla_zeroStructs(pData
->events
.in
, kMaxEngineEventInternalCount
);
178 for (uint32_t i
=0; i
< fPorts
.numMidiIns
; ++i
)
180 LV2_ATOM_SEQUENCE_FOREACH(fPorts
.eventsIn
[i
], event
)
182 if (event
== nullptr)
184 if (event
->body
.type
!= fURIs
.midiEvent
)
186 if (event
->body
.size
> 4)
188 if (event
->time
.frames
>= frames
)
191 const uint8_t* const data((const uint8_t*)(event
+ 1));
193 EngineEvent
& engineEvent(pData
->events
.in
[engineEventIndex
++]);
195 engineEvent
.time
= (uint32_t)event
->time
.frames
;
196 engineEvent
.fillFromMidiData((uint8_t)event
->body
.size
, data
, (uint8_t)i
);
198 if (engineEventIndex
>= kMaxEngineEventInternalCount
)
204 if (fPorts
.numMidiOuts
> 0)
206 carla_zeroStructs(pData
->events
.out
, kMaxEngineEventInternalCount
);
209 if (fPlugin
->tryLock(fIsOffline
))
211 fPlugin
->initBuffers();
212 fPlugin
->process(fPorts
.audioCVIns
,
214 fPorts
.audioCVIns
+ fPorts
.numAudioIns
,
215 fPorts
.audioCVOuts
+ fPorts
.numAudioOuts
,
219 if (fPorts
.numMidiOuts
> 0)
223 uint8_t mdata
[3] = { 0, 0, 0 };
224 uint8_t mdataTmp
[EngineMidiEvent::kDataSize
];
225 const uint8_t* mdataPtr
;
227 for (ushort i
=0; i
< kMaxEngineEventInternalCount
; ++i
)
229 const EngineEvent
& engineEvent(pData
->events
.out
[i
]);
231 /**/ if (engineEvent
.type
== kEngineEventTypeNull
)
235 else if (engineEvent
.type
== kEngineEventTypeControl
)
237 const EngineControlEvent
& ctrlEvent(engineEvent
.ctrl
);
239 size
= ctrlEvent
.convertToMidiData(engineEvent
.channel
, mdata
);
242 else if (engineEvent
.type
== kEngineEventTypeMidi
)
244 const EngineMidiEvent
& midiEvent(engineEvent
.midi
);
246 port
= midiEvent
.port
;
247 size
= midiEvent
.size
;
248 CARLA_SAFE_ASSERT_CONTINUE(size
> 0);
250 if (size
> EngineMidiEvent::kDataSize
)
252 CARLA_SAFE_ASSERT_CONTINUE(midiEvent
.dataExt
!= nullptr);
253 mdataPtr
= midiEvent
.dataExt
;
258 mdataTmp
[0] = static_cast<uint8_t>(midiEvent
.data
[0] | (engineEvent
.channel
& MIDI_CHANNEL_BIT
));
261 carla_copy
<uint8_t>(mdataTmp
+1, midiEvent
.data
+1, size
-1U);
272 if (size
> 0 && ! writeMidiEvent(port
, engineEvent
.time
, size
, mdataPtr
))
279 for (uint32_t i
=0; i
<fPorts
.numAudioOuts
; ++i
)
280 carla_zeroFloats(fPorts
.audioCVOuts
[i
], frames
);
281 for (uint32_t i
=0; i
<fPorts
.numCVOuts
; ++i
)
282 carla_zeroFloats(fPorts
.audioCVOuts
[fPorts
.numAudioOuts
+i
], frames
);
285 lv2_post_run(frames
);
286 updateParameterOutputs();
290 // ----------------------------------------------------------------------------------------------------------------
292 bool lv2ui_instantiate(LV2UI_Write_Function writeFunction
, LV2UI_Controller controller
,
293 LV2UI_Widget
* widget
, const LV2_Feature
* const* features
)
295 fUI
.writeFunction
= writeFunction
;
296 fUI
.controller
= controller
;
299 const LV2_URID_Map
* uridMap
= nullptr;
301 // ------------------------------------------------------------------------------------------------------------
302 // see if the host supports external-ui, get uridMap
304 for (int i
=0; features
[i
] != nullptr; ++i
)
306 if (std::strcmp(features
[i
]->URI
, LV2_EXTERNAL_UI__Host
) == 0 ||
307 std::strcmp(features
[i
]->URI
, LV2_EXTERNAL_UI_DEPRECATED_URI
) == 0)
309 fUI
.host
= (const LV2_External_UI_Host
*)features
[i
]->data
;
311 else if (std::strcmp(features
[i
]->URI
, LV2_URID__map
) == 0)
313 uridMap
= (const LV2_URID_Map
*)features
[i
]->data
;
317 if (fUI
.host
!= nullptr)
319 fPlugin
->setCustomUITitle(fUI
.host
->plugin_human_id
);
320 *widget
= (LV2_External_UI_Widget_Compat
*)this;
324 // ------------------------------------------------------------------------------------------------------------
325 // no external-ui support, use showInterface
327 const char* uiTitle
= nullptr;
329 for (int i
=0; features
[i
] != nullptr; ++i
)
331 if (std::strcmp(features
[i
]->URI
, LV2_OPTIONS__options
) == 0)
333 const LV2_Options_Option
* const options((const LV2_Options_Option
*)features
[i
]->data
);
335 for (int j
=0; options
[j
].key
!= 0; ++j
)
337 if (options
[j
].key
== uridMap
->map(uridMap
->handle
, LV2_UI__windowTitle
))
339 uiTitle
= (const char*)options
[j
].value
;
347 if (uiTitle
== nullptr)
348 uiTitle
= fPlugin
->getName();
350 fPlugin
->setCustomUITitle(uiTitle
);
355 void lv2ui_port_event(uint32_t portIndex
, uint32_t bufferSize
, uint32_t format
, const void* buffer
) const
357 if (format
!= 0 || bufferSize
!= sizeof(float) || buffer
== nullptr)
359 if (portIndex
>= fPorts
.indexOffset
|| ! fUI
.isVisible
)
362 const float value(*(const float*)buffer
);
363 fPlugin
->uiParameterChange(portIndex
-fPorts
.indexOffset
, value
);
367 // ----------------------------------------------------------------------------------------------------------------
368 // CarlaEngine virtual calls
370 bool init(const char* const clientName
) override
372 carla_stdout("CarlaEngineNative::init(\"%s\")", clientName
);
374 if (! pData
->init(clientName
))
377 setLastError("Failed to init internal data");
384 bool hasIdleOnMainThread() const noexcept override
389 bool isRunning() const noexcept override
394 bool isOffline() const noexcept override
399 bool usesConstantBufferSize() const noexcept override
404 EngineType
getType() const noexcept override
406 return kEngineTypePlugin
;
409 const char* getCurrentDriverName() const noexcept override
414 void engineCallback(const EngineCallbackOpcode action
, const uint pluginId
,
415 const int value1
, const int value2
, const int value3
,
416 const float valuef
, const char* const valueStr
)
420 case ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED
:
421 if (value1
== PARAMETER_ACTIVE
)
423 CARLA_SAFE_ASSERT_RETURN(value1
>= 0,);
424 if (fUI
.writeFunction
!= nullptr && fUI
.controller
!= nullptr && fUI
.isVisible
)
426 fUI
.writeFunction(fUI
.controller
,
427 static_cast<uint32_t>(value1
)+fPorts
.indexOffset
,
428 sizeof(float), 0, &valuef
);
432 case ENGINE_CALLBACK_UI_STATE_CHANGED
:
433 fUI
.isVisible
= (value1
== 1);
434 if (fUI
.host
!= nullptr)
435 fUI
.host
->ui_closed(fUI
.controller
);
438 case ENGINE_CALLBACK_IDLE
:
442 carla_stdout("engineCallback(%i:%s, %u, %i, %i, %f, %s)",
443 action
, EngineCallbackOpcode2Str(action
), pluginId
,
444 value1
, value2
, value3
,
445 static_cast<double>(valuef
), valueStr
);
450 // ----------------------------------------------------------------------------------------------------------------
452 void handleUiRun() const override
456 } CARLA_SAFE_EXCEPTION("fPlugin->uiIdle()")
459 void handleUiShow() override
461 fPlugin
->showCustomUI(true);
462 fUI
.isVisible
= true;
465 void handleUiHide() override
467 fUI
.isVisible
= false;
468 fPlugin
->showCustomUI(false);
471 // ----------------------------------------------------------------------------------------------------------------
473 void handleParameterValueChanged(const uint32_t index
, const float value
) override
475 fPlugin
->setParameterValue(index
, value
, false, false, false);
478 void handleBufferSizeChanged(const uint32_t bufferSize
) override
480 CarlaEngine::bufferSizeChanged(bufferSize
);
483 void handleSampleRateChanged(const double sampleRate
) override
485 CarlaEngine::sampleRateChanged(sampleRate
);
488 // ----------------------------------------------------------------------------------------------------------------
491 CarlaPluginPtr fPlugin
;
494 CarlaJUCE::ScopedJuceInitialiser_GUI fJuceInitialiser
;
497 void updateParameterOutputs() noexcept
501 for (uint32_t i
=0; i
< fPorts
.numParams
; ++i
)
503 if (! fPorts
.paramsOut
[i
])
506 fPorts
.paramsLast
[i
] = value
= fPlugin
->getParameterValue(i
);
508 if (fPorts
.paramsPtr
[i
] != nullptr)
509 *fPorts
.paramsPtr
[i
] = value
;
513 bool writeMidiEvent(const uint8_t port
, const uint32_t time
, const uint8_t midiSize
, const uint8_t* midiData
)
515 CARLA_SAFE_ASSERT_RETURN(fPorts
.numMidiOuts
> 0, false);
516 CARLA_SAFE_ASSERT_RETURN(port
< fPorts
.numMidiOuts
, false);
517 CARLA_SAFE_ASSERT_RETURN(midiData
!= nullptr, false);
518 CARLA_SAFE_ASSERT_RETURN(midiSize
> 0, false);
520 LV2_Atom_Sequence
* const seq(fPorts
.eventsOut
[port
]);
521 CARLA_SAFE_ASSERT_RETURN(seq
!= nullptr, false);
523 Ports::EventsOutData
& mData(fPorts
.eventsOutData
[port
]);
525 if (sizeof(LV2_Atom_Event
) + midiSize
> mData
.capacity
- mData
.offset
)
528 LV2_Atom_Event
* const aev
= (LV2_Atom_Event
*)(LV2_ATOM_CONTENTS(LV2_Atom_Sequence
, seq
) + mData
.offset
);
530 aev
->time
.frames
= time
;
531 aev
->body
.size
= midiSize
;
532 aev
->body
.type
= fURIs
.midiEvent
;
533 std::memcpy(LV2_ATOM_BODY(&aev
->body
), midiData
, midiSize
);
535 const uint32_t size
= lv2_atom_pad_size(static_cast<uint32_t>(sizeof(LV2_Atom_Event
) + midiSize
));
536 mData
.offset
+= size
;
537 seq
->atom
.size
+= size
;
542 // -------------------------------------------------------------------
544 #define handlePtr ((CarlaEngineSingleLV2*)handle)
546 static void _engine_callback(void* handle
, EngineCallbackOpcode action
, uint pluginId
,
547 int value1
, int value2
, int value3
,
548 float valuef
, const char* valueStr
)
550 handlePtr
->engineCallback(action
, pluginId
, value1
, value2
, value3
, valuef
, valueStr
);
555 CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineSingleLV2
)
558 CARLA_BACKEND_END_NAMESPACE
560 using CARLA_BACKEND_NAMESPACE::CarlaEngineSingleLV2
;
562 // --------------------------------------------------------------------------------------------------------------------
565 static LV2_Handle
lv2_instantiate(const LV2_Descriptor
* lv2Descriptor
, double sampleRate
, const char* bundlePath
, const LV2_Feature
* const* features
)
567 carla_stdout("lv2_instantiate(%p, %g, %s, %p)", lv2Descriptor
, sampleRate
, bundlePath
, features
);
569 CarlaEngineSingleLV2
* const instance(new CarlaEngineSingleLV2(sampleRate
, bundlePath
, features
));
571 if (instance
->hasPlugin())
572 return (LV2_Handle
)instance
;
578 #define instancePtr ((CarlaEngineSingleLV2*)instance)
580 static void lv2_connect_port(LV2_Handle instance
, uint32_t port
, void* dataLocation
)
582 instancePtr
->lv2_connect_port(port
, dataLocation
);
585 static void lv2_activate(LV2_Handle instance
)
587 carla_debug("lv2_activate(%p)", instance
);
588 instancePtr
->lv2_activate();
591 static void lv2_run(LV2_Handle instance
, uint32_t sampleCount
)
593 instancePtr
->lv2_run(sampleCount
);
596 static void lv2_deactivate(LV2_Handle instance
)
598 carla_debug("lv2_deactivate(%p)", instance
);
599 instancePtr
->lv2_deactivate();
602 static void lv2_cleanup(LV2_Handle instance
)
604 carla_debug("lv2_cleanup(%p)", instance
);
608 static const void* lv2_extension_data(const char* uri
)
610 carla_debug("lv2_extension_data(\"%s\")", uri
);
619 // --------------------------------------------------------------------------------------------------------------------
622 static LV2UI_Handle
lv2ui_instantiate(const LV2UI_Descriptor
*, const char*, const char*,
623 LV2UI_Write_Function writeFunction
, LV2UI_Controller controller
,
624 LV2UI_Widget
* widget
, const LV2_Feature
* const* features
)
626 carla_debug("lv2ui_instantiate(..., %p, %p, %p)", writeFunction
, controller
, widget
, features
);
628 CarlaEngineSingleLV2
* engine
= nullptr;
630 for (int i
=0; features
[i
] != nullptr; ++i
)
632 if (std::strcmp(features
[i
]->URI
, LV2_INSTANCE_ACCESS_URI
) == 0)
634 engine
= (CarlaEngineSingleLV2
*)features
[i
]->data
;
639 if (engine
== nullptr)
641 carla_stderr("Host doesn't support instance-access, cannot show UI");
645 if (! engine
->lv2ui_instantiate(writeFunction
, controller
, widget
, features
))
648 return (LV2UI_Handle
)engine
;
651 #define uiPtr ((CarlaEngineSingleLV2*)ui)
653 static void lv2ui_port_event(LV2UI_Handle ui
, uint32_t portIndex
, uint32_t bufferSize
, uint32_t format
, const void* buffer
)
655 uiPtr
->lv2ui_port_event(portIndex
, bufferSize
, format
, buffer
);
658 static void lv2ui_cleanup(LV2UI_Handle ui
)
660 carla_debug("lv2ui_cleanup(%p)", ui
);
661 uiPtr
->lv2ui_cleanup();
664 static int lv2ui_idle(LV2UI_Handle ui
)
666 return uiPtr
->lv2ui_idle();
669 static int lv2ui_show(LV2UI_Handle ui
)
671 carla_debug("lv2ui_show(%p)", ui
);
672 return uiPtr
->lv2ui_show();
675 static int lv2ui_hide(LV2UI_Handle ui
)
677 carla_debug("lv2ui_hide(%p)", ui
);
678 return uiPtr
->lv2ui_hide();
681 static const void* lv2ui_extension_data(const char* uri
)
683 carla_debug("lv2ui_extension_data(\"%s\")", uri
);
685 static const LV2UI_Idle_Interface uiidle
= { lv2ui_idle
};
686 static const LV2UI_Show_Interface uishow
= { lv2ui_show
, lv2ui_hide
};
688 if (std::strcmp(uri
, LV2_UI__idleInterface
) == 0)
690 if (std::strcmp(uri
, LV2_UI__showInterface
) == 0)
698 // --------------------------------------------------------------------------------------------------------------------
702 const LV2_Descriptor
* lv2_descriptor(uint32_t index
)
704 carla_debug("lv2_descriptor(%i)", index
);
709 static CarlaString ret
;
713 using namespace water
;
714 const File
file(File::getSpecialLocation(File::currentExecutableFile
).withFileExtension("ttl"));
716 ret
= String("file:///" + file
.getFullPathName()).toRawUTF8();
717 ret
.replace('\\','/');
719 ret
= String("file://" + file
.getFullPathName()).toRawUTF8();
723 carla_stdout("lv2_descriptor(%i) has URI '%s'", index
, ret
.buffer());
725 static const LV2_Descriptor desc
= {
726 /* URI */ ret
.buffer(),
727 /* instantiate */ lv2_instantiate
,
728 /* connect_port */ lv2_connect_port
,
729 /* activate */ lv2_activate
,
731 /* deactivate */ lv2_deactivate
,
732 /* cleanup */ lv2_cleanup
,
733 /* extension_data */ lv2_extension_data
740 const LV2UI_Descriptor
* lv2ui_descriptor(uint32_t index
)
742 carla_debug("lv2ui_descriptor(%i)", index
);
744 static CarlaString ret
;
747 using namespace water
;
748 const File
file(File::getSpecialLocation(File::currentExecutableFile
).getSiblingFile("ext-ui"));
750 ret
= String("file:///" + file
.getFullPathName()).toRawUTF8();
751 ret
.replace('\\','/');
753 ret
= String("file://" + file
.getFullPathName()).toRawUTF8();
757 carla_stdout("lv2ui_descriptor(%i) has URI '%s'", index
, ret
.buffer());
759 static const LV2UI_Descriptor lv2UiExtDesc
= {
760 /* URI */ ret
.buffer(),
761 /* instantiate */ lv2ui_instantiate
,
762 /* cleanup */ lv2ui_cleanup
,
763 /* port_event */ lv2ui_port_event
,
764 /* extension_data */ lv2ui_extension_data
767 return (index
== 0) ? &lv2UiExtDesc
: nullptr;
770 // --------------------------------------------------------------------------------------------------------------------