Fix crash with clap plugins without MIDI input
[carla.git] / source / backend / plugin / CarlaPluginJSFX.cpp
blobf5e6f43fd11c6efdc334bd033248545c1cb749ba
1 /*
2 * Carla JSFX Plugin
3 * Copyright (C) 2021-2023 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.
18 // TODO(jsfx) graphics section
20 #include "CarlaPluginInternal.hpp"
21 #include "CarlaEngine.hpp"
23 #ifdef HAVE_YSFX
25 #include "CarlaJsfxUtils.hpp"
26 #include "CarlaBackendUtils.hpp"
27 #include "CarlaUtils.hpp"
29 #include "water/files/File.h"
30 #include "water/text/StringArray.h"
32 #include <algorithm>
33 #include <cstdio>
34 #include <cstring>
36 using water::CharPointer_UTF8;
37 using water::File;
38 using water::String;
39 using water::StringArray;
41 CARLA_BACKEND_START_NAMESPACE
43 // -------------------------------------------------------------------------------------------------------------------
44 // Fallback data
46 static const ExternalMidiNote kExternalMidiNoteFallback = { -1, 0, 0 };
48 // -------------------------------------------------------------------------------------------------------------------
50 class CarlaPluginJSFX : public CarlaPlugin
52 public:
53 CarlaPluginJSFX(CarlaEngine* const engine, const uint id) noexcept
54 : CarlaPlugin(engine, id),
55 fEffect(nullptr),
56 fEffectState(nullptr),
57 fUnit(),
58 fChunkText(),
59 fTransportValues(),
60 fMapOfSliderToParameter(ysfx_max_sliders, -1)
62 carla_debug("CarlaPluginJSFX::CarlaPluginJSFX(%p, %i)", engine, id);
64 carla_zeroStruct(fTransportValues);
67 ~CarlaPluginJSFX() noexcept override
69 carla_debug("CarlaPluginJSFX::~CarlaPluginJSFX()");
71 pData->singleMutex.lock();
72 pData->masterMutex.lock();
74 if (pData->client != nullptr && pData->client->isActive())
75 pData->client->deactivate(true);
77 if (pData->active)
79 deactivate();
80 pData->active = false;
83 clearBuffers();
85 ysfx_state_free(fEffectState);
86 ysfx_free(fEffect);
89 // -------------------------------------------------------------------
90 // Information (base)
92 PluginType getType() const noexcept override
94 return PLUGIN_JSFX;
97 PluginCategory getCategory() const noexcept override
99 CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr, CarlaPlugin::getCategory());
101 return CarlaJsfxCategories::getFromEffect(fEffect);
104 uint32_t getLatencyInFrames() const noexcept override
106 CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr, 0);
108 ysfx_real sampleRate = ysfx_get_sample_rate(fEffect);
109 ysfx_real latencyInSeconds = ysfx_get_pdc_delay(fEffect);
111 //NOTE: `pdc_bot_ch` and `pdc_top_ch` channel range ignored
113 int32_t latencyInFrames = water::roundToInt(latencyInSeconds * sampleRate);
114 wassert(latencyInFrames >= 0);
116 return (uint32_t)latencyInFrames;
119 // -------------------------------------------------------------------
120 // Information (count)
122 uint32_t getMidiInCount() const noexcept override
124 return 1;
127 uint32_t getMidiOutCount() const noexcept override
129 return 1;
132 uint32_t getParameterScalePointCount(const uint32_t parameterId) const noexcept override
134 CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, 0);
136 const uint32_t rindex = static_cast<uint32_t>(pData->param.data[parameterId].rindex);
137 return ysfx_slider_get_enum_names(fEffect, rindex, nullptr, 0);;
140 // -------------------------------------------------------------------
141 // Information (current data)
143 std::size_t getChunkData(void** const dataPtr) noexcept override
145 CARLA_SAFE_ASSERT_RETURN(pData->options & PLUGIN_OPTION_USE_CHUNKS, 0);
146 CARLA_SAFE_ASSERT_RETURN(dataPtr != nullptr, 0);
148 ysfx_state_free(fEffectState);
149 fEffectState = ysfx_save_state(fEffect);
150 CARLA_SAFE_ASSERT_RETURN(fEffectState != nullptr, 0);
152 *dataPtr = fEffectState->data;
153 return fEffectState->data_size;
156 // -------------------------------------------------------------------
157 // Information (per-plugin data)
159 uint getOptionsAvailable() const noexcept override
161 uint options = 0x0;
163 options |= PLUGIN_OPTION_USE_CHUNKS;
165 options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES;
166 options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE;
167 options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH;
168 options |= PLUGIN_OPTION_SEND_PITCHBEND;
169 options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF;
170 options |= PLUGIN_OPTION_SEND_PROGRAM_CHANGES;
171 options |= PLUGIN_OPTION_SKIP_SENDING_NOTES;
173 return options;
176 float getParameterValue(const uint32_t parameterId) const noexcept override
178 CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, 0.0f);
180 const uint32_t rindex = static_cast<uint32_t>(pData->param.data[parameterId].rindex);
181 return static_cast<float>(ysfx_slider_get_value(fEffect, rindex));
184 bool getParameterName(const uint32_t parameterId, char* const strBuf) const noexcept override
186 CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr, false);
187 CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, false);
189 const uint32_t rindex = static_cast<uint32_t>(pData->param.data[parameterId].rindex);
191 if (const char* const name = ysfx_slider_get_name(fEffect, rindex))
193 std::snprintf(strBuf, STR_MAX, "%s", name);
194 return true;
197 return false;
200 float getParameterScalePointValue(const uint32_t parameterId, const uint32_t scalePointId) const noexcept override
202 CARLA_SAFE_ASSERT_RETURN(parameterId < getParameterCount(), 0.0f);
203 CARLA_SAFE_ASSERT_RETURN(scalePointId < getParameterScalePointCount(parameterId), 0.0f);
204 return (float)scalePointId;
207 bool getParameterScalePointLabel(const uint32_t parameterId, const uint32_t scalePointId, char* const strBuf) const noexcept override
209 CARLA_SAFE_ASSERT_RETURN(parameterId < getParameterCount(), false);
211 const uint32_t rindex = static_cast<uint32_t>(pData->param.data[parameterId].rindex);
213 const uint32_t enumCount = ysfx_slider_get_enum_names(fEffect, rindex, nullptr, 0);
214 CARLA_SAFE_ASSERT_RETURN(scalePointId < enumCount, false);
216 if (const char* const name = ysfx_slider_get_enum_name(fEffect, rindex, scalePointId))
218 std::snprintf(strBuf, STR_MAX, "%s", name);
219 return true;
222 return false;
225 bool getLabel(char* const strBuf) const noexcept override
227 std::strncpy(strBuf, fUnit.getFileId().toRawUTF8(), STR_MAX);
228 return true;
231 // -------------------------------------------------------------------
232 // Set data (plugin-specific stuff)
234 void setParameterValue(const uint32_t parameterId, const float value, const bool sendGui, const bool sendOsc, const bool sendCallback) noexcept override
236 CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr,);
237 CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count,);
239 const uint32_t rindex = static_cast<uint32_t>(pData->param.data[parameterId].rindex);
240 ysfx_slider_set_value(fEffect, rindex, value);
242 CarlaPlugin::setParameterValue(parameterId, value, sendGui, sendOsc, sendCallback);
245 void setParameterValueRT(const uint32_t parameterId, const float value, const uint32_t frameOffset, const bool sendCallbackLater) noexcept override
247 CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr,);
248 CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count,);
250 const uint32_t rindex = static_cast<uint32_t>(pData->param.data[parameterId].rindex);
251 ysfx_slider_set_value(fEffect, rindex, value);
253 CarlaPlugin::setParameterValueRT(parameterId, value, frameOffset, sendCallbackLater);
256 void setChunkData(const void* data, std::size_t dataSize) override
258 CARLA_SAFE_ASSERT_RETURN(pData->options & PLUGIN_OPTION_USE_CHUNKS,);
260 ysfx_state_t state;
261 state.sliders = nullptr;
262 state.slider_count = 0;
263 state.data = static_cast<uint8_t*>(const_cast<void*>(data));
264 state.data_size = dataSize;
266 CARLA_SAFE_ASSERT(ysfx_load_state(fEffect, &state));
269 // -------------------------------------------------------------------
270 // Plugin state
272 void reload() override
274 CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,);
275 CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr,);
276 carla_debug("CarlaPluginJSFX::reload()");
278 const EngineProcessMode processMode(pData->engine->getProccessMode());
280 // Safely disable plugin for reload
281 const ScopedDisabler sd(this);
283 if (pData->active)
284 deactivate();
286 clearBuffers();
288 // ---------------------------------------------------------------
290 // initialize the block size and sample rate
291 // loading the chunk can invoke @slider which makes computations based on these
292 ysfx_set_sample_rate(fEffect, pData->engine->getSampleRate());
293 ysfx_set_block_size(fEffect, (uint32_t)pData->engine->getBufferSize());
294 ysfx_init(fEffect);
296 const uint32_t aIns = ysfx_get_num_inputs(fEffect);
297 const uint32_t aOuts = ysfx_get_num_outputs(fEffect);
299 // perhaps we obtained a latency value from @init
300 pData->client->setLatency(getLatencyInFrames());
302 if (aIns > 0)
304 pData->audioIn.createNew(aIns);
307 if (aOuts > 0)
309 pData->audioOut.createNew(aOuts);
312 // count the sliders and establish the mappings between parameter and slider
313 uint32_t params = 0;
314 uint32_t mapOfParameterToSlider[ysfx_max_sliders];
315 for (uint32_t rindex = 0; rindex < ysfx_max_sliders; ++rindex)
317 if (ysfx_slider_exists(fEffect, rindex))
319 mapOfParameterToSlider[params] = rindex;
320 fMapOfSliderToParameter[rindex] = (int32_t)params;
321 ++params;
323 else
325 fMapOfSliderToParameter[rindex] = -1;
329 if (params > 0)
331 pData->param.createNew(params, false);
334 const uint portNameSize = pData->engine->getMaxPortNameSize();
335 CarlaString portName;
337 // Audio Ins
338 for (uint32_t j = 0; j < aIns; ++j)
340 portName.clear();
342 if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
344 portName = pData->name;
345 portName += ":";
348 const char* const inputName = ysfx_get_input_name(fEffect, j);
349 if (inputName && inputName[0])
351 portName += inputName;
353 else if (aIns > 1)
355 portName += "input_";
356 portName += CarlaString(j+1);
358 else
359 portName += "input";
361 portName.truncate(portNameSize);
363 pData->audioIn.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, true, j);
364 pData->audioIn.ports[j].rindex = j;
367 // Audio Outs
368 for (uint32_t j = 0; j < aOuts; ++j)
370 portName.clear();
372 if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
374 portName = pData->name;
375 portName += ":";
378 const char* const outputName = ysfx_get_input_name(fEffect, j);
379 if (outputName && outputName[0])
381 portName += outputName;
383 else if (aOuts > 1)
385 portName += "output_";
386 portName += CarlaString(j+1);
388 else
389 portName += "output";
391 portName.truncate(portNameSize);
393 pData->audioOut.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, j);
394 pData->audioOut.ports[j].rindex = j;
397 // Parameters
398 for (uint32_t j = 0; j < params; ++j)
400 const uint32_t rindex = mapOfParameterToSlider[j];
401 pData->param.data[j].type = PARAMETER_INPUT;
402 pData->param.data[j].index = (int32_t)j;
403 pData->param.data[j].rindex = (int32_t)rindex;
405 ysfx_slider_range_t range = {};
406 ysfx_slider_get_range(fEffect, rindex, &range);
408 float min = (float)range.min;
409 float max = (float)range.max;
410 float def = (float)range.def;
411 float step = (float)range.inc;
412 float stepSmall;
413 float stepLarge;
415 // only use values as integer if we have a proper range
416 const bool isEnum = ysfx_slider_is_enum(fEffect, rindex) &&
417 carla_isZero(min) &&
418 max >= 0.0f &&
419 carla_isEqual(max + 1.0f, static_cast<float>(ysfx_slider_get_enum_names(fEffect, rindex, nullptr, 0)));
421 // NOTE: in case of incomplete slider specification without <min,max,step>;
422 // these are usually output-only sliders.
423 if (carla_isEqual(min, max))
425 // replace with a dummy range
426 min = 0.0f;
427 max = 1.0f;
430 if (min > max)
431 std::swap(min, max);
433 if (def < min)
434 def = min;
435 else if (def > max)
436 def = max;
438 pData->param.data[j].hints |= PARAMETER_IS_ENABLED;
440 if (isEnum)
442 step = 1.0f;
443 stepSmall = 1.0f;
444 stepLarge = 10.0f;
445 pData->param.data[j].hints |= PARAMETER_IS_INTEGER;
446 pData->param.data[j].hints |= PARAMETER_USES_SCALEPOINTS;
448 else
450 stepSmall = step/10.0f;
451 stepLarge = step*10.0f;
452 pData->param.data[j].hints |= PARAMETER_CAN_BE_CV_CONTROLLED;
455 pData->param.ranges[j].min = min;
456 pData->param.ranges[j].max = max;
457 pData->param.ranges[j].def = def;
458 pData->param.ranges[j].step = step;
459 pData->param.ranges[j].stepSmall = stepSmall;
460 pData->param.ranges[j].stepLarge = stepLarge;
463 //if (needsCtrlIn)
465 portName.clear();
467 if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
469 portName = pData->name;
470 portName += ":";
473 portName += "events-in";
474 portName.truncate(portNameSize);
476 pData->event.portIn = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, true, 0);
479 //if (needsCtrlOut)
481 portName.clear();
483 if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
485 portName = pData->name;
486 portName += ":";
489 portName += "events-out";
490 portName.truncate(portNameSize);
492 pData->event.portOut = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, false, 0);
496 // -------------------------------------------------------------------
497 // Plugin processing
499 void activate() noexcept override
501 CARLA_SAFE_ASSERT_RETURN(fEffect,);
503 ysfx_set_sample_rate(fEffect, pData->engine->getSampleRate());
504 ysfx_set_block_size(fEffect, (uint32_t)pData->engine->getBufferSize());
505 ysfx_init(fEffect);
507 fTransportValues.tempo = 120;
508 fTransportValues.playback_state = ysfx_playback_paused;
509 fTransportValues.time_position = 0;
510 fTransportValues.beat_position = 0;
511 fTransportValues.time_signature[0] = 4;
512 fTransportValues.time_signature[1] = 4;
515 void process(const float* const* const audioIn, float** const audioOut,
516 const float* const* const, float**,
517 const uint32_t frames) override
519 CARLA_SAFE_ASSERT_RETURN(fEffect,);
521 // --------------------------------------------------------------------------------------------------------
522 // Set TimeInfo
524 const EngineTimeInfo timeInfo = pData->engine->getTimeInfo();
525 const EngineTimeInfoBBT& bbt = timeInfo.bbt;
527 fTransportValues.playback_state = timeInfo.playing ?
528 ysfx_playback_playing : ysfx_playback_paused;
529 fTransportValues.time_position = 1e-6*double(timeInfo.usecs);
531 if (bbt.valid)
533 const double samplePos = double(timeInfo.frame);
534 const double sampleRate = pData->engine->getSampleRate();
535 fTransportValues.tempo = bbt.beatsPerMinute;
536 fTransportValues.beat_position = samplePos / (sampleRate * 60 / bbt.beatsPerMinute);
537 fTransportValues.time_signature[0] = (uint32_t)bbt.beatsPerBar;
538 fTransportValues.time_signature[1] = (uint32_t)bbt.beatType;
541 ysfx_set_time_info(fEffect, &fTransportValues);
543 // --------------------------------------------------------------------------------------------------------
544 // Event Input and Processing
546 if (pData->event.portIn != nullptr)
548 // ----------------------------------------------------------------------------------------------------
549 // MIDI Input (External)
551 if (pData->extNotes.mutex.tryLock())
553 for (RtLinkedList<ExternalMidiNote>::Itenerator it = pData->extNotes.data.begin2(); it.valid(); it.next())
555 const ExternalMidiNote& note(it.getValue(kExternalMidiNoteFallback));
556 CARLA_SAFE_ASSERT_CONTINUE(note.channel >= 0 && note.channel < MAX_MIDI_CHANNELS);
558 uint8_t midiData[3];
559 midiData[0] = uint8_t((note.velo > 0 ? MIDI_STATUS_NOTE_ON : MIDI_STATUS_NOTE_OFF) | (note.channel & MIDI_CHANNEL_BIT));
560 midiData[1] = note.note;
561 midiData[2] = note.velo;
563 ysfx_midi_event_t event;
564 event.bus = 0;
565 event.offset = 0;
566 event.size = 3;
567 event.data = midiData;
568 ysfx_send_midi(fEffect, &event);
571 pData->extNotes.data.clear();
572 pData->extNotes.mutex.unlock();
574 } // End of MIDI Input (External)
576 // ----------------------------------------------------------------------------------------------------
577 // Event Input (System)
579 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
580 bool allNotesOffSent = false;
581 #endif
583 for (uint32_t i=0, numEvents=pData->event.portIn->getEventCount(); i < numEvents; ++i)
585 EngineEvent& event(pData->event.portIn->getEvent(i));
587 if (event.time >= frames)
588 continue;
590 switch (event.type)
592 case kEngineEventTypeNull:
593 break;
595 case kEngineEventTypeControl: {
596 EngineControlEvent& ctrlEvent(event.ctrl);
598 switch (ctrlEvent.type)
600 case kEngineControlEventTypeNull:
601 break;
603 case kEngineControlEventTypeParameter: {
604 float value;
606 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
607 // non-midi
608 if (event.channel == kEngineEventNonMidiChannel)
610 const uint32_t k = ctrlEvent.param;
611 CARLA_SAFE_ASSERT_CONTINUE(k < pData->param.count);
613 ctrlEvent.handled = true;
614 value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.normalizedValue);
615 setParameterValueRT(k, value, event.time, true);
616 continue;
619 // Control backend stuff
620 if (event.channel == pData->ctrlChannel)
622 if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0)
624 ctrlEvent.handled = true;
625 value = ctrlEvent.normalizedValue;
626 setDryWetRT(value, true);
628 else if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
630 ctrlEvent.handled = true;
631 value = ctrlEvent.normalizedValue*127.0f/100.0f;
632 setVolumeRT(value, true);
634 else if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
636 float left, right;
637 value = ctrlEvent.normalizedValue/0.5f - 1.0f;
639 if (value < 0.0f)
641 left = -1.0f;
642 right = (value*2.0f)+1.0f;
644 else if (value > 0.0f)
646 left = (value*2.0f)-1.0f;
647 right = 1.0f;
649 else
651 left = -1.0f;
652 right = 1.0f;
655 ctrlEvent.handled = true;
656 setBalanceLeftRT(left, true);
657 setBalanceRightRT(right, true);
660 #endif
661 // Control plugin parameters
662 uint32_t k;
663 for (k=0; k < pData->param.count; ++k)
665 if (pData->param.data[k].midiChannel != event.channel)
666 continue;
667 if (pData->param.data[k].mappedControlIndex != ctrlEvent.param)
668 continue;
669 if (pData->param.data[k].type != PARAMETER_INPUT)
670 continue;
671 if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0)
672 continue;
674 ctrlEvent.handled = true;
675 value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.normalizedValue);
676 setParameterValueRT(k, value, event.time, true);
679 if ((pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) != 0 && ctrlEvent.param < MAX_MIDI_VALUE)
681 uint8_t midiData[3];
682 midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
683 midiData[1] = uint8_t(ctrlEvent.param);
684 midiData[2] = uint8_t(ctrlEvent.normalizedValue*127.0f);
686 ysfx_midi_event_t yevent;
687 yevent.bus = 0;
688 yevent.offset = event.time;
689 yevent.size = 3;
690 yevent.data = midiData;
691 ysfx_send_midi(fEffect, &yevent);
694 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
695 if (! ctrlEvent.handled)
696 checkForMidiLearn(event);
697 #endif
698 break;
699 } // case kEngineControlEventTypeParameter
701 case kEngineControlEventTypeMidiBank:
702 if ((pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) != 0)
704 uint8_t midiData[3];
705 midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
706 midiData[1] = MIDI_CONTROL_BANK_SELECT;
707 midiData[2] = 0;
709 ysfx_midi_event_t yevent;
710 yevent.bus = 0;
711 yevent.offset = event.time;
712 yevent.size = 3;
713 yevent.data = midiData;
714 ysfx_send_midi(fEffect, &yevent);
716 midiData[1] = MIDI_CONTROL_BANK_SELECT__LSB;
717 midiData[2] = uint8_t(ctrlEvent.normalizedValue*127.0f);
718 yevent.bus = 0;
719 yevent.offset = event.time;
720 yevent.size = 3;
721 yevent.data = midiData;
722 ysfx_send_midi(fEffect, &yevent);
724 break;
726 case kEngineControlEventTypeMidiProgram:
727 if (event.channel == pData->ctrlChannel && (pData->options & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) != 0)
729 if (ctrlEvent.param < pData->prog.count)
731 setProgramRT(ctrlEvent.param, true);
734 else if ((pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) != 0)
736 uint8_t midiData[2];
737 midiData[0] = uint8_t(MIDI_STATUS_PROGRAM_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
738 midiData[1] = uint8_t(ctrlEvent.normalizedValue*127.0f);
739 ysfx_midi_event_t yevent;
740 yevent.bus = 0;
741 yevent.offset = event.time;
742 yevent.size = 2;
743 yevent.data = midiData;
744 ysfx_send_midi(fEffect, &yevent);
746 break;
748 case kEngineControlEventTypeAllSoundOff:
749 if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
751 uint8_t midiData[3];
752 midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
753 midiData[1] = MIDI_CONTROL_ALL_SOUND_OFF;
754 midiData[2] = 0;
755 ysfx_midi_event_t yevent;
756 yevent.bus = 0;
757 yevent.offset = event.time;
758 yevent.size = 3;
759 yevent.data = midiData;
760 ysfx_send_midi(fEffect, &yevent);
762 break;
764 case kEngineControlEventTypeAllNotesOff:
765 if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
767 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
768 if (event.channel == pData->ctrlChannel && ! allNotesOffSent)
770 allNotesOffSent = true;
771 postponeRtAllNotesOff();
773 #endif
775 uint8_t midiData[3];
776 midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
777 midiData[1] = MIDI_CONTROL_ALL_NOTES_OFF;
778 midiData[2] = 0;
780 ysfx_midi_event_t yevent;
781 yevent.bus = 0;
782 yevent.offset = event.time;
783 yevent.size = 3;
784 yevent.data = midiData;
785 ysfx_send_midi(fEffect, &yevent);
787 break;
788 } // switch (ctrlEvent.type)
789 break;
790 } // case kEngineEventTypeControl
792 case kEngineEventTypeMidi: {
793 const EngineMidiEvent& midiEvent(event.midi);
795 if (midiEvent.size > EngineMidiEvent::kDataSize)
796 continue;
798 uint8_t status = uint8_t(MIDI_GET_STATUS_FROM_DATA(midiEvent.data));
800 if ((status == MIDI_STATUS_NOTE_OFF || status == MIDI_STATUS_NOTE_ON) && (pData->options & PLUGIN_OPTION_SKIP_SENDING_NOTES))
801 continue;
802 if (status == MIDI_STATUS_CHANNEL_PRESSURE && (pData->options & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) == 0)
803 continue;
804 if (status == MIDI_STATUS_CONTROL_CHANGE && (pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) == 0)
805 continue;
806 if (status == MIDI_STATUS_POLYPHONIC_AFTERTOUCH && (pData->options & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) == 0)
807 continue;
808 if (status == MIDI_STATUS_PITCH_WHEEL_CONTROL && (pData->options & PLUGIN_OPTION_SEND_PITCHBEND) == 0)
809 continue;
811 // Fix bad note-off
812 if (status == MIDI_STATUS_NOTE_ON && midiEvent.data[2] == 0)
813 status = MIDI_STATUS_NOTE_OFF;
815 // put back channel in data
816 uint8_t midiData2[EngineMidiEvent::kDataSize];
817 midiData2[0] = uint8_t(status | (event.channel & MIDI_CHANNEL_BIT));
818 std::memcpy(midiData2 + 1, midiEvent.data + 1, static_cast<std::size_t>(midiEvent.size - 1));
820 ysfx_midi_event_t yevent;
821 yevent.bus = midiEvent.port;
822 yevent.offset = event.time;
823 yevent.size = midiEvent.size;
824 yevent.data = midiData2;
825 ysfx_send_midi(fEffect, &yevent);
827 if (status == MIDI_STATUS_NOTE_ON)
829 pData->postponeNoteOnRtEvent(true, event.channel, midiEvent.data[1], midiEvent.data[2]);
831 else if (status == MIDI_STATUS_NOTE_OFF)
833 pData->postponeNoteOffRtEvent(true, event.channel, midiEvent.data[1]);
835 } break;
836 } // switch (event.type)
839 pData->postRtEvents.trySplice();
841 } // End of Event Input and Processing
843 // --------------------------------------------------------------------------------------------------------
844 // Plugin processing
846 const uint32_t numInputs = ysfx_get_num_inputs(fEffect);
847 const uint32_t numOutputs = ysfx_get_num_outputs(fEffect);
848 ysfx_process_float(fEffect, audioIn, audioOut, numInputs, numOutputs, frames);
850 // End of Plugin processing (no events)
852 // --------------------------------------------------------------------------------------------------------
853 // MIDI Output
855 if (pData->event.portOut != nullptr)
857 ysfx_midi_event_t event;
859 while (ysfx_receive_midi(fEffect, &event))
861 CARLA_SAFE_ASSERT_BREAK(event.offset < frames);
862 CARLA_SAFE_ASSERT_BREAK(event.size > 0);
863 CARLA_SAFE_ASSERT_CONTINUE(event.size <= 0xff);
865 if (! pData->event.portOut->writeMidiEvent(event.offset,
866 static_cast<uint8_t>(event.size),
867 event.data))
868 break;
871 } // End of MIDI Output
873 // --------------------------------------------------------------------------------------------------------
874 // Control Output
877 uint64_t changes = ysfx_fetch_slider_changes(fEffect);
878 uint64_t automations = ysfx_fetch_slider_automations(fEffect);
880 if ((changes|automations) != 0)
882 for (uint32_t rindex = 0; rindex < ysfx_max_sliders; ++rindex)
884 uint64_t mask = (uint64_t)1 << rindex;
886 //TODO: automations and changes are handled identically
887 // refer to `sliderchange` vs `slider_automate`
889 if (((changes|automations) & mask) != 0)
891 int32_t parameterIndex = fMapOfSliderToParameter[rindex];
892 CARLA_SAFE_ASSERT_CONTINUE(parameterIndex != -1);
894 const float newValue = static_cast<float>(ysfx_slider_get_value(fEffect, (uint32_t)parameterIndex));
895 setParameterValueRT((uint32_t)parameterIndex, newValue, 0, true);
900 //TODO: slider visibility changes, if this feature can be supported
904 // -------------------------------------------------------------------
906 bool initJSFX(const CarlaPluginPtr plugin,
907 const char* const filename, const char* name, const char* const label, const uint options)
909 CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr, false);
911 // ---------------------------------------------------------------
912 // first checks
914 if (pData->client != nullptr)
916 pData->engine->setLastError("Plugin client is already registered");
917 return false;
920 if ((filename == nullptr || filename[0] == '\0') &&
921 (label == nullptr || label[0] == '\0'))
923 pData->engine->setLastError("null filename and label");
924 return false;
927 // ---------------------------------------------------------------
929 fUnit = CarlaJsfxUnit();
932 StringArray splitPaths;
934 if (const char* paths = pData->engine->getOptions().pathJSFX)
935 splitPaths = StringArray::fromTokens(CharPointer_UTF8(paths), CARLA_OS_SPLIT_STR, "");
937 File file;
938 if (filename && filename[0] != '\0')
939 file = File(CharPointer_UTF8(filename));
941 if (file.isNotNull() && file.existsAsFile())
943 // find which engine search path we're in, and use this as the root
944 for (int i = 0; i < splitPaths.size() && !fUnit; ++i)
946 const File currentPath(splitPaths[i]);
947 if (file.isAChildOf(currentPath))
948 fUnit = CarlaJsfxUnit(currentPath, file);
951 // if not found in engine search paths, use parent directory as the root
952 if (! fUnit)
953 fUnit = CarlaJsfxUnit(file.getParentDirectory(), file);
955 else if (label && label[0] != '\0')
957 // search a matching file in plugin paths
958 for (int i = 0; i < splitPaths.size() && !fUnit; ++i)
960 const File currentPath(splitPaths[i]);
961 const File currentFile = currentPath.getChildFile(CharPointer_UTF8(label));
962 const CarlaJsfxUnit currentUnit(currentPath, currentFile);
963 if (File(currentUnit.getFilePath()).existsAsFile())
964 fUnit = currentUnit;
969 if (! fUnit)
971 pData->engine->setLastError("Cannot locate the JSFX plugin");
972 return false;
975 // ---------------------------------------------------------------
977 ysfx_config_u config(ysfx_config_new());
978 CARLA_SAFE_ASSERT_RETURN(config != nullptr, false);
980 const water::String rootPath = fUnit.getRootPath();
981 const water::String filePath = fUnit.getFilePath();
983 ysfx_register_builtin_audio_formats(config.get());
984 ysfx_set_import_root(config.get(), rootPath.toRawUTF8());
985 ysfx_guess_file_roots(config.get(), filePath.toRawUTF8());
986 ysfx_set_log_reporter(config.get(), &CarlaJsfxLogging::logAll);
987 ysfx_set_user_data(config.get(), (intptr_t)this);
989 fEffect = ysfx_new(config.get());
990 CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr, false);
992 // ---------------------------------------------------------------
993 // get info
996 if (! ysfx_load_file(fEffect, filePath.toRawUTF8(), 0))
998 pData->engine->setLastError("Failed to load JSFX");
999 return false;
1002 // TODO(jsfx) adapt when implementing these features
1003 const int compileFlags = 0
1004 //| ysfx_compile_no_serialize
1005 | ysfx_compile_no_gfx
1008 if (! ysfx_compile(fEffect, compileFlags))
1010 pData->engine->setLastError("Failed to compile JSFX");
1011 return false;
1015 if (name != nullptr && name[0] != '\0')
1017 pData->name = pData->engine->getUniquePluginName(name);
1019 else
1021 pData->name = carla_strdup(ysfx_get_name(fEffect));
1024 pData->filename = carla_strdup(filePath.toRawUTF8());
1026 // ---------------------------------------------------------------
1027 // register client
1029 pData->client = pData->engine->addClient(plugin);
1031 if (pData->client == nullptr || ! pData->client->isOk())
1033 pData->engine->setLastError("Failed to register plugin client");
1034 return false;
1037 // ---------------------------------------------------------------
1038 // set options
1040 pData->options = 0x0;
1042 if (isPluginOptionEnabled(options, PLUGIN_OPTION_USE_CHUNKS))
1043 pData->options |= PLUGIN_OPTION_USE_CHUNKS;
1045 if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CONTROL_CHANGES))
1046 pData->options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES;
1047 if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CHANNEL_PRESSURE))
1048 pData->options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE;
1049 if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_PITCHBEND))
1050 pData->options |= PLUGIN_OPTION_SEND_PITCHBEND;
1051 if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_ALL_SOUND_OFF))
1052 pData->options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF;
1053 if (isPluginOptionEnabled(options, PLUGIN_OPTION_MAP_PROGRAM_CHANGES))
1054 pData->options |= PLUGIN_OPTION_MAP_PROGRAM_CHANGES;
1055 if (isPluginOptionInverseEnabled(options, PLUGIN_OPTION_SKIP_SENDING_NOTES))
1056 pData->options |= PLUGIN_OPTION_SKIP_SENDING_NOTES;
1057 if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH))
1058 pData->options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH;
1060 return true;
1063 private:
1064 ysfx_t* fEffect;
1065 ysfx_state_t* fEffectState;
1066 CarlaJsfxUnit fUnit;
1067 water::String fChunkText;
1068 ysfx_time_info_t fTransportValues;
1069 std::vector<int32_t> fMapOfSliderToParameter;
1071 CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaPluginJSFX)
1074 CARLA_BACKEND_END_NAMESPACE
1076 #endif // HAVE_YSFX
1078 CARLA_BACKEND_START_NAMESPACE
1080 // -------------------------------------------------------------------------------------------------------------------
1082 CarlaPluginPtr CarlaPlugin::newJSFX(const Initializer& init)
1084 carla_debug("CarlaPlugin::newJSFX({%p, \"%s\", \"%s\", \"%s\", " P_INT64 "})",
1085 init.engine, init.filename, init.name, init.label, init.uniqueId);
1087 #ifdef HAVE_YSFX
1088 std::shared_ptr<CarlaPluginJSFX> plugin(new CarlaPluginJSFX(init.engine, init.id));
1090 if (! plugin->initJSFX(plugin, init.filename, init.name, init.label, init.options))
1091 return nullptr;
1093 return plugin;
1094 #else
1095 init.engine->setLastError("JSFX support not available");
1096 return nullptr;
1097 #endif
1100 // -------------------------------------------------------------------------------------------------------------------
1102 CARLA_BACKEND_END_NAMESPACE