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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951
  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. JsusFx_Slider& slider = fEffect->sliders[pData->param.data[parameterId].rindex];
  177. slider.setValue(value);
  178. CarlaPlugin::setParameterValue(parameterId, slider.getValue(), sendGui, sendOsc, sendCallback);
  179. }
  180. void setParameterValueRT(const uint32_t parameterId, const float value, const bool sendCallbackLater) noexcept override
  181. {
  182. CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr,);
  183. CARLA_SAFE_ASSERT_RETURN(parameterId < pData->param.count,);
  184. JsusFx_Slider& slider = fEffect->sliders[pData->param.data[parameterId].rindex];
  185. slider.setValue(value);
  186. CarlaPlugin::setParameterValueRT(parameterId, slider.getValue(), sendCallbackLater);
  187. }
  188. void setChunkData(const void* data, std::size_t dataSize) override
  189. {
  190. CARLA_SAFE_ASSERT_RETURN(pData->options & PLUGIN_OPTION_USE_CHUNKS,);
  191. water::String dataText(water::CharPointer_UTF8((const char*)data), dataSize);
  192. JsusFxSerializationData fxData;
  193. CARLA_SAFE_ASSERT_RETURN(CarlaJsusFxSerializer::convertStringToData(dataText, fxData),);
  194. CarlaJsusFxSerializer serializer(fxData);
  195. CARLA_SAFE_ASSERT_RETURN(fEffect->serialize(serializer, false),);
  196. }
  197. // -------------------------------------------------------------------
  198. // Plugin state
  199. void reload() override
  200. {
  201. CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr,);
  202. CARLA_SAFE_ASSERT_RETURN(fEffect != nullptr,);
  203. carla_debug("CarlaPluginHSFX::reload()");
  204. const EngineProcessMode processMode(pData->engine->getProccessMode());
  205. // Safely disable plugin for reload
  206. const ScopedDisabler sd(this);
  207. if (pData->active)
  208. deactivate();
  209. clearBuffers();
  210. // ---------------------------------------------------------------
  211. // TODO(jsfx) uncomment when implementing these features
  212. const int compileFlags = 0
  213. //| JsusFx::kCompileFlag_CompileGraphicsSection
  214. | JsusFx::kCompileFlag_CompileSerializeSection
  215. ;
  216. if (!fEffect->compile(*fPathLibrary, fFilename, compileFlags))
  217. carla_stderr("Failed to compile JSFX");
  218. // NOTE: count can be -1 in case of "none"
  219. uint32_t aIns = (fEffect->numInputs == -1) ? 0 : (uint32_t)fEffect->numInputs;
  220. uint32_t aOuts = (fEffect->numOutputs == -1) ? 0 : (uint32_t)fEffect->numOutputs;
  221. if (aIns > 0)
  222. {
  223. pData->audioIn.createNew(aIns);
  224. }
  225. if (aOuts > 0)
  226. {
  227. pData->audioOut.createNew(aOuts);
  228. }
  229. uint32_t params = 0;
  230. uint32_t mapOfParameterToSlider[JsusFx::kMaxSliders];
  231. for (uint32_t sliderIndex = 0; sliderIndex < JsusFx::kMaxSliders; ++sliderIndex)
  232. {
  233. JsusFx_Slider &slider = fEffect->sliders[sliderIndex];
  234. if (slider.exists)
  235. {
  236. mapOfParameterToSlider[params] = sliderIndex;
  237. ++params;
  238. }
  239. }
  240. if (params > 0)
  241. {
  242. pData->param.createNew(params, false);
  243. }
  244. const uint portNameSize(pData->engine->getMaxPortNameSize());
  245. CarlaString portName;
  246. // Audio Ins
  247. for (uint32_t j = 0; j < aIns; ++j)
  248. {
  249. portName.clear();
  250. if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
  251. {
  252. portName = pData->name;
  253. portName += ":";
  254. }
  255. if (j < fEffect->inputNames.size() && !fEffect->inputNames[j].empty())
  256. {
  257. portName += fEffect->inputNames[j].c_str();
  258. }
  259. else if (aIns > 1)
  260. {
  261. portName += "input_";
  262. portName += CarlaString(j+1);
  263. }
  264. else
  265. portName += "input";
  266. portName.truncate(portNameSize);
  267. pData->audioIn.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, true, j);
  268. pData->audioIn.ports[j].rindex = j;
  269. }
  270. // Audio Outs
  271. for (uint32_t j = 0; j < aOuts; ++j)
  272. {
  273. portName.clear();
  274. if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
  275. {
  276. portName = pData->name;
  277. portName += ":";
  278. }
  279. if (j < fEffect->outputNames.size() && !fEffect->outputNames[j].empty())
  280. {
  281. portName += fEffect->outputNames[j].c_str();
  282. }
  283. else if (aOuts > 1)
  284. {
  285. portName += "output_";
  286. portName += CarlaString(j+1);
  287. }
  288. else
  289. portName += "output";
  290. portName.truncate(portNameSize);
  291. pData->audioOut.ports[j].port = (CarlaEngineAudioPort*)pData->client->addPort(kEnginePortTypeAudio, portName, false, j);
  292. pData->audioOut.ports[j].rindex = j;
  293. }
  294. // Parameters
  295. for (uint32_t j = 0; j < params; ++j)
  296. {
  297. pData->param.data[j].type = PARAMETER_INPUT;
  298. pData->param.data[j].index = (int32_t)j;
  299. pData->param.data[j].rindex = (int32_t)mapOfParameterToSlider[j];
  300. const JsusFx_Slider &slider = fEffect->sliders[mapOfParameterToSlider[j]];
  301. float min = slider.min;
  302. float max = slider.max;
  303. float def = slider.def;
  304. float step = slider.inc;
  305. // only use values as integer if we have a proper range
  306. bool isEnum = slider.isEnum && min == 0.0f && max >= 0.0f &&
  307. max + 1.0f == (float)slider.enumNames.size();
  308. if (min > max)
  309. std::swap(min, max);
  310. if (def < min)
  311. def = min;
  312. else if (def > max)
  313. def = max;
  314. float stepSmall;
  315. float stepLarge;
  316. if (isEnum)
  317. {
  318. step = 1.0f;
  319. stepSmall = 1.0f;
  320. stepLarge = 10.0f;
  321. }
  322. else
  323. {
  324. stepSmall = step/10.0f;
  325. stepLarge = step*10.0f;
  326. }
  327. pData->param.data[j].hints |= PARAMETER_IS_ENABLED;
  328. if (isEnum)
  329. {
  330. pData->param.data[j].hints |= PARAMETER_IS_INTEGER;
  331. pData->param.data[j].hints |= PARAMETER_USES_SCALEPOINTS;
  332. pData->param.data[j].hints |= PARAMETER_USES_CUSTOM_TEXT;
  333. }
  334. else
  335. {
  336. pData->param.data[j].hints |= PARAMETER_CAN_BE_CV_CONTROLLED;
  337. }
  338. pData->param.ranges[j].min = min;
  339. pData->param.ranges[j].max = max;
  340. pData->param.ranges[j].def = def;
  341. pData->param.ranges[j].step = step;
  342. pData->param.ranges[j].stepSmall = stepSmall;
  343. pData->param.ranges[j].stepLarge = stepLarge;
  344. }
  345. //if (needsCtrlIn)
  346. {
  347. portName.clear();
  348. if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
  349. {
  350. portName = pData->name;
  351. portName += ":";
  352. }
  353. portName += "events-in";
  354. portName.truncate(portNameSize);
  355. pData->event.portIn = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, true, 0);
  356. }
  357. //if (needsCtrlOut)
  358. {
  359. portName.clear();
  360. if (processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT)
  361. {
  362. portName = pData->name;
  363. portName += ":";
  364. }
  365. portName += "events-out";
  366. portName.truncate(portNameSize);
  367. pData->event.portOut = (CarlaEngineEventPort*)pData->client->addPort(kEnginePortTypeEvent, portName, false, 0);
  368. }
  369. }
  370. // -------------------------------------------------------------------
  371. // Plugin processing
  372. void activate() noexcept override
  373. {
  374. CARLA_SAFE_ASSERT_RETURN(fEffect,);
  375. const double sampleRate = pData->engine->getSampleRate();
  376. const uint32_t bufferSize = pData->engine->getBufferSize();
  377. fEffect->prepare((int)sampleRate, (int)bufferSize);
  378. fTransportValues = TransportValues();
  379. }
  380. void process(const float* const* const audioIn, float** const audioOut,
  381. const float* const* const, float**,
  382. const uint32_t frames) override
  383. {
  384. CARLA_SAFE_ASSERT_RETURN(fEffect,);
  385. // --------------------------------------------------------------------------------------------------------
  386. // Set TimeInfo
  387. const EngineTimeInfo timeInfo = pData->engine->getTimeInfo();
  388. const EngineTimeInfoBBT& bbt = timeInfo.bbt;
  389. fTransportValues.playbackState = timeInfo.playing ?
  390. JsusFx::kPlaybackState_Playing : JsusFx::kPlaybackState_Paused;
  391. fTransportValues.playbackPositionInSeconds = 1e-6*double(timeInfo.usecs);
  392. if (bbt.valid)
  393. {
  394. const double samplePos = double(timeInfo.frame);
  395. const double sampleRate = pData->engine->getSampleRate();
  396. fTransportValues.tempo = bbt.beatsPerMinute;
  397. fTransportValues.beatPosition = samplePos / (sampleRate * 60 / bbt.beatsPerMinute);
  398. fTransportValues.timeSignatureNumerator = int(bbt.beatsPerBar);
  399. fTransportValues.timeSignatureDenumerator = int(bbt.beatType);
  400. }
  401. fEffect->setTransportValues(
  402. fTransportValues.tempo,
  403. fTransportValues.playbackState,
  404. fTransportValues.playbackPositionInSeconds,
  405. fTransportValues.beatPosition,
  406. fTransportValues.timeSignatureNumerator,
  407. fTransportValues.timeSignatureDenumerator);
  408. // --------------------------------------------------------------------------------------------------------
  409. // Event Input and Processing
  410. if (pData->event.portIn != nullptr)
  411. {
  412. // ----------------------------------------------------------------------------------------------------
  413. // MIDI Input (External)
  414. if (pData->extNotes.mutex.tryLock())
  415. {
  416. for (RtLinkedList<ExternalMidiNote>::Itenerator it = pData->extNotes.data.begin2(); it.valid(); it.next())
  417. {
  418. const ExternalMidiNote& note(it.getValue(kExternalMidiNoteFallback));
  419. CARLA_SAFE_ASSERT_CONTINUE(note.channel >= 0 && note.channel < MAX_MIDI_CHANNELS);
  420. uint8_t midiEvent[3];
  421. midiEvent[0] = uint8_t((note.velo > 0 ? MIDI_STATUS_NOTE_ON : MIDI_STATUS_NOTE_OFF) | (note.channel & MIDI_CHANNEL_BIT));
  422. midiEvent[1] = note.note;
  423. midiEvent[2] = note.velo;
  424. fEffect->addInputEvent(0, midiEvent, 3);
  425. }
  426. pData->extNotes.data.clear();
  427. pData->extNotes.mutex.unlock();
  428. } // End of MIDI Input (External)
  429. // ----------------------------------------------------------------------------------------------------
  430. // Event Input (System)
  431. #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
  432. bool allNotesOffSent = false;
  433. #endif
  434. for (uint32_t i=0, numEvents=pData->event.portIn->getEventCount(); i < numEvents; ++i)
  435. {
  436. EngineEvent& event(pData->event.portIn->getEvent(i));
  437. if (event.time >= frames)
  438. continue;
  439. switch (event.type)
  440. {
  441. case kEngineEventTypeNull:
  442. break;
  443. case kEngineEventTypeControl: {
  444. EngineControlEvent& ctrlEvent(event.ctrl);
  445. switch (ctrlEvent.type)
  446. {
  447. case kEngineControlEventTypeNull:
  448. break;
  449. case kEngineControlEventTypeParameter: {
  450. float value;
  451. #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
  452. // non-midi
  453. if (event.channel == kEngineEventNonMidiChannel)
  454. {
  455. const uint32_t k = ctrlEvent.param;
  456. CARLA_SAFE_ASSERT_CONTINUE(k < pData->param.count);
  457. ctrlEvent.handled = true;
  458. value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.normalizedValue);
  459. setParameterValueRT(k, value, true);
  460. continue;
  461. }
  462. // Control backend stuff
  463. if (event.channel == pData->ctrlChannel)
  464. {
  465. if (MIDI_IS_CONTROL_BREATH_CONTROLLER(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_DRYWET) != 0)
  466. {
  467. ctrlEvent.handled = true;
  468. value = ctrlEvent.normalizedValue;
  469. setDryWetRT(value, true);
  470. }
  471. else if (MIDI_IS_CONTROL_CHANNEL_VOLUME(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_VOLUME) != 0)
  472. {
  473. ctrlEvent.handled = true;
  474. value = ctrlEvent.normalizedValue*127.0f/100.0f;
  475. setVolumeRT(value, true);
  476. }
  477. else if (MIDI_IS_CONTROL_BALANCE(ctrlEvent.param) && (pData->hints & PLUGIN_CAN_BALANCE) != 0)
  478. {
  479. float left, right;
  480. value = ctrlEvent.normalizedValue/0.5f - 1.0f;
  481. if (value < 0.0f)
  482. {
  483. left = -1.0f;
  484. right = (value*2.0f)+1.0f;
  485. }
  486. else if (value > 0.0f)
  487. {
  488. left = (value*2.0f)-1.0f;
  489. right = 1.0f;
  490. }
  491. else
  492. {
  493. left = -1.0f;
  494. right = 1.0f;
  495. }
  496. ctrlEvent.handled = true;
  497. setBalanceLeftRT(left, true);
  498. setBalanceRightRT(right, true);
  499. }
  500. }
  501. #endif
  502. // Control plugin parameters
  503. uint32_t k;
  504. for (k=0; k < pData->param.count; ++k)
  505. {
  506. if (pData->param.data[k].midiChannel != event.channel)
  507. continue;
  508. if (pData->param.data[k].mappedControlIndex != ctrlEvent.param)
  509. continue;
  510. if (pData->param.data[k].type != PARAMETER_INPUT)
  511. continue;
  512. if ((pData->param.data[k].hints & PARAMETER_IS_AUTOMABLE) == 0)
  513. continue;
  514. ctrlEvent.handled = true;
  515. value = pData->param.getFinalUnnormalizedValue(k, ctrlEvent.normalizedValue);
  516. setParameterValueRT(k, value, true);
  517. }
  518. if ((pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) != 0 && ctrlEvent.param < MAX_MIDI_VALUE)
  519. {
  520. uint8_t midiData[3];
  521. midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
  522. midiData[1] = uint8_t(ctrlEvent.param);
  523. midiData[2] = uint8_t(ctrlEvent.normalizedValue*127.0f);
  524. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 3);
  525. }
  526. #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
  527. if (! ctrlEvent.handled)
  528. checkForMidiLearn(event);
  529. #endif
  530. break;
  531. } // case kEngineControlEventTypeParameter
  532. case kEngineControlEventTypeMidiBank:
  533. if ((pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) != 0)
  534. {
  535. uint8_t midiData[3];
  536. midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
  537. midiData[1] = MIDI_CONTROL_BANK_SELECT;
  538. midiData[2] = 0;
  539. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 3);
  540. midiData[1] = MIDI_CONTROL_BANK_SELECT__LSB;
  541. midiData[2] = uint8_t(ctrlEvent.normalizedValue*127.0f);
  542. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 3);
  543. }
  544. break;
  545. case kEngineControlEventTypeMidiProgram:
  546. if (event.channel == pData->ctrlChannel && (pData->options & PLUGIN_OPTION_MAP_PROGRAM_CHANGES) != 0)
  547. {
  548. if (ctrlEvent.param < pData->prog.count)
  549. {
  550. setProgramRT(ctrlEvent.param, true);
  551. }
  552. }
  553. else if ((pData->options & PLUGIN_OPTION_SEND_PROGRAM_CHANGES) != 0)
  554. {
  555. uint8_t midiData[3];
  556. midiData[0] = uint8_t(MIDI_STATUS_PROGRAM_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
  557. midiData[1] = uint8_t(ctrlEvent.normalizedValue*127.0f);
  558. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 2);
  559. }
  560. break;
  561. case kEngineControlEventTypeAllSoundOff:
  562. if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
  563. {
  564. uint8_t midiData[3];
  565. midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
  566. midiData[1] = MIDI_CONTROL_ALL_SOUND_OFF;
  567. midiData[2] = 0;
  568. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 3);
  569. }
  570. break;
  571. case kEngineControlEventTypeAllNotesOff:
  572. if (pData->options & PLUGIN_OPTION_SEND_ALL_SOUND_OFF)
  573. {
  574. #ifndef BUILD_BRIDGE_ALTERNATIVE_ARCH
  575. if (event.channel == pData->ctrlChannel && ! allNotesOffSent)
  576. {
  577. allNotesOffSent = true;
  578. postponeRtAllNotesOff();
  579. }
  580. #endif
  581. uint8_t midiData[3];
  582. midiData[0] = uint8_t(MIDI_STATUS_CONTROL_CHANGE | (event.channel & MIDI_CHANNEL_BIT));
  583. midiData[1] = MIDI_CONTROL_ALL_NOTES_OFF;
  584. midiData[2] = 0;
  585. fEffect->addInputEvent(static_cast<int>(event.time), midiData, 3);
  586. }
  587. break;
  588. } // switch (ctrlEvent.type)
  589. break;
  590. } // case kEngineEventTypeControl
  591. case kEngineEventTypeMidi: {
  592. const EngineMidiEvent& midiEvent(event.midi);
  593. const uint8_t* const midiData(midiEvent.size > EngineMidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data);
  594. uint8_t status = uint8_t(MIDI_GET_STATUS_FROM_DATA(midiData));
  595. if ((status == MIDI_STATUS_NOTE_OFF || status == MIDI_STATUS_NOTE_ON) && (pData->options & PLUGIN_OPTION_SKIP_SENDING_NOTES))
  596. continue;
  597. if (status == MIDI_STATUS_CHANNEL_PRESSURE && (pData->options & PLUGIN_OPTION_SEND_CHANNEL_PRESSURE) == 0)
  598. continue;
  599. if (status == MIDI_STATUS_CONTROL_CHANGE && (pData->options & PLUGIN_OPTION_SEND_CONTROL_CHANGES) == 0)
  600. continue;
  601. if (status == MIDI_STATUS_POLYPHONIC_AFTERTOUCH && (pData->options & PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH) == 0)
  602. continue;
  603. if (status == MIDI_STATUS_PITCH_WHEEL_CONTROL && (pData->options & PLUGIN_OPTION_SEND_PITCHBEND) == 0)
  604. continue;
  605. // Fix bad note-off
  606. if (status == MIDI_STATUS_NOTE_ON && midiData[2] == 0)
  607. status = MIDI_STATUS_NOTE_OFF;
  608. // put back channel in data
  609. uint8_t midiData2[midiEvent.size];
  610. midiData2[0] = uint8_t(status | (event.channel & MIDI_CHANNEL_BIT));
  611. std::memcpy(midiData2+1, midiData+1, static_cast<std::size_t>(midiEvent.size-1));
  612. fEffect->addInputEvent(static_cast<int>(event.time), midiData2, midiEvent.size);
  613. if (status == MIDI_STATUS_NOTE_ON)
  614. {
  615. pData->postponeNoteOnRtEvent(true, event.channel, midiData[1], midiData[2]);
  616. }
  617. else if (status == MIDI_STATUS_NOTE_OFF)
  618. {
  619. pData->postponeNoteOffRtEvent(true, event.channel, midiData[1]);
  620. }
  621. } break;
  622. } // switch (event.type)
  623. }
  624. pData->postRtEvents.trySplice();
  625. } // End of Event Input and Processing
  626. // --------------------------------------------------------------------------------------------------------
  627. // Plugin processing
  628. fEffect->process((const float**)audioIn, audioOut, (int)frames, fEffect->numInputs, fEffect->numOutputs);
  629. // End of Plugin processing (no events)
  630. // --------------------------------------------------------------------------------------------------------
  631. // MIDI Output
  632. if (pData->event.portOut != nullptr)
  633. {
  634. size_t iterPos = 0;
  635. int samplePosition;
  636. const uint8_t *data;
  637. int numBytes;
  638. while (fEffect->iterateOutputEvents(iterPos, samplePosition, data, numBytes))
  639. {
  640. CARLA_SAFE_ASSERT_BREAK(samplePosition >= 0);
  641. CARLA_SAFE_ASSERT_BREAK(samplePosition < static_cast<int>(frames));
  642. CARLA_SAFE_ASSERT_BREAK(numBytes > 0);
  643. CARLA_SAFE_ASSERT_CONTINUE(numBytes <= 0xff);
  644. if (! pData->event.portOut->writeMidiEvent(static_cast<uint32_t>(samplePosition),
  645. static_cast<uint8_t>(numBytes),
  646. data))
  647. break;
  648. }
  649. } // End of MIDI Output
  650. }
  651. // -------------------------------------------------------------------
  652. bool initJSFX(const CarlaPluginPtr plugin,
  653. const char* const filename, const char* name, const char* const label, const uint options)
  654. {
  655. CARLA_SAFE_ASSERT_RETURN(pData->engine != nullptr, false);
  656. // ---------------------------------------------------------------
  657. // first checks
  658. if (pData->client != nullptr)
  659. {
  660. pData->engine->setLastError("Plugin client is already registered");
  661. return false;
  662. }
  663. if (filename == nullptr || filename[0] == '\0')
  664. {
  665. pData->engine->setLastError("null filename");
  666. return false;
  667. }
  668. // ---------------------------------------------------------------
  669. fPathLibrary = new CarlaJsusFxPathLibrary(String(CharPointer_UTF8(filename)));
  670. fFileAPI = new CarlaJsusFxFileAPI;
  671. fEffect = new CarlaJsusFx(*fPathLibrary);
  672. fEffect->fileAPI = fFileAPI;
  673. fFileAPI->init(fEffect->m_vm);
  674. fFilename.assign(filename);
  675. // ---------------------------------------------------------------
  676. // get info
  677. if (!fEffect->readHeader(*fPathLibrary, filename))
  678. {
  679. pData->engine->setLastError("Cannot read the JSFX header");
  680. return false;
  681. }
  682. // jsusfx lacks validity checks currently,
  683. // ensure we have at least the description (required)
  684. if (fEffect->desc[0] == '\0')
  685. {
  686. pData->engine->setLastError("The JSFX header is invalid");
  687. return false;
  688. }
  689. if (name != nullptr && name[0] != '\0')
  690. {
  691. pData->name = pData->engine->getUniquePluginName(name);
  692. }
  693. else
  694. {
  695. String baseName = File(filename).getFileNameWithoutExtension();
  696. pData->name = pData->engine->getUniquePluginName(baseName.toRawUTF8());
  697. }
  698. pData->filename = carla_strdup(filename);
  699. // ---------------------------------------------------------------
  700. // register client
  701. pData->client = pData->engine->addClient(plugin);
  702. if (pData->client == nullptr || ! pData->client->isOk())
  703. {
  704. pData->engine->setLastError("Failed to register plugin client");
  705. return false;
  706. }
  707. // ---------------------------------------------------------------
  708. // set options
  709. pData->options = 0x0;
  710. if (isPluginOptionEnabled(options, PLUGIN_OPTION_USE_CHUNKS))
  711. pData->options |= PLUGIN_OPTION_USE_CHUNKS;
  712. if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CONTROL_CHANGES))
  713. pData->options |= PLUGIN_OPTION_SEND_CONTROL_CHANGES;
  714. if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_CHANNEL_PRESSURE))
  715. pData->options |= PLUGIN_OPTION_SEND_CHANNEL_PRESSURE;
  716. if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_PITCHBEND))
  717. pData->options |= PLUGIN_OPTION_SEND_PITCHBEND;
  718. if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_ALL_SOUND_OFF))
  719. pData->options |= PLUGIN_OPTION_SEND_ALL_SOUND_OFF;
  720. if (isPluginOptionEnabled(options, PLUGIN_OPTION_MAP_PROGRAM_CHANGES))
  721. pData->options |= PLUGIN_OPTION_MAP_PROGRAM_CHANGES;
  722. if (isPluginOptionInverseEnabled(options, PLUGIN_OPTION_SKIP_SENDING_NOTES))
  723. pData->options |= PLUGIN_OPTION_SKIP_SENDING_NOTES;
  724. if (isPluginOptionEnabled(options, PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH))
  725. pData->options |= PLUGIN_OPTION_SEND_NOTE_AFTERTOUCH;
  726. return true;
  727. }
  728. private:
  729. CarlaJsusFxPathLibrary* fPathLibrary = nullptr;
  730. CarlaJsusFxFileAPI* fFileAPI = nullptr;
  731. CarlaJsusFx* fEffect = nullptr;
  732. std::string fFilename;
  733. water::String fChunkText;
  734. struct TransportValues
  735. {
  736. double tempo = 120;
  737. JsusFx::PlaybackState playbackState = JsusFx::kPlaybackState_Paused;
  738. double playbackPositionInSeconds = 0;
  739. double beatPosition = 0;
  740. int timeSignatureNumerator = 4;
  741. int timeSignatureDenumerator = 4;
  742. };
  743. TransportValues fTransportValues;
  744. };
  745. // -------------------------------------------------------------------------------------------------------------------
  746. CarlaPluginPtr CarlaPlugin::newJSFX(const Initializer& init)
  747. {
  748. carla_debug("CarlaPlugin::newJSFX({%p, \"%s\", \"%s\", \"%s\", " P_INT64 "})",
  749. init.engine, init.filename, init.name, init.label, init.uniqueId);
  750. JsusFx::init();
  751. std::shared_ptr<CarlaPluginJSFX> plugin(new CarlaPluginJSFX(init.engine, init.id));
  752. if (!plugin->initJSFX(plugin, init.filename, init.name, init.label, init.options))
  753. return nullptr;
  754. return plugin;
  755. }
  756. // -------------------------------------------------------------------------------------------------------------------
  757. CARLA_BACKEND_END_NAMESPACE