Mercurial > hg > pub > prymula > com
view DPF-Prymula-audioplugins/dpf/distrho/src/DistrhoPluginCLAP.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 |
line wrap: on
line source
/* * DISTRHO Plugin Framework (DPF) * Copyright (C) 2012-2023 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this * permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* TODO items: * CV: write a specification * INFO: define url, manual url, support url and string version * PARAMETERS: test parameter triggers * States: skip DSP/UI only states as appropriate * UI: expose external-only UIs */ #include "DistrhoPluginInternal.hpp" #include "extra/ScopedPointer.hpp" #ifndef DISTRHO_PLUGIN_CLAP_ID # error DISTRHO_PLUGIN_CLAP_ID undefined! #endif #if DISTRHO_PLUGIN_HAS_UI && ! defined(HAVE_DGL) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI # undef DISTRHO_PLUGIN_HAS_UI # define DISTRHO_PLUGIN_HAS_UI 0 #endif #if DISTRHO_PLUGIN_HAS_UI # include "DistrhoUIInternal.hpp" # include "../extra/Mutex.hpp" #endif #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT # include "../extra/RingBuffer.hpp" #endif #include <map> #include <vector> #include "clap/entry.h" #include "clap/plugin-factory.h" #include "clap/ext/audio-ports.h" #include "clap/ext/latency.h" #include "clap/ext/gui.h" #include "clap/ext/note-ports.h" #include "clap/ext/params.h" #include "clap/ext/state.h" #include "clap/ext/thread-check.h" #include "clap/ext/timer-support.h" #if (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI # define DPF_CLAP_USING_HOST_TIMER 0 #else # define DPF_CLAP_USING_HOST_TIMER 1 #endif #ifndef DPF_CLAP_TIMER_INTERVAL # define DPF_CLAP_TIMER_INTERVAL 16 /* ~60 fps */ #endif START_NAMESPACE_DISTRHO // -------------------------------------------------------------------------------------------------------------------- typedef std::map<const String, String> StringMap; struct ClapEventQueue { #if DISTRHO_PLUGIN_HAS_UI enum EventType { kEventGestureBegin, kEventGestureEnd, kEventParamSet }; struct Event { EventType type; uint32_t index; float value; }; struct Queue { RecursiveMutex lock; uint allocated; uint used; Event* events; Queue() : allocated(0), used(0), events(nullptr) {} ~Queue() { delete[] events; } void addEventFromUI(const Event& event) { const RecursiveMutexLocker crml(lock); if (events == nullptr) { events = static_cast<Event*>(std::malloc(sizeof(Event) * 8)); allocated = 8; } else if (used + 1 > allocated) { allocated = used * 2; events = static_cast<Event*>(std::realloc(events, sizeof(Event) * allocated)); } std::memcpy(&events[used++], &event, sizeof(Event)); } } fEventQueue; #if DISTRHO_PLUGIN_WANT_MIDI_INPUT SmallStackBuffer fNotesBuffer; #endif #endif #if DISTRHO_PLUGIN_WANT_PROGRAMS uint32_t fCurrentProgram; #endif #if DISTRHO_PLUGIN_WANT_STATE StringMap fStateMap; #if DISTRHO_PLUGIN_HAS_UI virtual void setStateFromUI(const char* key, const char* value) = 0; #endif #endif struct CachedParameters { uint numParams; bool* changed; float* values; CachedParameters() : numParams(0), changed(nullptr), values(nullptr) {} ~CachedParameters() { delete[] changed; delete[] values; } void setup(const uint numParameters) { if (numParameters == 0) return; numParams = numParameters; changed = new bool[numParameters]; values = new float[numParameters]; std::memset(changed, 0, sizeof(bool)*numParameters); std::memset(values, 0, sizeof(float)*numParameters); } } fCachedParameters; ClapEventQueue() #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT : fNotesBuffer(StackBuffer_INIT) #endif { #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT && ! defined(DISTRHO_PROPER_CPP11_SUPPORT) std::memset(&fNotesBuffer, 0, sizeof(fNotesBuffer)); #endif #if DISTRHO_PLUGIN_WANT_PROGRAMS fCurrentProgram = 0; #endif } virtual ~ClapEventQueue() {} }; // -------------------------------------------------------------------------------------------------------------------- #if DISTRHO_PLUGIN_HAS_UI #if ! DISTRHO_PLUGIN_WANT_STATE static constexpr const setStateFunc setStateCallback = nullptr; #endif #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT static constexpr const sendNoteFunc sendNoteCallback = nullptr; #endif /** * CLAP UI class. */ class ClapUI : public DGL_NAMESPACE::IdleCallback { public: ClapUI(PluginExporter& plugin, ClapEventQueue* const eventQueue, const clap_host_t* const host, const clap_host_gui_t* const hostGui, #if DPF_CLAP_USING_HOST_TIMER const clap_host_timer_support_t* const hostTimer, #endif const bool isFloating) : fPlugin(plugin), fPluginEventQueue(eventQueue), fEventQueue(eventQueue->fEventQueue), fCachedParameters(eventQueue->fCachedParameters), #if DISTRHO_PLUGIN_WANT_PROGRAMS fCurrentProgram(eventQueue->fCurrentProgram), #endif #if DISTRHO_PLUGIN_WANT_STATE fStateMap(eventQueue->fStateMap), #endif fHost(host), fHostGui(hostGui), #if DPF_CLAP_USING_HOST_TIMER fTimerId(0), fHostTimer(hostTimer), #else fCallbackRegistered(false), #endif fIsFloating(isFloating), fScaleFactor(0.0), fParentWindow(0), fTransientWindow(0) { #if DISTRHO_PLUGIN_WANT_MIDI_INPUT fNotesRingBuffer.setRingBuffer(&eventQueue->fNotesBuffer, false); #endif } ~ClapUI() override { #if DPF_CLAP_USING_HOST_TIMER if (fTimerId != 0) fHostTimer->unregister_timer(fHost, fTimerId); #else if (fCallbackRegistered && fUI != nullptr) fUI->removeIdleCallbackForNativeIdle(this); #endif } #ifndef DISTRHO_OS_MAC bool setScaleFactor(const double scaleFactor) { if (d_isEqual(fScaleFactor, scaleFactor)) return true; fScaleFactor = scaleFactor; if (UIExporter* const ui = fUI.get()) ui->notifyScaleFactorChanged(scaleFactor); return true; } #endif bool getSize(uint32_t* const width, uint32_t* const height) const { if (UIExporter* const ui = fUI.get()) { *width = ui->getWidth(); *height = ui->getHeight(); #ifdef DISTRHO_OS_MAC const double scaleFactor = ui->getScaleFactor(); *width /= scaleFactor; *height /= scaleFactor; #endif return true; } double scaleFactor = fScaleFactor; #if defined(DISTRHO_UI_DEFAULT_WIDTH) && defined(DISTRHO_UI_DEFAULT_HEIGHT) *width = DISTRHO_UI_DEFAULT_WIDTH; *height = DISTRHO_UI_DEFAULT_HEIGHT; if (d_isZero(scaleFactor)) scaleFactor = 1.0; #else UIExporter tmpUI(nullptr, 0, fPlugin.getSampleRate(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, d_nextBundlePath, fPlugin.getInstancePointer(), scaleFactor); *width = tmpUI.getWidth(); *height = tmpUI.getHeight(); scaleFactor = tmpUI.getScaleFactor(); tmpUI.quit(); #endif #ifdef DISTRHO_OS_MAC *width /= scaleFactor; *height /= scaleFactor; #endif return true; } bool canResize() const noexcept { #if DISTRHO_UI_USER_RESIZABLE if (UIExporter* const ui = fUI.get()) return ui->isResizable(); #endif return false; } bool getResizeHints(clap_gui_resize_hints_t* const hints) const { if (canResize()) { uint minimumWidth, minimumHeight; bool keepAspectRatio; fUI->getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio); #ifdef DISTRHO_OS_MAC const double scaleFactor = fUI->getScaleFactor(); minimumWidth /= scaleFactor; minimumHeight /= scaleFactor; #endif hints->can_resize_horizontally = true; hints->can_resize_vertically = true; hints->preserve_aspect_ratio = keepAspectRatio; hints->aspect_ratio_width = minimumWidth; hints->aspect_ratio_height = minimumHeight; return true; } hints->can_resize_horizontally = false; hints->can_resize_vertically = false; hints->preserve_aspect_ratio = false; hints->aspect_ratio_width = 0; hints->aspect_ratio_height = 0; return false; } bool adjustSize(uint32_t* const width, uint32_t* const height) const { if (canResize()) { uint minimumWidth, minimumHeight; bool keepAspectRatio; fUI->getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio); #ifdef DISTRHO_OS_MAC const double scaleFactor = fUI->getScaleFactor(); minimumWidth /= scaleFactor; minimumHeight /= scaleFactor; #endif if (keepAspectRatio) { if (*width < 1) *width = 1; if (*height < 1) *height = 1; const double ratio = static_cast<double>(minimumWidth) / static_cast<double>(minimumHeight); const double reqRatio = static_cast<double>(*width) / static_cast<double>(*height); if (d_isNotEqual(ratio, reqRatio)) { // fix width if (reqRatio > ratio) *width = static_cast<int32_t>(*height * ratio + 0.5); // fix height else *height = static_cast<int32_t>(static_cast<double>(*width) / ratio + 0.5); } } if (minimumWidth > *width) *width = minimumWidth; if (minimumHeight > *height) *height = minimumHeight; return true; } return false; } bool setSizeFromHost(uint32_t width, uint32_t height) { if (UIExporter* const ui = fUI.get()) { #ifdef DISTRHO_OS_MAC const double scaleFactor = ui->getScaleFactor(); width *= scaleFactor; height *= scaleFactor; #endif ui->setWindowSizeFromHost(width, height); return true; } return false; } bool setParent(const clap_window_t* const window) { if (fIsFloating) return false; fParentWindow = window->uptr; if (fUI == nullptr) { createUI(); fHostGui->resize_hints_changed(fHost); } return true; } bool setTransient(const clap_window_t* const window) { if (! fIsFloating) return false; fTransientWindow = window->uptr; if (UIExporter* const ui = fUI.get()) ui->setWindowTransientWinId(window->uptr); return true; } void suggestTitle(const char* const title) { if (! fIsFloating) return; fWindowTitle = title; if (UIExporter* const ui = fUI.get()) ui->setWindowTitle(title); } bool show() { if (fUI == nullptr) { createUI(); fHostGui->resize_hints_changed(fHost); } if (fIsFloating) fUI->setWindowVisible(true); #if DPF_CLAP_USING_HOST_TIMER fHostTimer->register_timer(fHost, DPF_CLAP_TIMER_INTERVAL, &fTimerId); #else fCallbackRegistered = true; fUI->addIdleCallbackForNativeIdle(this, DPF_CLAP_TIMER_INTERVAL); #endif return true; } bool hide() { if (UIExporter* const ui = fUI.get()) { ui->setWindowVisible(false); #if DPF_CLAP_USING_HOST_TIMER fHostTimer->unregister_timer(fHost, fTimerId); fTimerId = 0; #else ui->removeIdleCallbackForNativeIdle(this); fCallbackRegistered = false; #endif } return true; } // ---------------------------------------------------------------------------------------------------------------- void idleCallback() override { if (UIExporter* const ui = fUI.get()) { #if DPF_CLAP_USING_HOST_TIMER ui->plugin_idle(); #else ui->idleFromNativeIdle(); #endif for (uint i=0; i<fCachedParameters.numParams; ++i) { if (fCachedParameters.changed[i]) { fCachedParameters.changed[i] = false; ui->parameterChanged(i, fCachedParameters.values[i]); } } } } // ---------------------------------------------------------------------------------------------------------------- void setParameterValueFromPlugin(const uint index, const float value) { if (UIExporter* const ui = fUI.get()) ui->parameterChanged(index, value); } #if DISTRHO_PLUGIN_WANT_PROGRAMS void setProgramFromPlugin(const uint index) { if (UIExporter* const ui = fUI.get()) ui->programLoaded(index); } #endif #if DISTRHO_PLUGIN_WANT_STATE void setStateFromPlugin(const char* const key, const char* const value) { if (UIExporter* const ui = fUI.get()) ui->stateChanged(key, value); } #endif // ---------------------------------------------------------------------------------------------------------------- private: // Plugin and UI PluginExporter& fPlugin; ClapEventQueue* const fPluginEventQueue; ClapEventQueue::Queue& fEventQueue; ClapEventQueue::CachedParameters& fCachedParameters; #if DISTRHO_PLUGIN_WANT_PROGRAMS uint32_t& fCurrentProgram; #endif #if DISTRHO_PLUGIN_WANT_STATE StringMap& fStateMap; #endif const clap_host_t* const fHost; const clap_host_gui_t* const fHostGui; #if DPF_CLAP_USING_HOST_TIMER clap_id fTimerId; const clap_host_timer_support_t* const fHostTimer; #else bool fCallbackRegistered; #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT RingBufferControl<SmallStackBuffer> fNotesRingBuffer; #endif ScopedPointer<UIExporter> fUI; const bool fIsFloating; // Temporary data double fScaleFactor; uintptr_t fParentWindow; uintptr_t fTransientWindow; String fWindowTitle; // ---------------------------------------------------------------------------------------------------------------- void createUI() { DISTRHO_SAFE_ASSERT_RETURN(fUI == nullptr,); fUI = new UIExporter(this, fParentWindow, fPlugin.getSampleRate(), editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, nullptr, // TODO fileRequestCallback, d_nextBundlePath, fPlugin.getInstancePointer(), fScaleFactor); #if DISTRHO_PLUGIN_WANT_PROGRAMS fUI->programLoaded(fCurrentProgram); #endif #if DISTRHO_PLUGIN_WANT_FULL_STATE // Update current state from plugin side for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; fStateMap[key] = fPlugin.getStateValue(key); } #endif #if DISTRHO_PLUGIN_WANT_STATE // Set state for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; const String& value = cit->second; // TODO skip DSP only states fUI->stateChanged(key, value); } #endif for (uint32_t i=0; i<fCachedParameters.numParams; ++i) { const float value = fCachedParameters.values[i] = fPlugin.getParameterValue(i); fCachedParameters.changed[i] = false; fUI->parameterChanged(i, value); } if (fIsFloating) { if (fWindowTitle.isNotEmpty()) fUI->setWindowTitle(fWindowTitle); if (fTransientWindow != 0) fUI->setWindowTransientWinId(fTransientWindow); } } // ---------------------------------------------------------------------------------------------------------------- // DPF callbacks void editParameter(const uint32_t rindex, const bool started) const { const ClapEventQueue::Event ev = { started ? ClapEventQueue::kEventGestureBegin : ClapEventQueue::kEventGestureEnd, rindex, 0.f }; fEventQueue.addEventFromUI(ev); } static void editParameterCallback(void* const ptr, const uint32_t rindex, const bool started) { static_cast<ClapUI*>(ptr)->editParameter(rindex, started); } void setParameterValue(const uint32_t rindex, const float value) { const ClapEventQueue::Event ev = { ClapEventQueue::kEventParamSet, rindex, value }; fEventQueue.addEventFromUI(ev); } static void setParameterCallback(void* const ptr, const uint32_t rindex, const float value) { static_cast<ClapUI*>(ptr)->setParameterValue(rindex, value); } void setSizeFromPlugin(const uint width, const uint height) { DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); #ifdef DISTRHO_OS_MAC const double scaleFactor = fUI->getScaleFactor(); const uint hostWidth = width / scaleFactor; const uint hostHeight = height / scaleFactor; #else const uint hostWidth = width; const uint hostHeight = height; #endif if (fHostGui->request_resize(fHost, hostWidth, hostHeight)) fUI->setWindowSizeFromHost(width, height); } static void setSizeCallback(void* const ptr, const uint width, const uint height) { static_cast<ClapUI*>(ptr)->setSizeFromPlugin(width, height); } #if DISTRHO_PLUGIN_WANT_STATE void setState(const char* const key, const char* const value) { fPluginEventQueue->setStateFromUI(key, value); } static void setStateCallback(void* const ptr, const char* key, const char* value) { static_cast<ClapUI*>(ptr)->setState(key, value); } #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) { uint8_t midiData[3]; midiData[0] = (velocity != 0 ? 0x90 : 0x80) | channel; midiData[1] = note; midiData[2] = velocity; fNotesRingBuffer.writeCustomData(midiData, 3); fNotesRingBuffer.commitWrite(); } static void sendNoteCallback(void* const ptr, const uint8_t channel, const uint8_t note, const uint8_t velocity) { static_cast<ClapUI*>(ptr)->sendNote(channel, note, velocity); } #endif /* TODO bool fileRequest(const char*) { return true; } static bool fileRequestCallback(void* const ptr, const char* const key) { return static_cast<ClapUI*>(ptr)->fileRequest(key); } */ }; // -------------------------------------------------------------------------------------------------------------------- #endif // DISTRHO_PLUGIN_HAS_UI #if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT static constexpr const writeMidiFunc writeMidiCallback = nullptr; #endif #if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST static constexpr const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; #endif #if ! DISTRHO_PLUGIN_WANT_STATE static constexpr const updateStateValueFunc updateStateValueCallback = nullptr; #endif // -------------------------------------------------------------------------------------------------------------------- /** * CLAP plugin class. */ class PluginCLAP : ClapEventQueue { public: PluginCLAP(const clap_host_t* const host) : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, updateStateValueCallback), fHost(host), fOutputEvents(nullptr), #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0 fUsingCV(false), #endif #if DISTRHO_PLUGIN_WANT_LATENCY fLatencyChanged(false), fLastKnownLatency(0), #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT fMidiEventCount(0), #endif fHostExtensions(host) { fCachedParameters.setup(fPlugin.getParameterCount()); #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT fNotesRingBuffer.setRingBuffer(&fNotesBuffer, true); #endif #if DISTRHO_PLUGIN_WANT_STATE for (uint32_t i=0, count=fPlugin.getStateCount(); i<count; ++i) { const String& dkey(fPlugin.getStateKey(i)); fStateMap[dkey] = fPlugin.getStateDefaultValue(i); } #endif #if DISTRHO_PLUGIN_NUM_INPUTS != 0 fillInBusInfoDetails<true>(); #endif #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0 fillInBusInfoDetails<false>(); #endif #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0 fillInBusInfoPairs(); #endif } // ---------------------------------------------------------------------------------------------------------------- // core template <class T> const T* getHostExtension(const char* const extensionId) const { return static_cast<const T*>(fHost->get_extension(fHost, extensionId)); } bool init() { if (!clap_version_is_compatible(fHost->clap_version)) return false; return fHostExtensions.init(); } void activate(const double sampleRate, const uint32_t maxFramesCount) { fPlugin.setSampleRate(sampleRate, true); fPlugin.setBufferSize(maxFramesCount, true); fPlugin.activate(); } void deactivate() { fPlugin.deactivate(); #if DISTRHO_PLUGIN_WANT_LATENCY checkForLatencyChanges(false, true); reportLatencyChangeIfNeeded(); #endif } bool process(const clap_process_t* const process) { #if DISTRHO_PLUGIN_WANT_MIDI_INPUT fMidiEventCount = 0; #endif #if DISTRHO_PLUGIN_HAS_UI if (const clap_output_events_t* const outputEvents = process->out_events) { const RecursiveMutexTryLocker crmtl(fEventQueue.lock); if (crmtl.wasLocked()) { // reuse the same struct for gesture and parameters, they are compatible up to where it matters clap_event_param_value_t clapEvent = { { 0, 0, 0, 0, CLAP_EVENT_IS_LIVE }, 0, nullptr, 0, 0, 0, 0, 0.0 }; for (uint32_t i=0; i<fEventQueue.used; ++i) { const Event& event(fEventQueue.events[i]); switch (event.type) { case kEventGestureBegin: clapEvent.header.size = sizeof(clap_event_param_gesture_t); clapEvent.header.type = CLAP_EVENT_PARAM_GESTURE_BEGIN; clapEvent.param_id = event.index; break; case kEventGestureEnd: clapEvent.header.size = sizeof(clap_event_param_gesture_t); clapEvent.header.type = CLAP_EVENT_PARAM_GESTURE_END; clapEvent.param_id = event.index; break; case kEventParamSet: clapEvent.header.size = sizeof(clap_event_param_value_t); clapEvent.header.type = CLAP_EVENT_PARAM_VALUE; clapEvent.param_id = event.index; clapEvent.value = event.value; fPlugin.setParameterValue(event.index, event.value); break; default: continue; } outputEvents->try_push(outputEvents, &clapEvent.header); } fEventQueue.used = 0; } } #endif #if DISTRHO_PLUGIN_WANT_TIMEPOS if (const clap_event_transport_t* const transport = process->transport) { fTimePosition.playing = (transport->flags & CLAP_TRANSPORT_IS_PLAYING) != 0 && (transport->flags & CLAP_TRANSPORT_IS_WITHIN_PRE_ROLL) == 0; fTimePosition.frame = process->steady_time >= 0 ? process->steady_time : 0; if (transport->flags & CLAP_TRANSPORT_HAS_TEMPO) fTimePosition.bbt.beatsPerMinute = transport->tempo; else fTimePosition.bbt.beatsPerMinute = 120.0; // ticksPerBeat is not possible with CLAP fTimePosition.bbt.ticksPerBeat = 1920.0; if ((transport->flags & (CLAP_TRANSPORT_HAS_BEATS_TIMELINE|CLAP_TRANSPORT_HAS_TIME_SIGNATURE)) == (CLAP_TRANSPORT_HAS_BEATS_TIMELINE|CLAP_TRANSPORT_HAS_TIME_SIGNATURE)) { if (transport->song_pos_beats >= 0) { const int64_t clapPos = std::abs(transport->song_pos_beats); const int64_t clapBeats = clapPos >> 31; const double clapRest = static_cast<double>(clapPos & 0x7fffffff) / CLAP_BEATTIME_FACTOR; fTimePosition.bbt.bar = static_cast<int32_t>(clapBeats) / transport->tsig_num + 1; fTimePosition.bbt.beat = static_cast<int32_t>(clapBeats % transport->tsig_num) + 1; fTimePosition.bbt.tick = clapRest * fTimePosition.bbt.ticksPerBeat; } else { fTimePosition.bbt.bar = 1; fTimePosition.bbt.beat = 1; fTimePosition.bbt.tick = 0.0; } fTimePosition.bbt.valid = true; fTimePosition.bbt.beatsPerBar = transport->tsig_num; fTimePosition.bbt.beatType = transport->tsig_denom; } else { fTimePosition.bbt.valid = false; fTimePosition.bbt.bar = 1; fTimePosition.bbt.beat = 1; fTimePosition.bbt.tick = 0.0; fTimePosition.bbt.beatsPerBar = 4.0f; fTimePosition.bbt.beatType = 4.0f; } fTimePosition.bbt.barStartTick = fTimePosition.bbt.ticksPerBeat* fTimePosition.bbt.beatsPerBar* (fTimePosition.bbt.bar-1); } else { fTimePosition.playing = false; fTimePosition.frame = 0; fTimePosition.bbt.valid = false; fTimePosition.bbt.beatsPerMinute = 120.0; fTimePosition.bbt.bar = 1; fTimePosition.bbt.beat = 1; fTimePosition.bbt.tick = 0.0; fTimePosition.bbt.beatsPerBar = 4.f; fTimePosition.bbt.beatType = 4.f; fTimePosition.bbt.barStartTick = 0; } fPlugin.setTimePosition(fTimePosition); #endif if (const clap_input_events_t* const inputEvents = process->in_events) { if (const uint32_t len = inputEvents->size(inputEvents)) { for (uint32_t i=0; i<len; ++i) { const clap_event_header_t* const event = inputEvents->get(inputEvents, i); switch (event->type) { case CLAP_EVENT_NOTE_ON: #if DISTRHO_PLUGIN_WANT_MIDI_INPUT // BUG: even though we only report CLAP_NOTE_DIALECT_MIDI as supported, Bitwig sends us this anyway addNoteEvent(static_cast<const clap_event_note_t*>(static_cast<const void*>(event)), true); #endif break; case CLAP_EVENT_NOTE_OFF: #if DISTRHO_PLUGIN_WANT_MIDI_INPUT // BUG: even though we only report CLAP_NOTE_DIALECT_MIDI as supported, Bitwig sends us this anyway addNoteEvent(static_cast<const clap_event_note_t*>(static_cast<const void*>(event)), false); #endif break; case CLAP_EVENT_NOTE_CHOKE: case CLAP_EVENT_NOTE_END: case CLAP_EVENT_NOTE_EXPRESSION: break; case CLAP_EVENT_PARAM_VALUE: DISTRHO_SAFE_ASSERT_UINT2_BREAK(event->size == sizeof(clap_event_param_value), event->size, sizeof(clap_event_param_value)); setParameterValueFromEvent(static_cast<const clap_event_param_value*>(static_cast<const void*>(event))); break; case CLAP_EVENT_PARAM_MOD: case CLAP_EVENT_PARAM_GESTURE_BEGIN: case CLAP_EVENT_PARAM_GESTURE_END: case CLAP_EVENT_TRANSPORT: break; case CLAP_EVENT_MIDI: DISTRHO_SAFE_ASSERT_UINT2_BREAK(event->size == sizeof(clap_event_midi_t), event->size, sizeof(clap_event_midi_t)); #if DISTRHO_PLUGIN_WANT_MIDI_INPUT addMidiEvent(static_cast<const clap_event_midi_t*>(static_cast<const void*>(event))); #endif break; case CLAP_EVENT_MIDI_SYSEX: case CLAP_EVENT_MIDI2: break; } } } } #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT if (fMidiEventCount != kMaxMidiEvents && fNotesRingBuffer.isDataAvailableForReading()) { uint8_t midiData[3]; const uint32_t frame = fMidiEventCount != 0 ? fMidiEvents[fMidiEventCount-1].frame : 0; while (fNotesRingBuffer.isDataAvailableForReading()) { if (! fNotesRingBuffer.readCustomData(midiData, 3)) break; MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]); midiEvent.frame = frame; midiEvent.size = 3; std::memcpy(midiEvent.data, midiData, 3); if (fMidiEventCount == kMaxMidiEvents) break; } } #endif if (const uint32_t frames = process->frames_count) { #if DISTRHO_PLUGIN_NUM_INPUTS != 0 const float** const audioInputs = fAudioInputs; uint32_t in=0; for (uint32_t i=0; i<process->audio_inputs_count; ++i) { const clap_audio_buffer_t& inputs(process->audio_inputs[i]); DISTRHO_SAFE_ASSERT_CONTINUE(inputs.channel_count != 0); for (uint32_t j=0; j<inputs.channel_count; ++j, ++in) audioInputs[in] = const_cast<const float*>(inputs.data32[j]); } if (fUsingCV) { for (; in<DISTRHO_PLUGIN_NUM_INPUTS; ++in) audioInputs[in] = nullptr; } else { DISTRHO_SAFE_ASSERT_UINT2_RETURN(in == DISTRHO_PLUGIN_NUM_INPUTS, in, process->audio_inputs_count, false); } #else constexpr const float** const audioInputs = nullptr; #endif #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0 float** const audioOutputs = fAudioOutputs; uint32_t out=0; for (uint32_t i=0; i<process->audio_outputs_count; ++i) { const clap_audio_buffer_t& outputs(process->audio_outputs[i]); DISTRHO_SAFE_ASSERT_CONTINUE(outputs.channel_count != 0); for (uint32_t j=0; j<outputs.channel_count; ++j, ++out) audioOutputs[out] = outputs.data32[j]; } if (fUsingCV) { for (; out<DISTRHO_PLUGIN_NUM_OUTPUTS; ++out) audioOutputs[out] = nullptr; } else { DISTRHO_SAFE_ASSERT_UINT2_RETURN(out == DISTRHO_PLUGIN_NUM_OUTPUTS, out, DISTRHO_PLUGIN_NUM_OUTPUTS, false); } #else constexpr float** const audioOutputs = nullptr; #endif fOutputEvents = process->out_events; #if DISTRHO_PLUGIN_WANT_MIDI_INPUT fPlugin.run(audioInputs, audioOutputs, frames, fMidiEvents, fMidiEventCount); #else fPlugin.run(audioInputs, audioOutputs, frames); #endif flushParameters(nullptr, process->out_events, frames - 1); fOutputEvents = nullptr; } #if DISTRHO_PLUGIN_WANT_LATENCY checkForLatencyChanges(true, false); #endif return true; } void onMainThread() { #if DISTRHO_PLUGIN_WANT_LATENCY reportLatencyChangeIfNeeded(); #endif } // ---------------------------------------------------------------------------------------------------------------- // parameters uint32_t getParameterCount() const { return fPlugin.getParameterCount(); } bool getParameterInfo(const uint32_t index, clap_param_info_t* const info) const { const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); if (fPlugin.getParameterDesignation(index) == kParameterDesignationBypass) { info->flags = CLAP_PARAM_IS_STEPPED|CLAP_PARAM_IS_BYPASS|CLAP_PARAM_IS_AUTOMATABLE; std::strcpy(info->name, "Bypass"); std::strcpy(info->module, "dpf_bypass"); } else { const uint32_t hints = fPlugin.getParameterHints(index); const uint32_t groupId = fPlugin.getParameterGroupId(index); info->flags = 0; if (hints & kParameterIsOutput) info->flags |= CLAP_PARAM_IS_READONLY; else if (hints & kParameterIsAutomatable) info->flags |= CLAP_PARAM_IS_AUTOMATABLE; if (hints & (kParameterIsBoolean|kParameterIsInteger)) info->flags |= CLAP_PARAM_IS_STEPPED; d_strncpy(info->name, fPlugin.getParameterName(index), CLAP_NAME_SIZE); uint wrtn; if (groupId != kPortGroupNone) { const PortGroupWithId& portGroup(fPlugin.getPortGroupById(groupId)); strncpy(info->module, portGroup.symbol, CLAP_PATH_SIZE / 2); info->module[CLAP_PATH_SIZE / 2] = '\0'; wrtn = std::strlen(info->module); info->module[wrtn++] = '/'; } else { wrtn = 0; } d_strncpy(info->module + wrtn, fPlugin.getParameterSymbol(index), CLAP_PATH_SIZE - wrtn); } info->id = index; info->cookie = nullptr; info->min_value = ranges.min; info->max_value = ranges.max; info->default_value = ranges.def; return true; } bool getParameterValue(const clap_id param_id, double* const value) const { *value = fPlugin.getParameterValue(param_id); return true; } bool getParameterStringForValue(const clap_id param_id, double value, char* const display, const uint32_t size) const { const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(param_id)); const ParameterRanges& ranges(fPlugin.getParameterRanges(param_id)); const uint32_t hints = fPlugin.getParameterHints(param_id); if (hints & kParameterIsBoolean) { const float midRange = ranges.min + (ranges.max - ranges.min) * 0.5f; value = value > midRange ? ranges.max : ranges.min; } else if (hints & kParameterIsInteger) { value = std::round(value); } for (uint32_t i=0; i < enumValues.count; ++i) { if (d_isEqual(static_cast<double>(enumValues.values[i].value), value)) { d_strncpy(display, enumValues.values[i].label, size); return true; } } if (hints & kParameterIsInteger) snprintf_i32(display, value, size); else snprintf_f32(display, value, size); return true; } bool getParameterValueForString(const clap_id param_id, const char* const display, double* const value) const { const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(param_id)); const bool isInteger = fPlugin.isParameterInteger(param_id); for (uint32_t i=0; i < enumValues.count; ++i) { if (std::strcmp(display, enumValues.values[i].label) == 0) { *value = enumValues.values[i].value; return true; } } if (isInteger) *value = std::atoi(display); else *value = std::atof(display); return true; } void flushParameters(const clap_input_events_t* const in, const clap_output_events_t* const out, const uint32_t frameOffset) { if (const uint32_t len = in != nullptr ? in->size(in) : 0) { for (uint32_t i=0; i<len; ++i) { const clap_event_header_t* const event = in->get(in, i); if (event->type != CLAP_EVENT_PARAM_VALUE) continue; DISTRHO_SAFE_ASSERT_UINT2_BREAK(event->size == sizeof(clap_event_param_value), event->size, sizeof(clap_event_param_value)); setParameterValueFromEvent(static_cast<const clap_event_param_value*>(static_cast<const void*>(event))); } } if (out != nullptr) { clap_event_param_value_t clapEvent = { { sizeof(clap_event_param_value_t), frameOffset, 0, CLAP_EVENT_PARAM_VALUE, CLAP_EVENT_IS_LIVE }, 0, nullptr, 0, 0, 0, 0, 0.0 }; float value; for (uint i=0; i<fCachedParameters.numParams; ++i) { if (fPlugin.isParameterOutputOrTrigger(i)) { value = fPlugin.getParameterValue(i); if (d_isEqual(fCachedParameters.values[i], value)) continue; fCachedParameters.values[i] = value; fCachedParameters.changed[i] = true; clapEvent.param_id = i; clapEvent.value = value; out->try_push(out, &clapEvent.header); } } } #if DISTRHO_PLUGIN_WANT_LATENCY const bool active = fPlugin.isActive(); checkForLatencyChanges(active, !active); #endif } // ---------------------------------------------------------------------------------------------------------------- #if DISTRHO_PLUGIN_WANT_MIDI_INPUT void addNoteEvent(const clap_event_note_t* const event, const bool isOn) noexcept { DISTRHO_SAFE_ASSERT_UINT_RETURN(event->port_index == 0, event->port_index,); if (fMidiEventCount == kMaxMidiEvents) return; MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]); midiEvent.frame = event->header.time; midiEvent.size = 3; midiEvent.data[0] = (isOn ? 0x90 : 0x80) | (event->channel & 0x0F); midiEvent.data[1] = std::max(0, std::min(127, static_cast<int>(event->key))); midiEvent.data[2] = std::max(0, std::min(127, static_cast<int>(event->velocity * 127 + 0.5))); } void addMidiEvent(const clap_event_midi_t* const event) noexcept { DISTRHO_SAFE_ASSERT_UINT_RETURN(event->port_index == 0, event->port_index,); if (fMidiEventCount == kMaxMidiEvents) return; MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]); midiEvent.frame = event->header.time; midiEvent.size = 3; std::memcpy(midiEvent.data, event->data, 3); } #endif void setParameterValueFromEvent(const clap_event_param_value* const event) { fCachedParameters.values[event->param_id] = event->value; fCachedParameters.changed[event->param_id] = true; fPlugin.setParameterValue(event->param_id, event->value); } // ---------------------------------------------------------------------------------------------------------------- // audio ports #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0 template<bool isInput> uint32_t getAudioPortCount() const noexcept { return (isInput ? fAudioInputBuses : fAudioOutputBuses).size(); } template<bool isInput> bool getAudioPortInfo(const uint32_t index, clap_audio_port_info_t* const info) const noexcept { const std::vector<BusInfo>& busInfos(isInput ? fAudioInputBuses : fAudioOutputBuses); DISTRHO_SAFE_ASSERT_RETURN(index < busInfos.size(), false); const BusInfo& busInfo(busInfos[index]); info->id = busInfo.groupId; d_strncpy(info->name, busInfo.name, CLAP_NAME_SIZE); info->flags = busInfo.isMain ? CLAP_AUDIO_PORT_IS_MAIN : 0x0; info->channel_count = busInfo.numChannels; switch (busInfo.groupId) { case kPortGroupMono: info->port_type = CLAP_PORT_MONO; break; case kPortGroupStereo: info->port_type = CLAP_PORT_STEREO; break; default: info->port_type = nullptr; break; } info->in_place_pair = busInfo.hasPair ? busInfo.groupId : CLAP_INVALID_ID; return true; } #endif // ---------------------------------------------------------------------------------------------------------------- // latency #if DISTRHO_PLUGIN_WANT_LATENCY uint32_t getLatency() const noexcept { return fPlugin.getLatency(); } void checkForLatencyChanges(const bool isActive, const bool fromMainThread) { const uint32_t latency = fPlugin.getLatency(); if (fLastKnownLatency == latency) return; fLastKnownLatency = latency; if (isActive) { fLatencyChanged = true; fHost->request_restart(fHost); } else { // if this is main-thread we can report latency change directly if (fromMainThread || (fHostExtensions.threadCheck != nullptr && fHostExtensions.threadCheck->is_main_thread(fHost))) { fLatencyChanged = false; fHostExtensions.latency->changed(fHost); } // otherwise we need to request a main-thread callback else { fLatencyChanged = true; fHost->request_callback(fHost); } } } // called from main thread void reportLatencyChangeIfNeeded() { if (fLatencyChanged) { fLatencyChanged = false; fHostExtensions.latency->changed(fHost); } } #endif // ---------------------------------------------------------------------------------------------------------------- // state bool stateSave(const clap_ostream_t* const stream) { const uint32_t paramCount = fPlugin.getParameterCount(); #if DISTRHO_PLUGIN_WANT_STATE const uint32_t stateCount = fPlugin.getStateCount(); #else const uint32_t stateCount = 0; #endif if (stateCount == 0 && paramCount == 0) { char buffer = '\0'; return stream->write(stream, &buffer, 1) == 1; } #if DISTRHO_PLUGIN_WANT_FULL_STATE // Update current state for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; fStateMap[key] = fPlugin.getStateValue(key); } #endif String state; #if DISTRHO_PLUGIN_WANT_PROGRAMS { String tmpStr("__dpf_program__\xff"); tmpStr += String(fCurrentProgram); tmpStr += "\xff"; state += tmpStr; } #endif #if DISTRHO_PLUGIN_WANT_STATE if (stateCount != 0) { state += "__dpf_state_begin__\xff"; for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; const String& value = cit->second; // join key and value String tmpStr; tmpStr = key; tmpStr += "\xff"; tmpStr += value; tmpStr += "\xff"; state += tmpStr; } state += "__dpf_state_end__\xff"; } #endif if (paramCount != 0) { state += "__dpf_parameters_begin__\xff"; for (uint32_t i=0; i<paramCount; ++i) { if (fPlugin.isParameterOutputOrTrigger(i)) continue; // join key and value String tmpStr; tmpStr = fPlugin.getParameterSymbol(i); tmpStr += "\xff"; if (fPlugin.getParameterHints(i) & kParameterIsInteger) tmpStr += String(static_cast<int>(std::round(fPlugin.getParameterValue(i)))); else tmpStr += String(fPlugin.getParameterValue(i)); tmpStr += "\xff"; state += tmpStr; } state += "__dpf_parameters_end__\xff"; } // terminator state += "\xfe"; state.replace('\xff', '\0'); // now saving state, carefully until host written bytes matches full state size const char* buffer = state.buffer(); const int32_t size = static_cast<int32_t>(state.length())+1; for (int32_t wrtntotal = 0, wrtn; wrtntotal < size; wrtntotal += wrtn) { wrtn = stream->write(stream, buffer, size - wrtntotal); DISTRHO_SAFE_ASSERT_INT_RETURN(wrtn > 0, wrtn, false); } return true; } bool stateLoad(const clap_istream_t* const stream) { #if DISTRHO_PLUGIN_HAS_UI ClapUI* const ui = fUI.get(); #endif String key, value; bool hasValue = false; bool fillingKey = true; // if filling key or value char queryingType = 'i'; // can be 'n', 's' or 'p' (none, states, parameters) char buffer[512], orig; buffer[sizeof(buffer)-1] = '\xff'; for (int32_t terminated = 0, read; terminated == 0;) { read = stream->read(stream, buffer, sizeof(buffer)-1); DISTRHO_SAFE_ASSERT_INT_RETURN(read >= 0, read, false); if (read == 0) return true; for (int32_t i = 0; i < read; ++i) { // found terminator, stop here if (buffer[i] == '\xfe') { terminated = 1; break; } // store character at read position orig = buffer[read]; // place null character to create valid string buffer[read] = '\0'; // append to temporary vars if (fillingKey) { key += buffer + i; } else { value += buffer + i; hasValue = true; } // increase buffer offset by length of string i += std::strlen(buffer + i); // restore read character buffer[read] = orig; // if buffer offset points to null, we found the end of a string, lets check if (buffer[i] == '\0') { // special keys if (key == "__dpf_state_begin__") { DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n', queryingType, false); queryingType = 's'; key.clear(); value.clear(); hasValue = false; continue; } if (key == "__dpf_state_end__") { DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 's', queryingType, false); queryingType = 'n'; key.clear(); value.clear(); hasValue = false; continue; } if (key == "__dpf_parameters_begin__") { DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n', queryingType, false); queryingType = 'p'; key.clear(); value.clear(); hasValue = false; continue; } if (key == "__dpf_parameters_end__") { DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'p', queryingType, false); queryingType = 'x'; key.clear(); value.clear(); hasValue = false; continue; } // no special key, swap between reading real key and value fillingKey = !fillingKey; // if there is no value yet keep reading until we have one if (! hasValue) continue; if (key == "__dpf_program__") { DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i', queryingType, false); queryingType = 'n'; d_debug("found program '%s'", value.buffer()); #if DISTRHO_PLUGIN_WANT_PROGRAMS const int program = std::atoi(value.buffer()); DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0); fCurrentProgram = static_cast<uint32_t>(program); fPlugin.loadProgram(fCurrentProgram); #if DISTRHO_PLUGIN_HAS_UI if (ui != nullptr) ui->setProgramFromPlugin(fCurrentProgram); #endif #endif } else if (queryingType == 's') { d_debug("found state '%s' '%s'", key.buffer(), value.buffer()); #if DISTRHO_PLUGIN_WANT_STATE if (fPlugin.wantStateKey(key)) { fStateMap[key] = value; fPlugin.setState(key, value); #if DISTRHO_PLUGIN_HAS_UI if (ui != nullptr) ui->setStateFromPlugin(key, value); #endif } #endif } else if (queryingType == 'p') { d_debug("found parameter '%s' '%s'", key.buffer(), value.buffer()); float fvalue; // find parameter with this symbol, and set its value for (uint32_t j=0; j<fCachedParameters.numParams; ++j) { if (fPlugin.isParameterOutputOrTrigger(j)) continue; if (fPlugin.getParameterSymbol(j) != key) continue; if (fPlugin.getParameterHints(j) & kParameterIsInteger) fvalue = std::atoi(value.buffer()); else fvalue = std::atof(value.buffer()); fCachedParameters.values[j] = fvalue; #if DISTRHO_PLUGIN_HAS_UI if (ui != nullptr) { // UI parameter updates are handled outside the read loop (after host param restart) fCachedParameters.changed[j] = true; } #endif fPlugin.setParameterValue(j, fvalue); break; } } key.clear(); value.clear(); hasValue = false; } } } if (fHostExtensions.params != nullptr) fHostExtensions.params->rescan(fHost, CLAP_PARAM_RESCAN_VALUES|CLAP_PARAM_RESCAN_TEXT); #if DISTRHO_PLUGIN_WANT_LATENCY checkForLatencyChanges(fPlugin.isActive(), true); reportLatencyChangeIfNeeded(); #endif #if DISTRHO_PLUGIN_HAS_UI if (ui != nullptr) { for (uint32_t i=0; i<fCachedParameters.numParams; ++i) { if (fPlugin.isParameterOutputOrTrigger(i)) continue; fCachedParameters.changed[i] = false; ui->setParameterValueFromPlugin(i, fCachedParameters.values[i]); } } #endif return true; } // ---------------------------------------------------------------------------------------------------------------- // gui #if DISTRHO_PLUGIN_HAS_UI bool createUI(const bool isFloating) { const clap_host_gui_t* const hostGui = getHostExtension<clap_host_gui_t>(CLAP_EXT_GUI); DISTRHO_SAFE_ASSERT_RETURN(hostGui != nullptr, false); #if DPF_CLAP_USING_HOST_TIMER const clap_host_timer_support_t* const hostTimer = getHostExtension<clap_host_timer_support_t>(CLAP_EXT_TIMER_SUPPORT); DISTRHO_SAFE_ASSERT_RETURN(hostTimer != nullptr, false); #endif fUI = new ClapUI(fPlugin, this, fHost, hostGui, #if DPF_CLAP_USING_HOST_TIMER hostTimer, #endif isFloating); return true; } void destroyUI() { fUI = nullptr; } ClapUI* getUI() const noexcept { return fUI.get(); } #endif #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_STATE void setStateFromUI(const char* const key, const char* const value) override { fPlugin.setState(key, value); // check if we want to save this key if (! fPlugin.wantStateKey(key)) return; // check if key already exists for (StringMap::iterator it=fStateMap.begin(), ite=fStateMap.end(); it != ite; ++it) { const String& dkey(it->first); if (dkey == key) { it->second = value; return; } } d_stderr("Failed to find plugin state with key \"%s\"", key); } #endif // ---------------------------------------------------------------------------------------------------------------- private: // Plugin and UI PluginExporter fPlugin; #if DISTRHO_PLUGIN_HAS_UI ScopedPointer<ClapUI> fUI; #endif // CLAP stuff const clap_host_t* const fHost; const clap_output_events_t* fOutputEvents; #if DISTRHO_PLUGIN_NUM_INPUTS != 0 const float* fAudioInputs[DISTRHO_PLUGIN_NUM_INPUTS]; #endif #if DISTRHO_PLUGIN_NUM_OUTPUTS != 0 float* fAudioOutputs[DISTRHO_PLUGIN_NUM_OUTPUTS]; #endif #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0 bool fUsingCV; #endif #if DISTRHO_PLUGIN_WANT_LATENCY bool fLatencyChanged; uint32_t fLastKnownLatency; #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT uint32_t fMidiEventCount; MidiEvent fMidiEvents[kMaxMidiEvents]; #if DISTRHO_PLUGIN_HAS_UI RingBufferControl<SmallStackBuffer> fNotesRingBuffer; #endif #endif #if DISTRHO_PLUGIN_WANT_TIMEPOS TimePosition fTimePosition; #endif struct HostExtensions { const clap_host_t* const host; const clap_host_params_t* params; #if DISTRHO_PLUGIN_WANT_LATENCY const clap_host_latency_t* latency; const clap_host_thread_check_t* threadCheck; #endif HostExtensions(const clap_host_t* const host) : host(host), params(nullptr) #if DISTRHO_PLUGIN_WANT_LATENCY , latency(nullptr) , threadCheck(nullptr) #endif {} bool init() { params = static_cast<const clap_host_params_t*>(host->get_extension(host, CLAP_EXT_PARAMS)); #if DISTRHO_PLUGIN_WANT_LATENCY DISTRHO_SAFE_ASSERT_RETURN(host->request_restart != nullptr, false); DISTRHO_SAFE_ASSERT_RETURN(host->request_callback != nullptr, false); latency = static_cast<const clap_host_latency_t*>(host->get_extension(host, CLAP_EXT_LATENCY)); threadCheck = static_cast<const clap_host_thread_check_t*>(host->get_extension(host, CLAP_EXT_THREAD_CHECK)); #endif return true; } } fHostExtensions; // ---------------------------------------------------------------------------------------------------------------- // helper functions for dealing with buses #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0 struct BusInfo { char name[CLAP_NAME_SIZE]; uint32_t numChannels; bool hasPair; bool isCV; bool isMain; uint32_t groupId; }; std::vector<BusInfo> fAudioInputBuses, fAudioOutputBuses; template<bool isInput> void fillInBusInfoDetails() { constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS; std::vector<BusInfo>& busInfos(isInput ? fAudioInputBuses : fAudioOutputBuses); enum { kPortTypeNull, kPortTypeAudio, kPortTypeSidechain, kPortTypeCV, kPortTypeGroup } lastSeenPortType = kPortTypeNull; uint32_t lastSeenGroupId = kPortGroupNone; uint32_t nonGroupAudioId = 0; uint32_t nonGroupSidechainId = 0x20000000; for (uint32_t i=0; i<numPorts; ++i) { const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); if (port.groupId != kPortGroupNone) { if (lastSeenPortType != kPortTypeGroup || lastSeenGroupId != port.groupId) { lastSeenPortType = kPortTypeGroup; lastSeenGroupId = port.groupId; BusInfo busInfo = { {}, 1, false, false, // if this is the first port, set it as main busInfos.empty(), // user given group id with extra safety offset port.groupId + 0x80000000 }; const PortGroupWithId& group(fPlugin.getPortGroupById(port.groupId)); switch (port.groupId) { case kPortGroupStereo: case kPortGroupMono: if (busInfo.isMain) { d_strncpy(busInfo.name, isInput ? "Audio Input" : "Audio Output", CLAP_NAME_SIZE); break; } // fall-through default: if (group.name.isNotEmpty()) d_strncpy(busInfo.name, group.name, CLAP_NAME_SIZE); else d_strncpy(busInfo.name, port.name, CLAP_NAME_SIZE); break; } busInfos.push_back(busInfo); } else { ++busInfos.back().numChannels; } } else if (port.hints & kAudioPortIsCV) { // TODO lastSeenPortType = kPortTypeCV; lastSeenGroupId = kPortGroupNone; fUsingCV = true; } else if (port.hints & kAudioPortIsSidechain) { if (lastSeenPortType != kPortTypeSidechain) { lastSeenPortType = kPortTypeSidechain; lastSeenGroupId = kPortGroupNone; BusInfo busInfo = { {}, 1, false, false, // not main false, // give unique id nonGroupSidechainId++ }; d_strncpy(busInfo.name, port.name, CLAP_NAME_SIZE); busInfos.push_back(busInfo); } else { ++busInfos.back().numChannels; } } else { if (lastSeenPortType != kPortTypeAudio) { lastSeenPortType = kPortTypeAudio; lastSeenGroupId = kPortGroupNone; BusInfo busInfo = { {}, 1, false, false, // if this is the first port, set it as main busInfos.empty(), // give unique id nonGroupAudioId++ }; if (busInfo.isMain) { d_strncpy(busInfo.name, isInput ? "Audio Input" : "Audio Output", CLAP_NAME_SIZE); } else { d_strncpy(busInfo.name, port.name, CLAP_NAME_SIZE); } busInfos.push_back(busInfo); } else { ++busInfos.back().numChannels; } } } } #endif #if DISTRHO_PLUGIN_NUM_INPUTS != 0 && DISTRHO_PLUGIN_NUM_OUTPUTS != 0 void fillInBusInfoPairs() { const size_t numChannels = std::min(fAudioInputBuses.size(), fAudioOutputBuses.size()); for (size_t i=0; i<numChannels; ++i) { if (fAudioInputBuses[i].groupId != fAudioOutputBuses[i].groupId) break; if (fAudioInputBuses[i].numChannels != fAudioOutputBuses[i].numChannels) break; if (fAudioInputBuses[i].isMain != fAudioOutputBuses[i].isMain) break; fAudioInputBuses[i].hasPair = fAudioOutputBuses[i].hasPair = true; } } #endif // ---------------------------------------------------------------------------------------------------------------- // DPF callbacks #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT bool writeMidi(const MidiEvent& midiEvent) { DISTRHO_SAFE_ASSERT_RETURN(fOutputEvents != nullptr, false); if (midiEvent.size > 3) return true; const clap_event_midi clapEvent = { { sizeof(clap_event_midi), midiEvent.frame, 0, CLAP_EVENT_MIDI, CLAP_EVENT_IS_LIVE }, 0, { midiEvent.data[0], static_cast<uint8_t>(midiEvent.size >= 2 ? midiEvent.data[1] : 0), static_cast<uint8_t>(midiEvent.size >= 3 ? midiEvent.data[2] : 0) } }; return fOutputEvents->try_push(fOutputEvents, &clapEvent.header); } static bool writeMidiCallback(void* const ptr, const MidiEvent& midiEvent) { return static_cast<PluginCLAP*>(ptr)->writeMidi(midiEvent); } #endif #if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST bool requestParameterValueChange(uint32_t, float) { return true; } static bool requestParameterValueChangeCallback(void* const ptr, const uint32_t index, const float value) { return static_cast<PluginCLAP*>(ptr)->requestParameterValueChange(index, value); } #endif #if DISTRHO_PLUGIN_WANT_STATE bool updateState(const char*, const char*) { return true; } static bool updateStateValueCallback(void* const ptr, const char* const key, const char* const value) { return static_cast<PluginCLAP*>(ptr)->updateState(key, value); } #endif }; // -------------------------------------------------------------------------------------------------------------------- static ScopedPointer<PluginExporter> sPlugin; // -------------------------------------------------------------------------------------------------------------------- // plugin gui #if DISTRHO_PLUGIN_HAS_UI static const char* const kSupportedAPIs[] = { #if defined(DISTRHO_OS_WINDOWS) CLAP_WINDOW_API_WIN32, #elif defined(DISTRHO_OS_MAC) CLAP_WINDOW_API_COCOA, #else CLAP_WINDOW_API_X11, #endif }; // TODO DPF external UI static bool CLAP_ABI clap_gui_is_api_supported(const clap_plugin_t*, const char* const api, bool) { for (size_t i=0; i<ARRAY_SIZE(kSupportedAPIs); ++i) { if (std::strcmp(kSupportedAPIs[i], api) == 0) return true; } return false; } // TODO DPF external UI static bool CLAP_ABI clap_gui_get_preferred_api(const clap_plugin_t*, const char** const api, bool* const is_floating) { *api = kSupportedAPIs[0]; *is_floating = false; return true; } static bool CLAP_ABI clap_gui_create(const clap_plugin_t* const plugin, const char* const api, const bool is_floating) { for (size_t i=0; i<ARRAY_SIZE(kSupportedAPIs); ++i) { if (std::strcmp(kSupportedAPIs[i], api) == 0) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->createUI(is_floating); } } return false; } static void CLAP_ABI clap_gui_destroy(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); instance->destroyUI(); } static bool CLAP_ABI clap_gui_set_scale(const clap_plugin_t* const plugin, const double scale) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false); #ifndef DISTRHO_OS_MAC return gui->setScaleFactor(scale); #else return true; // unused (void)scale; #endif } static bool CLAP_ABI clap_gui_get_size(const clap_plugin_t* const plugin, uint32_t* const width, uint32_t* const height) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false); return gui->getSize(width, height); } static bool CLAP_ABI clap_gui_can_resize(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false); return gui->canResize(); } static bool CLAP_ABI clap_gui_get_resize_hints(const clap_plugin_t* const plugin, clap_gui_resize_hints_t* const hints) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false); return gui->getResizeHints(hints); } static bool CLAP_ABI clap_gui_adjust_size(const clap_plugin_t* const plugin, uint32_t* const width, uint32_t* const height) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false); return gui->adjustSize(width, height); } static bool CLAP_ABI clap_gui_set_size(const clap_plugin_t* const plugin, const uint32_t width, const uint32_t height) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false); return gui->setSizeFromHost(width, height); } static bool CLAP_ABI clap_gui_set_parent(const clap_plugin_t* const plugin, const clap_window_t* const window) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false); return gui->setParent(window); } static bool CLAP_ABI clap_gui_set_transient(const clap_plugin_t* const plugin, const clap_window_t* const window) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false); return gui->setTransient(window); } static void CLAP_ABI clap_gui_suggest_title(const clap_plugin_t* const plugin, const char* const title) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr,); return gui->suggestTitle(title); } static bool CLAP_ABI clap_gui_show(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false); return gui->show(); } static bool CLAP_ABI clap_gui_hide(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr, false); return gui->hide(); } static const clap_plugin_gui_t clap_plugin_gui = { clap_gui_is_api_supported, clap_gui_get_preferred_api, clap_gui_create, clap_gui_destroy, clap_gui_set_scale, clap_gui_get_size, clap_gui_can_resize, clap_gui_get_resize_hints, clap_gui_adjust_size, clap_gui_set_size, clap_gui_set_parent, clap_gui_set_transient, clap_gui_suggest_title, clap_gui_show, clap_gui_hide }; // -------------------------------------------------------------------------------------------------------------------- // plugin timer #if DPF_CLAP_USING_HOST_TIMER static void CLAP_ABI clap_plugin_on_timer(const clap_plugin_t* const plugin, clap_id) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); ClapUI* const gui = instance->getUI(); DISTRHO_SAFE_ASSERT_RETURN(gui != nullptr,); return gui->idleCallback(); } static const clap_plugin_timer_support_t clap_timer = { clap_plugin_on_timer }; #endif #endif // DISTRHO_PLUGIN_HAS_UI // -------------------------------------------------------------------------------------------------------------------- // plugin audio ports #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0 static uint32_t CLAP_ABI clap_plugin_audio_ports_count(const clap_plugin_t* const plugin, const bool is_input) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return is_input ? instance->getAudioPortCount<true>() : instance->getAudioPortCount<false>(); } static bool CLAP_ABI clap_plugin_audio_ports_get(const clap_plugin_t* const plugin, const uint32_t index, const bool is_input, clap_audio_port_info_t* const info) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return is_input ? instance->getAudioPortInfo<true>(index, info) : instance->getAudioPortInfo<false>(index, info); } static const clap_plugin_audio_ports_t clap_plugin_audio_ports = { clap_plugin_audio_ports_count, clap_plugin_audio_ports_get }; #endif // -------------------------------------------------------------------------------------------------------------------- // plugin note ports #if DISTRHO_PLUGIN_WANT_MIDI_INPUT+DISTRHO_PLUGIN_WANT_MIDI_OUTPUT != 0 static uint32_t CLAP_ABI clap_plugin_note_ports_count(const clap_plugin_t*, const bool is_input) { return (is_input ? DISTRHO_PLUGIN_WANT_MIDI_INPUT : DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) != 0 ? 1 : 0; } static bool CLAP_ABI clap_plugin_note_ports_get(const clap_plugin_t*, uint32_t, const bool is_input, clap_note_port_info_t* const info) { if (is_input) { #if DISTRHO_PLUGIN_WANT_MIDI_INPUT info->id = 0; info->supported_dialects = CLAP_NOTE_DIALECT_MIDI; info->preferred_dialect = CLAP_NOTE_DIALECT_MIDI; std::strcpy(info->name, "Event/MIDI Input"); return true; #endif } else { #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT info->id = 0; info->supported_dialects = CLAP_NOTE_DIALECT_MIDI; info->preferred_dialect = CLAP_NOTE_DIALECT_MIDI; std::strcpy(info->name, "Event/MIDI Output"); return true; #endif } return false; } static const clap_plugin_note_ports_t clap_plugin_note_ports = { clap_plugin_note_ports_count, clap_plugin_note_ports_get }; #endif // -------------------------------------------------------------------------------------------------------------------- // plugin parameters static uint32_t CLAP_ABI clap_plugin_params_count(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->getParameterCount(); } static bool CLAP_ABI clap_plugin_params_get_info(const clap_plugin_t* const plugin, const uint32_t index, clap_param_info_t* const info) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->getParameterInfo(index, info); } static bool CLAP_ABI clap_plugin_params_get_value(const clap_plugin_t* const plugin, const clap_id param_id, double* const value) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->getParameterValue(param_id, value); } static bool CLAP_ABI clap_plugin_params_value_to_text(const clap_plugin_t* plugin, const clap_id param_id, const double value, char* const display, const uint32_t size) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->getParameterStringForValue(param_id, value, display, size); } static bool CLAP_ABI clap_plugin_params_text_to_value(const clap_plugin_t* plugin, const clap_id param_id, const char* const display, double* const value) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->getParameterValueForString(param_id, display, value); } static void CLAP_ABI clap_plugin_params_flush(const clap_plugin_t* plugin, const clap_input_events_t* in, const clap_output_events_t* out) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->flushParameters(in, out, 0); } static const clap_plugin_params_t clap_plugin_params = { clap_plugin_params_count, clap_plugin_params_get_info, clap_plugin_params_get_value, clap_plugin_params_value_to_text, clap_plugin_params_text_to_value, clap_plugin_params_flush }; #if DISTRHO_PLUGIN_WANT_LATENCY // -------------------------------------------------------------------------------------------------------------------- // plugin latency static uint32_t CLAP_ABI clap_plugin_latency_get(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->getLatency(); } static const clap_plugin_latency_t clap_plugin_latency = { clap_plugin_latency_get }; #endif // -------------------------------------------------------------------------------------------------------------------- // plugin state static bool CLAP_ABI clap_plugin_state_save(const clap_plugin_t* const plugin, const clap_ostream_t* const stream) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->stateSave(stream); } static bool CLAP_ABI clap_plugin_state_load(const clap_plugin_t* const plugin, const clap_istream_t* const stream) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->stateLoad(stream); } static const clap_plugin_state_t clap_plugin_state = { clap_plugin_state_save, clap_plugin_state_load }; // -------------------------------------------------------------------------------------------------------------------- // plugin static bool CLAP_ABI clap_plugin_init(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->init(); } static void CLAP_ABI clap_plugin_destroy(const clap_plugin_t* const plugin) { delete static_cast<PluginCLAP*>(plugin->plugin_data); std::free(const_cast<clap_plugin_t*>(plugin)); } static bool CLAP_ABI clap_plugin_activate(const clap_plugin_t* const plugin, const double sample_rate, uint32_t, const uint32_t max_frames_count) { d_nextBufferSize = max_frames_count; d_nextSampleRate = sample_rate; PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); instance->activate(sample_rate, max_frames_count); return true; } static void CLAP_ABI clap_plugin_deactivate(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); instance->deactivate(); } static bool CLAP_ABI clap_plugin_start_processing(const clap_plugin_t*) { // nothing to do return true; } static void CLAP_ABI clap_plugin_stop_processing(const clap_plugin_t*) { // nothing to do } static void CLAP_ABI clap_plugin_reset(const clap_plugin_t*) { // nothing to do } static clap_process_status CLAP_ABI clap_plugin_process(const clap_plugin_t* const plugin, const clap_process_t* const process) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); return instance->process(process) ? CLAP_PROCESS_CONTINUE : CLAP_PROCESS_ERROR; } static const void* CLAP_ABI clap_plugin_get_extension(const clap_plugin_t*, const char* const id) { if (std::strcmp(id, CLAP_EXT_PARAMS) == 0) return &clap_plugin_params; if (std::strcmp(id, CLAP_EXT_STATE) == 0) return &clap_plugin_state; #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS != 0 if (std::strcmp(id, CLAP_EXT_AUDIO_PORTS) == 0) return &clap_plugin_audio_ports; #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT+DISTRHO_PLUGIN_WANT_MIDI_OUTPUT != 0 if (std::strcmp(id, CLAP_EXT_NOTE_PORTS) == 0) return &clap_plugin_note_ports; #endif #if DISTRHO_PLUGIN_WANT_LATENCY if (std::strcmp(id, CLAP_EXT_LATENCY) == 0) return &clap_plugin_latency; #endif #if DISTRHO_PLUGIN_HAS_UI if (std::strcmp(id, CLAP_EXT_GUI) == 0) return &clap_plugin_gui; #if DPF_CLAP_USING_HOST_TIMER if (std::strcmp(id, CLAP_EXT_TIMER_SUPPORT) == 0) return &clap_timer; #endif #endif return nullptr; } static void CLAP_ABI clap_plugin_on_main_thread(const clap_plugin_t* const plugin) { PluginCLAP* const instance = static_cast<PluginCLAP*>(plugin->plugin_data); instance->onMainThread(); } // -------------------------------------------------------------------------------------------------------------------- // plugin factory static uint32_t CLAP_ABI clap_get_plugin_count(const clap_plugin_factory_t*) { return 1; } static const clap_plugin_descriptor_t* CLAP_ABI clap_get_plugin_descriptor(const clap_plugin_factory_t*, const uint32_t index) { DISTRHO_SAFE_ASSERT_UINT_RETURN(index == 0, index, nullptr); static const char* features[] = { #ifdef DISTRHO_PLUGIN_CLAP_FEATURES DISTRHO_PLUGIN_CLAP_FEATURES, #elif DISTRHO_PLUGIN_IS_SYNTH "instrument", #endif nullptr }; static const clap_plugin_descriptor_t descriptor = { CLAP_VERSION, DISTRHO_PLUGIN_CLAP_ID, sPlugin->getName(), sPlugin->getMaker(), // TODO url "", // TODO manual url "", // TODO support url "", // TODO version string "", sPlugin->getDescription(), features }; return &descriptor; } static const clap_plugin_t* CLAP_ABI clap_create_plugin(const clap_plugin_factory_t* const factory, const clap_host_t* const host, const char*) { clap_plugin_t* const pluginptr = static_cast<clap_plugin_t*>(std::malloc(sizeof(clap_plugin_t))); DISTRHO_SAFE_ASSERT_RETURN(pluginptr != nullptr, nullptr); // default early values if (d_nextBufferSize == 0) d_nextBufferSize = 1024; if (d_nextSampleRate <= 0.0) d_nextSampleRate = 44100.0; d_nextCanRequestParameterValueChanges = true; const clap_plugin_t plugin = { clap_get_plugin_descriptor(factory, 0), new PluginCLAP(host), clap_plugin_init, clap_plugin_destroy, clap_plugin_activate, clap_plugin_deactivate, clap_plugin_start_processing, clap_plugin_stop_processing, clap_plugin_reset, clap_plugin_process, clap_plugin_get_extension, clap_plugin_on_main_thread }; std::memcpy(pluginptr, &plugin, sizeof(clap_plugin_t)); return pluginptr; } static const clap_plugin_factory_t clap_plugin_factory = { clap_get_plugin_count, clap_get_plugin_descriptor, clap_create_plugin }; // -------------------------------------------------------------------------------------------------------------------- // plugin entry static bool CLAP_ABI clap_plugin_entry_init(const char* const plugin_path) { static String bundlePath; bundlePath = plugin_path; d_nextBundlePath = bundlePath.buffer(); // init dummy plugin if (sPlugin == nullptr) { // set valid but dummy values d_nextBufferSize = 512; d_nextSampleRate = 44100.0; d_nextPluginIsDummy = true; d_nextCanRequestParameterValueChanges = true; // Create dummy plugin to get data from sPlugin = new PluginExporter(nullptr, nullptr, nullptr, nullptr); // unset d_nextBufferSize = 0; d_nextSampleRate = 0.0; d_nextPluginIsDummy = false; d_nextCanRequestParameterValueChanges = false; } return true; } static void CLAP_ABI clap_plugin_entry_deinit(void) { sPlugin = nullptr; } static const void* CLAP_ABI clap_plugin_entry_get_factory(const char* const factory_id) { if (std::strcmp(factory_id, CLAP_PLUGIN_FACTORY_ID) == 0) return &clap_plugin_factory; return nullptr; } static const clap_plugin_entry_t clap_plugin_entry = { CLAP_VERSION, clap_plugin_entry_init, clap_plugin_entry_deinit, clap_plugin_entry_get_factory }; // -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DISTRHO // -------------------------------------------------------------------------------------------------------------------- DISTRHO_PLUGIN_EXPORT const clap_plugin_entry_t clap_entry = DISTRHO_NAMESPACE::clap_plugin_entry; // --------------------------------------------------------------------------------------------------------------------