Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CarlaPluginJSFX.cpp 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. /*
  2. * Carla JSFX Plugin
  3. * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  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.
  14. *
  15. * For a full copy of the GNU General Public License see the doc/GPL.txt file.
  16. */
  17. // TODO(jsfx) graphics section
  18. // TODO(jsfx) set the correct import path
  19. #include "CarlaPluginInternal.hpp"
  20. #include "CarlaEngine.hpp"
  21. #include "CarlaJsfxUtils.hpp"
  22. #include "CarlaBackendUtils.hpp"
  23. #include "CarlaUtils.hpp"
  24. #include "water/files/File.h"
  25. #include <algorithm>
  26. #include <string>
  27. #include <cstdio>
  28. #include <cstring>
  29. using water::CharPointer_UTF8;
  30. using water::File;
  31. using water::String;
  32. CARLA_BACKEND_START_NAMESPACE
  33. // -------------------------------------------------------------------------------------------------------------------
  34. // Fallback data
  35. static const ExternalMidiNote kExternalMidiNoteFallback = { -1, 0, 0 };
  36. // -------------------------------------------------------------------------------------------------------------------
  37. class CarlaPluginJSFX : public CarlaPlugin
  38. {
  39. public:
  40. CarlaPluginJSFX(CarlaEngine* const engine, const uint id) noexcept
  41. : CarlaPlugin(engine, id)
  42. {
  43. carla_debug("CarlaPluginJSFX::CarlaPluginJSFX(%p, %i)", engine, id);
  44. }
  45. ~CarlaPluginJSFX()
  46. {
  47. carla_debug("CarlaPluginJSFX::~CarlaPluginJSFX()");
  48. pData->singleMutex.lock();
  49. pData->masterMutex.lock();
  50. if (pData->client != nullptr && pData->client->isActive())
  51. pData->client->deactivate(true);
  52. if (pData->active)
  53. {
  54. deactivate();
  55. pData->active = false;
  56. }
  57. if (fEffect != nullptr)
  58. {
  59. delete fEffect;
  60. fEffect = nullptr;
  61. }
  62. if (fFileAPI != nullptr)
  63. {
  64. delete fFileAPI;
  65. fFileAPI = nullptr;
  66. }
  67. if (fPathLibrary != nullptr)
  68. {
  69. delete fPathLibrary;
  70. fPathLibrary = nullptr;
  71. }
  72. clearBuffers();
  73. }
  74. // -------------------------------------------------------------------
  75. // Information (base)
  76. PluginType getType() const noexcept override
  77. {
  78. return PLUGIN_JSFX;
  79. }
  80. // -------------------------------------------------------------------
  81. // Information (count)
  82. uint32_t getMidiInCount() const noexcept override
  83. {
  84. return 1;
  85. }
  86. uint32_t getMidiOutCount() const noexcept override
  87. {
  88. return 1;
  89. }
  90. uint32_t getParameterScalePointCount(const uint32_t parameterId) const noexcept override
  91. {
  92. CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, 0);
  93. const JsusFx_Slider& slider = fEffect->sliders[pData->param.data[parameterId].rindex];
  94. return (uint32_t)slider.enumNames.size();
  95. }
  96. // -------------------------------------------------------------------
  97. // Information (current data)
  98. std::size_t getChunkData(void** dataPtr) noexcept override
  99. {
  100. CARLA_SAFE_ASSERT_RETURN(pData->options & PLUGIN_OPTION_USE_CHUNKS, 0);
  101. CARLA_SAFE_ASSERT_RETURN(dataPtr != nullptr, 0);
  102. JsusFxSerializationData fxData;
  103. CarlaJsusFxSerializer serializer(fxData);
  104. CARLA_SAFE_ASSERT_RETURN(fEffect->serialize(serializer, true), 0);
  105. fChunkText = serializer.convertDataToString(fxData);
  106. *dataPtr = (void*)fChunkText.toRawUTF8();
  107. return (std::size_t)fChunkText.getNumBytesAsUTF8();
  108. }
  109. // -------------------------------------------------------------------
  110. // Information (per-plugin data)
  111. uint getOptionsAvailable() const noexcept override
  112. {
  113. uint options = 0x0;
  114. options |= PLUGIN_OPTION_USE_CHUNKS;
  115. options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES;
  116. options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE;
  117. options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH;
  118. options |= PLUGIN_OPTION_SEND_PITCHBEND;
  119. options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF;
  120. options |= PLUGIN_OPTION_SEND_PROGRAM_CHANGES;
  121. options |= PLUGIN_OPTION_SKIP_SENDING_NOTES;
  122. return options;
  123. }
  124. float getParameterValue(const uint32_t parameterId) const noexcept override
  125. {
  126. CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, 0.0f);
  127. const JsusFx_Slider& slider = fEffect->sliders[pData->param.data[parameterId].rindex];
  128. return slider.getValue();
  129. }
  130. bool getParameterName(const uint32_t parameterId, char* const strBuf) const noexcept override
  131. {
  132. CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr, false);
  133. CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, false);
  134. const JsusFx_Slider& slider = fEffect->sliders[pData->param.data[parameterId].rindex];
  135. std::snprintf(strBuf, STR_MAX, "%s", slider.desc);
  136. return true;
  137. }
  138. bool getParameterText(const uint32_t parameterId, char* const strBuf) noexcept override
  139. {
  140. CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr, false);
  141. CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count, false);
  142. const JsusFx_Slider& slider = fEffect->sliders[pData->param.data[parameterId].rindex];
  143. float value = getParameterValue(parameterId);
  144. int intValue = (int)value;
  145. if (intValue >= 0 && (size_t)intValue < slider.enumNames.size())
  146. std::snprintf(strBuf, STR_MAX, "%s", slider.enumNames[(size_t)intValue].c_str());
  147. else
  148. std::snprintf(strBuf, STR_MAX, "%.12g", value);
  149. return true;
  150. }
  151. float getParameterScalePointValue(const uint32_t parameterId, const uint32_t scalePointId) const noexcept override
  152. {
  153. CARLA_SAFE_ASSERT_RETURN(parameterId < getParameterCount(), 0.0f);
  154. CARLA_SAFE_ASSERT_RETURN(scalePointId < getParameterScalePointCount(parameterId), 0.0f);
  155. return (float)scalePointId;
  156. }
  157. bool getParameterScalePointLabel(const uint32_t parameterId, const uint32_t scalePointId, char* const strBuf) const noexcept override
  158. {
  159. CARLA_SAFE_ASSERT_RETURN(parameterId < getParameterCount(), false);
  160. const JsusFx_Slider& slider = fEffect->sliders[pData->param.data[parameterId].rindex];
  161. CARLA_SAFE_ASSERT_RETURN(scalePointId < slider.enumNames.size(), false);
  162. std::snprintf(strBuf, STR_MAX, "%s", slider.enumNames[(size_t)scalePointId].c_str());
  163. return true;
  164. }
  165. bool getLabel(char* const strBuf) const noexcept override
  166. {
  167. std::strncpy(strBuf, fFilename.c_str(), STR_MAX);
  168. return true;
  169. }
  170. // -------------------------------------------------------------------
  171. // Set data (plugin-specific stuff)
  172. void setParameterValue(const uint32_t parameterId, const float value, const bool sendGui, const bool sendOsc, const bool sendCallback) noexcept override
  173. {
  174. CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr,);
  175. CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count,);
  176. int32_t sliderIndex = pData->param.data[parameterId].rindex;
  177. fEffect->moveSlider(sliderIndex, value);
  178. const JsusFx_Slider& slider = fEffect->sliders[sliderIndex];
  179. float newValue = slider.getValue();
  180. CarlaPlugin::setParameterValue(parameterId, newValue, sendGui, sendOsc, sendCallback);
  181. }
  182. void setParameterValueRT(const uint32_t parameterId, const float value, const bool sendCallbackLater) noexcept override
  183. {
  184. CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr,);
  185. CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count,);
  186. int32_t sliderIndex = pData->param.data[parameterId].rindex;
  187. fEffect->moveSlider(sliderIndex, value);
  188. const JsusFx_Slider& slider = fEffect->sliders[sliderIndex];
  189. float newValue = slider.getValue();
  190. CarlaPlugin::setParameterValueRT(parameterId, newValue, sendCallbackLater);
  191. }
  192. void setChunkData(const void* data, std::size_t dataSize) override
  193. {
  194. CARLA_SAFE_ASSERT_RETURN(pData->options & PLUGIN_OPTION_USE_CHUNKS,);
  195. water::String dataText(water::CharPointer_UTF8((const char*)data), dataSize);
  196. JsusFxSerializationData fxData;
  197. CARLA_SAFE_ASSERT_RETURN(CarlaJsusFxSerializer::convertStringToData(dataText, fxData),);
  198. CarlaJsusFxSerializer serializer(fxData);
  199. CARLA_SAFE_ASSERT_RETURN(fEffect->serialize(serializer, false),);
  200. }
  201. // -------------------------------------------------------------------
  202. // Plugin state
  203. void reload() override
  204. {
  205. CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,);
  206. CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr,);
  207. carla_debug("CarlaPluginHSFX::reload()");
  208. const EngineProcessMode processMode(pData->engine->getProccessMode());
  209. // Safely disable plugin for reload
  210. const ScopedDisabler sd(this);
  211. if (pData->active)
  212. deactivate();
  213. clearBuffers();
  214. // ---------------------------------------------------------------
  215. // TODO(jsfx) uncomment when implementing these features
  216. const int compileFlags = 0
  217. //| JsusFx::kCompileFlag_CompileGraphicsSection
  218. | JsusFx::kCompileFlag_CompileSerializeSection
  219. ;
  220. if (!fEffect->compile(*fPathLibrary, fFilename, compileFlags))
  221. carla_stderr("Failed to compile JSFX");
  222. // NOTE: count can be -1 in case of "none"
  223. uint32_t aIns = (fEffect->numInputs == -1) ? 0 : (uint32_t)fEffect->numInputs;
  224. uint32_t aOuts = (fEffect->numOutputs == -1) ? 0 : (uint32_t)fEffect->numOutputs;
  225. if (aIns > 0)
  226. {
  227. pData->audioIn.createNew(aIns);
  228. }
  229. if (aOuts > 0)
  230. {
  231. pData->audioOut.createNew(aOuts);
  232. }
  233. uint32_t params = 0;
  234. uint32_t mapOfParameterToSlider[JsusFx::kMaxSliders];
  235. for (uint32_t sliderIndex = 0; sliderIndex < JsusFx::kMaxSliders; ++sliderIndex)
  236. {
  237. JsusFx_Slider &slider = fEffect->sliders[sliderIndex];
  238. if (slider.exists)
  239. {
  240. mapOfParameterToSlider[params] = sliderIndex;
  241. ++params;
  242. }
  243. }
  244. if (params > 0)
  245. {
  246. pData->param.createNew(params, false);
  247. }
  248. const uint portNameSize(pData->engine->getMaxPortNameSize());
  249. CarlaString portName;
  250. // Audio Ins
  251. for (uint32_t j = 0; j < aIns; ++j)
  252. {
  253. portName.clear();
  254. if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
  255. {
  256. portName = pData->name;
  257. portName += ":";
  258. }
  259. if (j < fEffect->inputNames.size() && !fEffect->inputNames[j].empty())
  260. {
  261. portName += fEffect->inputNames[j].c_str();
  262. }
  263. else if (aIns > 1)
  264. {
  265. portName += "input_";
  266. portName += CarlaString(j+1);
  267. }
  268. else
  269. portName += "input";
  270. portName.truncate(portNameSize);
  271. pData->audioIn.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, true, j);
  272. pData->audioIn.ports[j].rindex = j;
  273. }
  274. // Audio Outs
  275. for (uint32_t j = 0; j < aOuts; ++j)
  276. {
  277. portName.clear();
  278. if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
  279. {
  280. portName = pData->name;
  281. portName += ":";
  282. }
  283. if (j < fEffect->outputNames.size() && !fEffect->outputNames[j].empty())
  284. {
  285. portName += fEffect->outputNames[j].c_str();
  286. }
  287. else if (aOuts > 1)
  288. {
  289. portName += "output_";
  290. portName += CarlaString(j+1);
  291. }
  292. else
  293. portName += "output";
  294. portName.truncate(portNameSize);
  295. pData->audioOut.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, j);
  296. pData->audioOut.ports[j].rindex = j;
  297. }
  298. // Parameters
  299. for (uint32_t j = 0; j < params; ++j)
  300. {
  301. pData->param.data[j].type = PARAMETER_INPUT;
  302. pData->param.data[j].index = (int32_t)j;
  303. pData->param.data[j].rindex = (int32_t)mapOfParameterToSlider[j];
  304. const JsusFx_Slider &slider = fEffect->sliders[mapOfParameterToSlider[j]];
  305. float min = slider.min;
  306. float max = slider.max;
  307. float def = slider.def;
  308. float step = slider.inc;
  309. // only use values as integer if we have a proper range
  310. bool isEnum = slider.isEnum && min == 0.0f && max >= 0.0f &&
  311. max + 1.0f == (float)slider.enumNames.size();
  312. if (min > max)
  313. std::swap(min, max);
  314. if (def < min)
  315. def = min;
  316. else if (def > max)
  317. def = max;
  318. float stepSmall;
  319. float stepLarge;
  320. if (isEnum)
  321. {
  322. step = 1.0f;
  323. stepSmall = 1.0f;
  324. stepLarge = 10.0f;
  325. }
  326. else
  327. {
  328. stepSmall = step/10.0f;
  329. stepLarge = step*10.0f;
  330. }
  331. pData->param.data[j].hints |= PARAMETER_IS_ENABLED;
  332. if (isEnum)
  333. {
  334. pData->param.data[j].hints |= PARAMETER_IS_INTEGER;
  335. pData->param.data[j].hints |= PARAMETER_USES_SCALEPOINTS;
  336. pData->param.data[j].hints |= PARAMETER_USES_CUSTOM_TEXT;
  337. }
  338. else
  339. {
  340. pData->param.data[j].hints |= PARAMETER_CAN_BE_CV_CONTROLLED;
  341. }
  342. pData->param.ranges[j].min = min;
  343. pData->param.ranges[j].max = max;
  344. pData->param.ranges[j].def = def;
  345. pData->param.ranges[j].step = step;
  346. pData->param.ranges[j].stepSmall = stepSmall;
  347. pData->param.ranges[j].stepLarge = stepLarge;
  348. }
  349. //if (needsCtrlIn)
  350. {
  351. portName.clear();
  352. if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
  353. {
  354. portName = pData->name;
  355. portName += ":";
  356. }
  357. portName += "events-in";
  358. portName.truncate(portNameSize);
  359. pData->event.portIn = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, true, 0);
  360. }
  361. //if (needsCtrlOut)
  362. {
  363. portName.clear();
  364. if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
  365. {
  366. portName = pData->name;
  367. portName += ":";
  368. }
  369. portName += "events-out";
  370. portName.truncate(portNameSize);
  371. pData->event.portOut = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, false, 0);
  372. }
  373. }
  374. // -------------------------------------------------------------------
  375. // Plugin processing
  376. void activate() noexcept override
  377. {
  378. CARLA_SAFE_ASSERT_RETURN(fEffect,);
  379. const double sampleRate = pData->engine->getSampleRate();
  380. const uint32_t bufferSize = pData->engine->getBufferSize();
  381. fEffect->prepare((int)sampleRate, (int)bufferSize);
  382. fTransportValues = TransportValues();
  383. }
  384. void process(const float* const* const audioIn, float** const audioOut,
  385. const float* const* const, float**,
  386. const uint32_t frames) override
  387. {
  388. CARLA_SAFE_ASSERT_RETURN(fEffect,);
  389. // --------------------------------------------------------------------------------------------------------
  390. // Set TimeInfo
  391. const EngineTimeInfo timeInfo = pData->engine->getTimeInfo();
  392. const EngineTimeInfoBBT& bbt = timeInfo.bbt;
  393. fTransportValues.playbackState = timeInfo.playing ?
  394. JsusFx::kPlaybackState_Playing : JsusFx::kPlaybackState_Paused;
  395. fTransportValues.playbackPositionInSeconds = 1e-6*double(timeInfo.usecs);
  396. if (bbt.valid)
  397. {
  398. const double samplePos = double(timeInfo.frame);
  399. const double sampleRate = pData->engine->getSampleRate();
  400. fTransportValues.tempo = bbt.beatsPerMinute;
  401. fTransportValues.beatPosition = samplePos / (sampleRate * 60 / bbt.beatsPerMinute);
  402. fTransportValues.timeSignatureNumerator = int(bbt.beatsPerBar);
  403. fTransportValues.timeSignatureDenumerator = int(bbt.beatType);
  404. }
  405. fEffect->setTransportValues(
  406. fTransportValues.tempo,
  407. fTransportValues.playbackState,
  408. fTransportValues.playbackPositionInSeconds,
  409. fTransportValues.beatPosition,
  410. fTransportValues.timeSignatureNumerator,
  411. fTransportValues.timeSignatureDenumerator);
  412. // --------------------------------------------------------------------------------------------------------
  413. // Event Input and Processing
  414. if (pData->event.portIn != nullptr)
  415. {
  416. // ----------------------------------------------------------------------------------------------------
  417. // MIDI Input (External)
  418. if (pData->extNotes.mutex.tryLock())
  419. {
  420. for (RtLinkedList<ExternalMidiNote>::Itenerator it = pData->extNotes.data.begin2(); it.valid(); it.next())
  421. {
  422. const ExternalMidiNote& note(it.getValue(kExternalMidiNoteFallback));
  423. CARLA_SAFE_ASSERT_CONTINUE(note.channel >= 0 && note.channel < MAX_MIDI_CHANNELS);
  424. uint8_t midiEvent[3];
  425. midiEvent[0] = uint8_t((note.velo > 0 ? MIDI_STATUS_NOTE_ON : MIDI_STATUS_NOTE_OFF) | (note.channel & MIDI_CHANNEL_BIT));
  426. midiEvent[1] = note.note;
  427. midiEvent[2] = note.velo;
  428. fEffect->addInputEvent(0, midiEvent, 3);
  429. }
  430. pData->extNotes.data.clear();
  431. pData->extNotes.mutex.unlock();
  432. } // End of MIDI Input (External)
  433. // ----------------------------------------------------------------------------------------------------
  434. // Event Input (System)
  435. #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
  436. bool allNotesOffSent = false;
  437. #endif
  438. for (uint32_t i=0, numEvents=pData->event.portIn->getEventCount(); i < numEvents; ++i)
  439. {
  440. EngineEvent& event(pData->event.portIn->getEvent(i));
  441. if (event.time >= frames)
  442. continue;
  443. switch (event.type)
  444. {
  445. case kEngineEventTypeNull:
  446. break;
  447. case kEngineEventTypeControl: {
  448. EngineControlEvent& ctrlEvent(event.ctrl);
  449. switch (ctrlEvent.type)
  450. {
  451. case kEngineControlEventTypeNull:
  452. break;
  453. case kEngineControlEventTypeParameter: {
  454. float value;
  455. #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
  456. // non-midi
  457. if (event.channel == kEngineEventNonMidiChannel)
  458. {
  459. const uint32_t k = ctrlEvent.param;
  460. CARLA_SAFE_ASSERT_CONTINUE(k < pData->param.count);
  461. ctrlEvent.handled = true;
  462. value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.normalizedValue);
  463. setParameterValueRT(k, value, true);
  464. continue;
  465. }
  466. // Control backend stuff
  467. if (event.channel == pData->ctrlChannel)
  468. {
  469. if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0)
  470. {
  471. ctrlEvent.handled = true;
  472. value = ctrlEvent.normalizedValue;
  473. setDryWetRT(value, true);
  474. }
  475. else if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
  476. {
  477. ctrlEvent.handled = true;
  478. value = ctrlEvent.normalizedValue*127.0f/100.0f;
  479. setVolumeRT(value, true);
  480. }
  481. else if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
  482. {
  483. float left, right;
  484. value = ctrlEvent.normalizedValue/0.5f - 1.0f;
  485. if (value < 0.0f)
  486. {
  487. left = -1.0f;
  488. right = (value*2.0f)+1.0f;
  489. }
  490. else if (value > 0.0f)
  491. {
  492. left = (value*2.0f)-1.0f;
  493. right = 1.0f;
  494. }
  495. else
  496. {
  497. left = -1.0f;
  498. right = 1.0f;
  499. }
  500. ctrlEvent.handled = true;
  501. setBalanceLeftRT(left, true);
  502. setBalanceRightRT(right, true);
  503. }
  504. }
  505. #endif
  506. // Control plugin parameters
  507. uint32_t k;
  508. for (k=0; k < pData->param.count; ++k)
  509. {
  510. if (pData->param.data[k].midiChannel != event.channel)
  511. continue;
  512. if (pData->param.data[k].mappedControlIndex != ctrlEvent.param)
  513. continue;
  514. if (pData->param.data[k].type != PARAMETER_INPUT)
  515. continue;
  516. if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0)
  517. continue;
  518. ctrlEvent.handled = true;
  519. value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.normalizedValue);
  520. setParameterValueRT(k, value, true);
  521. }
  522. if ((pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) != 0 && ctrlEvent.param < MAX_MIDI_VALUE)
  523. {
  524. uint8_t midiData[3];
  525. midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
  526. midiData[1] = uint8_t(ctrlEvent.param);
  527. midiData[2] = uint8_t(ctrlEvent.normalizedValue*127.0f);
  528. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 3);
  529. }
  530. #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
  531. if (! ctrlEvent.handled)
  532. checkForMidiLearn(event);
  533. #endif
  534. break;
  535. } // case kEngineControlEventTypeParameter
  536. case kEngineControlEventTypeMidiBank:
  537. if ((pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) != 0)
  538. {
  539. uint8_t midiData[3];
  540. midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
  541. midiData[1] = MIDI_CONTROL_BANK_SELECT;
  542. midiData[2] = 0;
  543. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 3);
  544. midiData[1] = MIDI_CONTROL_BANK_SELECT__LSB;
  545. midiData[2] = uint8_t(ctrlEvent.normalizedValue*127.0f);
  546. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 3);
  547. }
  548. break;
  549. case kEngineControlEventTypeMidiProgram:
  550. if (event.channel == pData->ctrlChannel && (pData->options & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) != 0)
  551. {
  552. if (ctrlEvent.param < pData->prog.count)
  553. {
  554. setProgramRT(ctrlEvent.param, true);
  555. }
  556. }
  557. else if ((pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) != 0)
  558. {
  559. uint8_t midiData[3];
  560. midiData[0] = uint8_t(MIDI_STATUS_PROGRAM_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
  561. midiData[1] = uint8_t(ctrlEvent.normalizedValue*127.0f);
  562. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 2);
  563. }
  564. break;
  565. case kEngineControlEventTypeAllSoundOff:
  566. if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
  567. {
  568. uint8_t midiData[3];
  569. midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
  570. midiData[1] = MIDI_CONTROL_ALL_SOUND_OFF;
  571. midiData[2] = 0;
  572. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 3);
  573. }
  574. break;
  575. case kEngineControlEventTypeAllNotesOff:
  576. if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
  577. {
  578. #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
  579. if (event.channel == pData->ctrlChannel && ! allNotesOffSent)
  580. {
  581. allNotesOffSent = true;
  582. postponeRtAllNotesOff();
  583. }
  584. #endif
  585. uint8_t midiData[3];
  586. midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
  587. midiData[1] = MIDI_CONTROL_ALL_NOTES_OFF;
  588. midiData[2] = 0;
  589. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 3);
  590. }
  591. break;
  592. } // switch (ctrlEvent.type)
  593. break;
  594. } // case kEngineEventTypeControl
  595. case kEngineEventTypeMidi: {
  596. const EngineMidiEvent& midiEvent(event.midi);
  597. const uint8_t* const midiData(midiEvent.size > EngineMidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data);
  598. uint8_t status = uint8_t(MIDI_GET_STATUS_FROM_DATA(midiData));
  599. if ((status == MIDI_STATUS_NOTE_OFF || status == MIDI_STATUS_NOTE_ON) && (pData->options & PLUGIN_OPTION_SKIP_SENDING_NOTES))
  600. continue;
  601. if (status == MIDI_STATUS_CHANNEL_PRESSURE && (pData->options & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) == 0)
  602. continue;
  603. if (status == MIDI_STATUS_CONTROL_CHANGE && (pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) == 0)
  604. continue;
  605. if (status == MIDI_STATUS_POLYPHONIC_AFTERTOUCH && (pData->options & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) == 0)
  606. continue;
  607. if (status == MIDI_STATUS_PITCH_WHEEL_CONTROL && (pData->options & PLUGIN_OPTION_SEND_PITCHBEND) == 0)
  608. continue;
  609. // Fix bad note-off
  610. if (status == MIDI_STATUS_NOTE_ON && midiData[2] == 0)
  611. status = MIDI_STATUS_NOTE_OFF;
  612. // put back channel in data
  613. uint8_t midiData2[midiEvent.size];
  614. midiData2[0] = uint8_t(status | (event.channel & MIDI_CHANNEL_BIT));
  615. std::memcpy(midiData2+1, midiData+1, static_cast<std::size_t>(midiEvent.size-1));
  616. fEffect->addInputEvent(static_cast<int>(event.time), midiData2, midiEvent.size);
  617. if (status == MIDI_STATUS_NOTE_ON)
  618. {
  619. pData->postponeNoteOnRtEvent(true, event.channel, midiData[1], midiData[2]);
  620. }
  621. else if (status == MIDI_STATUS_NOTE_OFF)
  622. {
  623. pData->postponeNoteOffRtEvent(true, event.channel, midiData[1]);
  624. }
  625. } break;
  626. } // switch (event.type)
  627. }
  628. pData->postRtEvents.trySplice();
  629. } // End of Event Input and Processing
  630. // --------------------------------------------------------------------------------------------------------
  631. // Plugin processing
  632. fEffect->process((const float**)audioIn, audioOut, (int)frames, fEffect->numInputs, fEffect->numOutputs);
  633. // End of Plugin processing (no events)
  634. // --------------------------------------------------------------------------------------------------------
  635. // MIDI Output
  636. if (pData->event.portOut != nullptr)
  637. {
  638. size_t iterPos = 0;
  639. int samplePosition;
  640. const uint8_t *data;
  641. int numBytes;
  642. while (fEffect->iterateOutputEvents(iterPos, samplePosition, data, numBytes))
  643. {
  644. CARLA_SAFE_ASSERT_BREAK(samplePosition >= 0);
  645. CARLA_SAFE_ASSERT_BREAK(samplePosition < static_cast<int>(frames));
  646. CARLA_SAFE_ASSERT_BREAK(numBytes > 0);
  647. CARLA_SAFE_ASSERT_CONTINUE(numBytes <= 0xff);
  648. if (! pData->event.portOut->writeMidiEvent(static_cast<uint32_t>(samplePosition),
  649. static_cast<uint8_t>(numBytes),
  650. data))
  651. break;
  652. }
  653. } // End of MIDI Output
  654. }
  655. // -------------------------------------------------------------------
  656. bool initJSFX(const CarlaPluginPtr plugin,
  657. const char* const filename, const char* name, const char* const label, const uint options)
  658. {
  659. CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr, false);
  660. // ---------------------------------------------------------------
  661. // first checks
  662. if (pData->client != nullptr)
  663. {
  664. pData->engine->setLastError("Plugin client is already registered");
  665. return false;
  666. }
  667. if (filename == nullptr || filename[0] == '\0')
  668. {
  669. pData->engine->setLastError("null filename");
  670. return false;
  671. }
  672. // ---------------------------------------------------------------
  673. fPathLibrary = new CarlaJsusFxPathLibrary(String(CharPointer_UTF8(filename)));
  674. fFileAPI = new CarlaJsusFxFileAPI;
  675. fEffect = new CarlaJsusFx(*fPathLibrary);
  676. fEffect->fileAPI = fFileAPI;
  677. fFileAPI->init(fEffect->m_vm);
  678. fFilename.assign(filename);
  679. // ---------------------------------------------------------------
  680. // get info
  681. if (!fEffect->readHeader(*fPathLibrary, filename))
  682. {
  683. pData->engine->setLastError("Cannot read the JSFX header");
  684. return false;
  685. }
  686. // jsusfx lacks validity checks currently,
  687. // ensure we have at least the description (required)
  688. if (fEffect->desc[0] == '\0')
  689. {
  690. pData->engine->setLastError("The JSFX header is invalid");
  691. return false;
  692. }
  693. if (name != nullptr && name[0] != '\0')
  694. {
  695. pData->name = pData->engine->getUniquePluginName(name);
  696. }
  697. else
  698. {
  699. String baseName = File(filename).getFileNameWithoutExtension();
  700. pData->name = pData->engine->getUniquePluginName(baseName.toRawUTF8());
  701. }
  702. pData->filename = carla_strdup(filename);
  703. // ---------------------------------------------------------------
  704. // register client
  705. pData->client = pData->engine->addClient(plugin);
  706. if (pData->client == nullptr || ! pData->client->isOk())
  707. {
  708. pData->engine->setLastError("Failed to register plugin client");
  709. return false;
  710. }
  711. // ---------------------------------------------------------------
  712. // set options
  713. pData->options = 0x0;
  714. if (isPluginOptionEnabled(options, PLUGIN_OPTION_USE_CHUNKS))
  715. pData->options |= PLUGIN_OPTION_USE_CHUNKS;
  716. if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CONTROL_CHANGES))
  717. pData->options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES;
  718. if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CHANNEL_PRESSURE))
  719. pData->options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE;
  720. if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_PITCHBEND))
  721. pData->options |= PLUGIN_OPTION_SEND_PITCHBEND;
  722. if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_ALL_SOUND_OFF))
  723. pData->options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF;
  724. if (isPluginOptionEnabled(options, PLUGIN_OPTION_MAP_PROGRAM_CHANGES))
  725. pData->options |= PLUGIN_OPTION_MAP_PROGRAM_CHANGES;
  726. if (isPluginOptionInverseEnabled(options, PLUGIN_OPTION_SKIP_SENDING_NOTES))
  727. pData->options |= PLUGIN_OPTION_SKIP_SENDING_NOTES;
  728. if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH))
  729. pData->options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH;
  730. return true;
  731. }
  732. private:
  733. CarlaJsusFxPathLibrary* fPathLibrary = nullptr;
  734. CarlaJsusFxFileAPI* fFileAPI = nullptr;
  735. CarlaJsusFx* fEffect = nullptr;
  736. std::string fFilename;
  737. water::String fChunkText;
  738. struct TransportValues
  739. {
  740. double tempo = 120;
  741. JsusFx::PlaybackState playbackState = JsusFx::kPlaybackState_Paused;
  742. double playbackPositionInSeconds = 0;
  743. double beatPosition = 0;
  744. int timeSignatureNumerator = 4;
  745. int timeSignatureDenumerator = 4;
  746. };
  747. TransportValues fTransportValues;
  748. };
  749. // -------------------------------------------------------------------------------------------------------------------
  750. CarlaPluginPtr CarlaPlugin::newJSFX(const Initializer& init)
  751. {
  752. carla_debug("CarlaPlugin::newJSFX({%p, \"%s\", \"%s\", \"%s\", " P_INT64 "})",
  753. init.engine, init.filename, init.name, init.label, init.uniqueId);
  754. JsusFx::init();
  755. std::shared_ptr<CarlaPluginJSFX> plugin(new CarlaPluginJSFX(init.engine, init.id));
  756. if (!plugin->initJSFX(plugin, init.filename, init.name, init.label, init.options))
  757. return nullptr;
  758. return plugin;
  759. }
  760. // -------------------------------------------------------------------------------------------------------------------
  761. CARLA_BACKEND_END_NAMESPACE