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"
25 #include "CarlaJsfxUtils.hpp"
26 #include "CarlaBackendUtils.hpp"
27 #include "CarlaUtils.hpp"
29 #include "water/files/File.h"
30 #include "water/text/StringArray.h"
36 using water::CharPointer_UTF8
;
39 using water::StringArray
;
41 CARLA_BACKEND_START_NAMESPACE
43 // -------------------------------------------------------------------------------------------------------------------
46 static const ExternalMidiNote kExternalMidiNoteFallback
= { -1, 0, 0 };
48 // -------------------------------------------------------------------------------------------------------------------
50 class CarlaPluginJSFX
: public CarlaPlugin
53 CarlaPluginJSFX(CarlaEngine
* const engine
, const uint id
) noexcept
54 : CarlaPlugin(engine
, id
),
56 fEffectState(nullptr),
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);
80 pData
->active
= false;
85 ysfx_state_free(fEffectState
);
89 // -------------------------------------------------------------------
92 PluginType
getType() const noexcept override
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
127 uint32_t getMidiOutCount() const noexcept override
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
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
;
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
);
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
);
225 bool getLabel(char* const strBuf
) const noexcept override
227 std::strncpy(strBuf
, fUnit
.getFileId().toRawUTF8(), STR_MAX
);
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
,);
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 // -------------------------------------------------------------------
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);
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());
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());
304 pData
->audioIn
.createNew(aIns
);
309 pData
->audioOut
.createNew(aOuts
);
312 // count the sliders and establish the mappings between parameter and slider
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
;
325 fMapOfSliderToParameter
[rindex
] = -1;
331 pData
->param
.createNew(params
, false);
334 const uint portNameSize
= pData
->engine
->getMaxPortNameSize();
335 CarlaString portName
;
338 for (uint32_t j
= 0; j
< aIns
; ++j
)
342 if (processMode
== ENGINE_PROCESS_MODE_SINGLE_CLIENT
)
344 portName
= pData
->name
;
348 const char* const inputName
= ysfx_get_input_name(fEffect
, j
);
349 if (inputName
&& inputName
[0])
351 portName
+= inputName
;
355 portName
+= "input_";
356 portName
+= CarlaString(j
+1);
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
;
368 for (uint32_t j
= 0; j
< aOuts
; ++j
)
372 if (processMode
== ENGINE_PROCESS_MODE_SINGLE_CLIENT
)
374 portName
= pData
->name
;
378 const char* const outputName
= ysfx_get_input_name(fEffect
, j
);
379 if (outputName
&& outputName
[0])
381 portName
+= outputName
;
385 portName
+= "output_";
386 portName
+= CarlaString(j
+1);
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
;
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
;
415 // only use values as integer if we have a proper range
416 const bool isEnum
= ysfx_slider_is_enum(fEffect
, rindex
) &&
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
438 pData
->param
.data
[j
].hints
|= PARAMETER_IS_ENABLED
;
445 pData
->param
.data
[j
].hints
|= PARAMETER_IS_INTEGER
;
446 pData
->param
.data
[j
].hints
|= PARAMETER_USES_SCALEPOINTS
;
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
;
467 if (processMode
== ENGINE_PROCESS_MODE_SINGLE_CLIENT
)
469 portName
= pData
->name
;
473 portName
+= "events-in";
474 portName
.truncate(portNameSize
);
476 pData
->event
.portIn
= (CarlaEngineEventPort
*)pData
->client
->addPort(kEnginePortTypeEvent
, portName
, true, 0);
483 if (processMode
== ENGINE_PROCESS_MODE_SINGLE_CLIENT
)
485 portName
= pData
->name
;
489 portName
+= "events-out";
490 portName
.truncate(portNameSize
);
492 pData
->event
.portOut
= (CarlaEngineEventPort
*)pData
->client
->addPort(kEnginePortTypeEvent
, portName
, false, 0);
496 // -------------------------------------------------------------------
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());
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 // --------------------------------------------------------------------------------------------------------
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
);
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
);
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
;
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;
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
)
592 case kEngineEventTypeNull
:
595 case kEngineEventTypeControl
: {
596 EngineControlEvent
& ctrlEvent(event
.ctrl
);
598 switch (ctrlEvent
.type
)
600 case kEngineControlEventTypeNull
:
603 case kEngineControlEventTypeParameter
: {
606 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
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);
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)
637 value
= ctrlEvent
.normalizedValue
/0.5f
- 1.0f
;
642 right
= (value
*2.0f
)+1.0f
;
644 else if (value
> 0.0f
)
646 left
= (value
*2.0f
)-1.0f
;
655 ctrlEvent
.handled
= true;
656 setBalanceLeftRT(left
, true);
657 setBalanceRightRT(right
, true);
661 // Control plugin parameters
663 for (k
=0; k
< pData
->param
.count
; ++k
)
665 if (pData
->param
.data
[k
].midiChannel
!= event
.channel
)
667 if (pData
->param
.data
[k
].mappedControlIndex
!= ctrlEvent
.param
)
669 if (pData
->param
.data
[k
].type
!= PARAMETER_INPUT
)
671 if ((pData
->param
.data
[k
].hints
& PARAMETER_IS_AUTOMABLE
) == 0)
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
)
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
;
688 yevent
.offset
= event
.time
;
690 yevent
.data
= midiData
;
691 ysfx_send_midi(fEffect
, &yevent
);
694 #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
695 if (! ctrlEvent
.handled
)
696 checkForMidiLearn(event
);
699 } // case kEngineControlEventTypeParameter
701 case kEngineControlEventTypeMidiBank
:
702 if ((pData
->options
& PLUGIN_OPTION_SEND_PROGRAM_CHANGES
) != 0)
705 midiData
[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE
| (event
.channel
& MIDI_CHANNEL_BIT
));
706 midiData
[1] = MIDI_CONTROL_BANK_SELECT
;
709 ysfx_midi_event_t yevent
;
711 yevent
.offset
= event
.time
;
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
);
719 yevent
.offset
= event
.time
;
721 yevent
.data
= midiData
;
722 ysfx_send_midi(fEffect
, &yevent
);
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)
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
;
741 yevent
.offset
= event
.time
;
743 yevent
.data
= midiData
;
744 ysfx_send_midi(fEffect
, &yevent
);
748 case kEngineControlEventTypeAllSoundOff
:
749 if (pData
->options
& PLUGIN_OPTION_SEND_ALL_SOUND_OFF
)
752 midiData
[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE
| (event
.channel
& MIDI_CHANNEL_BIT
));
753 midiData
[1] = MIDI_CONTROL_ALL_SOUND_OFF
;
755 ysfx_midi_event_t yevent
;
757 yevent
.offset
= event
.time
;
759 yevent
.data
= midiData
;
760 ysfx_send_midi(fEffect
, &yevent
);
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();
776 midiData
[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE
| (event
.channel
& MIDI_CHANNEL_BIT
));
777 midiData
[1] = MIDI_CONTROL_ALL_NOTES_OFF
;
780 ysfx_midi_event_t yevent
;
782 yevent
.offset
= event
.time
;
784 yevent
.data
= midiData
;
785 ysfx_send_midi(fEffect
, &yevent
);
788 } // switch (ctrlEvent.type)
790 } // case kEngineEventTypeControl
792 case kEngineEventTypeMidi
: {
793 const EngineMidiEvent
& midiEvent(event
.midi
);
795 if (midiEvent
.size
> EngineMidiEvent::kDataSize
)
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
))
802 if (status
== MIDI_STATUS_CHANNEL_PRESSURE
&& (pData
->options
& PLUGIN_OPTION_SEND_CHANNEL_PRESSURE
) == 0)
804 if (status
== MIDI_STATUS_CONTROL_CHANGE
&& (pData
->options
& PLUGIN_OPTION_SEND_CONTROL_CHANGES
) == 0)
806 if (status
== MIDI_STATUS_POLYPHONIC_AFTERTOUCH
&& (pData
->options
& PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH
) == 0)
808 if (status
== MIDI_STATUS_PITCH_WHEEL_CONTROL
&& (pData
->options
& PLUGIN_OPTION_SEND_PITCHBEND
) == 0)
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]);
836 } // switch (event.type)
839 pData
->postRtEvents
.trySplice();
841 } // End of Event Input and Processing
843 // --------------------------------------------------------------------------------------------------------
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 // --------------------------------------------------------------------------------------------------------
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
),
871 } // End of MIDI Output
873 // --------------------------------------------------------------------------------------------------------
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 // ---------------------------------------------------------------
914 if (pData
->client
!= nullptr)
916 pData
->engine
->setLastError("Plugin client is already registered");
920 if ((filename
== nullptr || filename
[0] == '\0') &&
921 (label
== nullptr || label
[0] == '\0'))
923 pData
->engine
->setLastError("null filename and label");
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
, "");
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
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())
971 pData
->engine
->setLastError("Cannot locate the JSFX plugin");
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 // ---------------------------------------------------------------
996 if (! ysfx_load_file(fEffect
, filePath
.toRawUTF8(), 0))
998 pData
->engine
->setLastError("Failed to load JSFX");
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");
1015 if (name
!= nullptr && name
[0] != '\0')
1017 pData
->name
= pData
->engine
->getUniquePluginName(name
);
1021 pData
->name
= carla_strdup(ysfx_get_name(fEffect
));
1024 pData
->filename
= carla_strdup(filePath
.toRawUTF8());
1026 // ---------------------------------------------------------------
1029 pData
->client
= pData
->engine
->addClient(plugin
);
1031 if (pData
->client
== nullptr || ! pData
->client
->isOk())
1033 pData
->engine
->setLastError("Failed to register plugin client");
1037 // ---------------------------------------------------------------
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
;
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
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
);
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
))
1095 init
.engine
->setLastError("JSFX support not available");
1100 // -------------------------------------------------------------------------------------------------------------------
1102 CARLA_BACKEND_END_NAMESPACE