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.

689 lines
22KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2022 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 3 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 LICENSE file.
  16. */
  17. #include <juce_audio_processors/juce_audio_processors.h>
  18. #include <AvailabilityMacros.h>
  19. #if MAC_OS_X_VERSION_MAX_ALLOWED > 101200
  20. #error unwanted macOS version, too new
  21. #endif
  22. #define createPlugin createStaticPlugin
  23. #include "src/DistrhoPluginInternal.hpp"
  24. #include "src/DistrhoUIInternal.hpp"
  25. START_NAMESPACE_DISTRHO
  26. // --------------------------------------------------------------------------------------------------------------------
  27. class ParameterFromDPF : public juce::AudioProcessorParameter
  28. {
  29. PluginExporter& plugin;
  30. const ParameterEnumerationValues& enumValues;
  31. const ParameterRanges& ranges;
  32. const uint32_t hints;
  33. const uint index;
  34. bool* const updatedPtr;
  35. mutable juce::StringArray dpfValueStrings;
  36. public:
  37. ParameterFromDPF(PluginExporter& plugin_, const uint index_, bool* const updatedPtr_)
  38. : plugin(plugin_),
  39. enumValues(plugin_.getParameterEnumValues(index_)),
  40. ranges(plugin_.getParameterRanges(index_)),
  41. hints(plugin_.getParameterHints(index_)),
  42. index(index_),
  43. updatedPtr(updatedPtr_) {}
  44. void setValueNotifyingHostFromDPF(const float newValue)
  45. {
  46. setValueNotifyingHost(ranges.getNormalizedValue(newValue));
  47. *updatedPtr = false;
  48. }
  49. protected:
  50. float getValue() const override
  51. {
  52. return ranges.getNormalizedValue(plugin.getParameterValue(index));
  53. }
  54. void setValue(const float newValue) override
  55. {
  56. *updatedPtr = true;
  57. plugin.setParameterValue(index, ranges.getUnnormalizedValue(newValue));
  58. }
  59. float getDefaultValue() const override
  60. {
  61. return ranges.getNormalizedValue(plugin.getParameterDefault(index));
  62. }
  63. juce::String getName(const int maximumStringLength) const override
  64. {
  65. if (maximumStringLength <= 0)
  66. return juce::String(plugin.getParameterName(index).buffer());
  67. return juce::String(plugin.getParameterName(index).buffer(), static_cast<size_t>(maximumStringLength));
  68. }
  69. juce::String getLabel() const override
  70. {
  71. return plugin.getParameterUnit(index).buffer();
  72. }
  73. int getNumSteps() const override
  74. {
  75. if (hints & kParameterIsBoolean)
  76. return 2;
  77. if (enumValues.restrictedMode)
  78. return enumValues.count;
  79. if (hints & kParameterIsInteger)
  80. return ranges.max - ranges.min;
  81. return juce::AudioProcessorParameter::getNumSteps();
  82. }
  83. bool isDiscrete() const override
  84. {
  85. if (hints & (kParameterIsBoolean|kParameterIsInteger))
  86. return true;
  87. if (enumValues.restrictedMode)
  88. return true;
  89. return false;
  90. }
  91. bool isBoolean() const override
  92. {
  93. return (hints & kParameterIsBoolean) != 0x0;
  94. }
  95. juce::String getText(const float normalizedValue, const int maximumStringLength) const override
  96. {
  97. float value = ranges.getUnnormalizedValue(normalizedValue);
  98. if (hints & kParameterIsBoolean)
  99. {
  100. const float midRange = ranges.min + (ranges.max - ranges.min) * 0.5f;
  101. value = value > midRange ? ranges.max : ranges.min;
  102. }
  103. else if (hints & kParameterIsInteger)
  104. {
  105. value = std::round(value);
  106. }
  107. if (enumValues.restrictedMode)
  108. {
  109. for (uint32_t i=0; i < enumValues.count; ++i)
  110. {
  111. if (d_isEqual(enumValues.values[i].value, value))
  112. {
  113. if (maximumStringLength <= 0)
  114. return juce::String(enumValues.values[i].label);
  115. return juce::String(enumValues.values[i].label, static_cast<size_t>(maximumStringLength));
  116. }
  117. }
  118. }
  119. juce::String text;
  120. if (hints & kParameterIsInteger)
  121. text = juce::String(static_cast<int>(value));
  122. else
  123. text = juce::String(value);
  124. if (maximumStringLength <= 0)
  125. return text;
  126. return juce::String(text.toRawUTF8(), static_cast<size_t>(maximumStringLength));
  127. }
  128. float getValueForText(const juce::String& text) const override
  129. {
  130. if (enumValues.restrictedMode)
  131. {
  132. for (uint32_t i=0; i < enumValues.count; ++i)
  133. {
  134. if (text == enumValues.values[i].label.buffer())
  135. return ranges.getNormalizedValue(enumValues.values[i].value);
  136. }
  137. }
  138. float value;
  139. if (hints & kParameterIsInteger)
  140. value = std::atoi(text.toRawUTF8());
  141. else
  142. value = std::atof(text.toRawUTF8());
  143. return ranges.getFixedAndNormalizedValue(value);
  144. }
  145. bool isAutomatable() const override
  146. {
  147. return (hints & kParameterIsAutomatable) != 0x0;
  148. }
  149. juce::String getCurrentValueAsText() const override
  150. {
  151. const float value = plugin.getParameterValue(index);
  152. if (enumValues.restrictedMode)
  153. {
  154. for (uint32_t i=0; i < enumValues.count; ++i)
  155. {
  156. if (d_isEqual(enumValues.values[i].value, value))
  157. return juce::String(enumValues.values[i].label);
  158. }
  159. }
  160. if (hints & kParameterIsInteger)
  161. return juce::String(static_cast<int>(value));
  162. return juce::String(value);
  163. }
  164. juce::StringArray getAllValueStrings() const override
  165. {
  166. if (dpfValueStrings.size() != 0)
  167. return dpfValueStrings;
  168. if (enumValues.restrictedMode)
  169. {
  170. for (uint32_t i=0; i < enumValues.count; ++i)
  171. dpfValueStrings.add(enumValues.values[i].label.buffer());
  172. return dpfValueStrings;
  173. }
  174. if (hints & kParameterIsBoolean)
  175. {
  176. if (hints & kParameterIsInteger)
  177. {
  178. dpfValueStrings.add(juce::String(static_cast<int>(ranges.min)));
  179. dpfValueStrings.add(juce::String(static_cast<int>(ranges.max)));
  180. }
  181. else
  182. {
  183. dpfValueStrings.add(juce::String(ranges.min));
  184. dpfValueStrings.add(juce::String(ranges.max));
  185. }
  186. }
  187. else if (hints & kParameterIsInteger)
  188. {
  189. const int imin = static_cast<int>(ranges.min);
  190. const int imax = static_cast<int>(ranges.max);
  191. for (int i=imin; i<=imax; ++i)
  192. dpfValueStrings.add(juce::String(i));
  193. }
  194. return dpfValueStrings;
  195. }
  196. };
  197. // --------------------------------------------------------------------------------------------------------------------
  198. // unused in cardinal
  199. static constexpr const requestParameterValueChangeFunc nullRequestParameterValueChangeFunc = nullptr;
  200. // only needed for headless builds, which this wrapper never builds for
  201. static constexpr const updateStateValueFunc nullUpdateStateValueFunc = nullptr;
  202. // DSP/processor implementation
  203. class CardinalWrapperProcessor : public juce::AudioProcessor
  204. {
  205. friend class CardinalWrapperEditor;
  206. PluginExporter plugin;
  207. MidiEvent midiEvents[kMaxMidiEvents];
  208. TimePosition timePosition;
  209. const uint32_t parameterCount;
  210. juce::AudioProcessorParameter* bypassParameter;
  211. juce::MidiBuffer* currentMidiMessages;
  212. bool* updatedParameters;
  213. public:
  214. CardinalWrapperProcessor()
  215. : plugin(this, writeMidiFunc, nullRequestParameterValueChangeFunc, nullUpdateStateValueFunc),
  216. parameterCount(plugin.getParameterCount()),
  217. bypassParameter(nullptr),
  218. currentMidiMessages(nullptr),
  219. updatedParameters(nullptr)
  220. {
  221. if (const double sampleRate = getSampleRate())
  222. if (sampleRate > 0.0)
  223. plugin.setSampleRate(sampleRate);
  224. if (const int samplesPerBlock = getBlockSize())
  225. if (samplesPerBlock > 0)
  226. plugin.setBufferSize(static_cast<uint32_t>(samplesPerBlock));
  227. if (parameterCount != 0)
  228. {
  229. updatedParameters = new bool[parameterCount];
  230. std::memset(updatedParameters, 0, sizeof(bool)*parameterCount);
  231. for (uint i=0; i<parameterCount; ++i)
  232. {
  233. ParameterFromDPF* const param = new ParameterFromDPF(plugin, i, updatedParameters + i);
  234. addParameter(param);
  235. if (plugin.getParameterDesignation(i) == kParameterDesignationBypass)
  236. bypassParameter = param;
  237. }
  238. }
  239. }
  240. ~CardinalWrapperProcessor() override
  241. {
  242. delete[] updatedParameters;
  243. }
  244. protected:
  245. const juce::String getName() const override
  246. {
  247. return plugin.getName();
  248. }
  249. juce::StringArray getAlternateDisplayNames() const override
  250. {
  251. return juce::StringArray(plugin.getLabel());
  252. }
  253. void prepareToPlay(const double sampleRate, const int samplesPerBlock) override
  254. {
  255. DISTRHO_SAFE_ASSERT_RETURN(samplesPerBlock > 0,);
  256. plugin.deactivateIfNeeded();
  257. plugin.setSampleRate(sampleRate);
  258. plugin.setBufferSize(static_cast<uint32_t>(samplesPerBlock));
  259. plugin.activate();
  260. }
  261. void releaseResources() override
  262. {
  263. plugin.deactivateIfNeeded();
  264. }
  265. void processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) override
  266. {
  267. const int numSamples = buffer.getNumSamples();
  268. DISTRHO_SAFE_ASSERT_INT_RETURN(numSamples > 0, numSamples, midiMessages.clear());
  269. uint32_t midiEventCount = 0;
  270. for (const juce::MidiMessageMetadata midiMessage : midiMessages)
  271. {
  272. DISTRHO_SAFE_ASSERT_CONTINUE(midiMessage.numBytes > 0);
  273. DISTRHO_SAFE_ASSERT_CONTINUE(midiMessage.samplePosition >= 0);
  274. if (midiMessage.numBytes > static_cast<int>(MidiEvent::kDataSize))
  275. continue;
  276. MidiEvent& midiEvent(midiEvents[midiEventCount++]);
  277. midiEvent.frame = static_cast<uint32_t>(midiMessage.samplePosition);
  278. midiEvent.size = (static_cast<uint8_t>(midiMessage.numBytes));
  279. std::memcpy(midiEvent.data, midiMessage.data, midiEvent.size);
  280. if (midiEventCount == kMaxMidiEvents)
  281. break;
  282. }
  283. midiMessages.clear();
  284. const juce::ScopedValueSetter<juce::MidiBuffer*> cvs(currentMidiMessages, &midiMessages, nullptr);
  285. juce::AudioPlayHead* const playhead = getPlayHead();
  286. juce::AudioPlayHead::CurrentPositionInfo posInfo;
  287. if (playhead != nullptr && playhead->getCurrentPosition(posInfo))
  288. {
  289. timePosition.playing = posInfo.isPlaying;
  290. timePosition.bbt.valid = true;
  291. // ticksPerBeat is not possible with JUCE
  292. timePosition.bbt.ticksPerBeat = 1920.0;
  293. if (posInfo.timeInSamples >= 0)
  294. timePosition.frame = static_cast<uint64_t>(posInfo.timeInSamples);
  295. else
  296. timePosition.frame = 0;
  297. timePosition.bbt.beatsPerMinute = posInfo.bpm;
  298. const double ppqPos = std::abs(posInfo.ppqPosition);
  299. const int ppqPerBar = posInfo.timeSigNumerator * 4 / posInfo.timeSigDenominator;
  300. const double barBeats = (std::fmod(ppqPos, ppqPerBar) / ppqPerBar) * posInfo.timeSigNumerator;
  301. const double rest = std::fmod(barBeats, 1.0);
  302. timePosition.bbt.bar = static_cast<int32_t>(ppqPos) / ppqPerBar + 1;
  303. timePosition.bbt.beat = static_cast<int32_t>(barBeats - rest + 0.5) + 1;
  304. timePosition.bbt.tick = rest * timePosition.bbt.ticksPerBeat;
  305. timePosition.bbt.beatsPerBar = posInfo.timeSigNumerator;
  306. timePosition.bbt.beatType = posInfo.timeSigDenominator;
  307. if (posInfo.ppqPosition < 0.0)
  308. {
  309. --timePosition.bbt.bar;
  310. timePosition.bbt.beat = posInfo.timeSigNumerator - timePosition.bbt.beat + 1;
  311. timePosition.bbt.tick = timePosition.bbt.ticksPerBeat - timePosition.bbt.tick - 1;
  312. }
  313. timePosition.bbt.barStartTick = timePosition.bbt.ticksPerBeat*
  314. timePosition.bbt.beatsPerBar*
  315. (timePosition.bbt.bar-1);
  316. }
  317. else
  318. {
  319. timePosition.frame = 0;
  320. timePosition.playing = false;
  321. timePosition.bbt.valid = false;
  322. }
  323. plugin.setTimePosition(timePosition);
  324. DISTRHO_SAFE_ASSERT_RETURN(buffer.getNumChannels() >= 2,);
  325. const float* audioBufferIn[18] = {};
  326. float* audioBufferOut[18] = {};
  327. for (int i=buffer.getNumChannels(); --i >= 0;)
  328. {
  329. audioBufferIn[i] = buffer.getReadPointer(i);
  330. audioBufferOut[i] = buffer.getWritePointer(i);
  331. }
  332. plugin.run(audioBufferIn, audioBufferOut, static_cast<uint32_t>(numSamples), midiEvents, midiEventCount);
  333. }
  334. // fix compiler warning
  335. void processBlock(juce::AudioBuffer<double>&, juce::MidiBuffer&) override {}
  336. double getTailLengthSeconds() const override
  337. {
  338. return 0.0;
  339. }
  340. bool acceptsMidi() const override
  341. {
  342. return true;
  343. }
  344. bool producesMidi() const override
  345. {
  346. return true;
  347. }
  348. juce::AudioProcessorParameter* getBypassParameter() const override
  349. {
  350. return bypassParameter;
  351. }
  352. juce::AudioProcessorEditor* createEditor() override;
  353. bool hasEditor() const override
  354. {
  355. return true;
  356. }
  357. int getNumPrograms() override
  358. {
  359. return 1;
  360. }
  361. int getCurrentProgram() override
  362. {
  363. return 0;
  364. }
  365. void setCurrentProgram(int) override
  366. {
  367. }
  368. const juce::String getProgramName(int) override
  369. {
  370. return "Default";
  371. }
  372. void changeProgramName(int, const juce::String&) override
  373. {
  374. }
  375. void getStateInformation(juce::MemoryBlock& destData) override
  376. {
  377. juce::XmlElement xmlState("CardinalState");
  378. for (uint32_t i=0; i<parameterCount; ++i)
  379. xmlState.setAttribute(plugin.getParameterSymbol(i).buffer(), plugin.getParameterValue(i));
  380. for (uint32_t i=0, stateCount=plugin.getStateCount(); i<stateCount; ++i)
  381. {
  382. const String& key(plugin.getStateKey(i));
  383. xmlState.setAttribute(key.buffer(), plugin.getStateValue(key).buffer());
  384. }
  385. copyXmlToBinary(xmlState, destData);
  386. }
  387. void setStateInformation(const void* const data, const int sizeInBytes) override
  388. {
  389. std::unique_ptr<juce::XmlElement> xmlState(getXmlFromBinary(data, sizeInBytes));
  390. DISTRHO_SAFE_ASSERT_RETURN(xmlState.get() != nullptr,);
  391. const juce::Array<juce::AudioProcessorParameter*>& parameters(getParameters());
  392. for (uint32_t i=0; i<parameterCount; ++i)
  393. {
  394. const double value = xmlState->getDoubleAttribute(plugin.getParameterSymbol(i).buffer(),
  395. plugin.getParameterDefault(i));
  396. const float normalizedValue = plugin.getParameterRanges(i).getFixedAndNormalizedValue(value);
  397. parameters.getUnchecked(static_cast<int>(i))->setValueNotifyingHost(normalizedValue);
  398. }
  399. for (uint32_t i=0, stateCount=plugin.getStateCount(); i<stateCount; ++i)
  400. {
  401. const String& key(plugin.getStateKey(i));
  402. const juce::String value = xmlState->getStringAttribute(key.buffer(),
  403. plugin.getStateDefaultValue(i).buffer());
  404. plugin.setState(key, value.toRawUTF8());
  405. }
  406. }
  407. private:
  408. static bool writeMidiFunc(void* const ptr, const MidiEvent& midiEvent)
  409. {
  410. CardinalWrapperProcessor* const processor = static_cast<CardinalWrapperProcessor*>(ptr);
  411. DISTRHO_SAFE_ASSERT_RETURN(processor != nullptr, false);
  412. juce::MidiBuffer* const currentMidiMessages = processor->currentMidiMessages;
  413. DISTRHO_SAFE_ASSERT_RETURN(currentMidiMessages != nullptr, false);
  414. const uint8_t* const data = midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data;
  415. return currentMidiMessages->addEvent(data,
  416. static_cast<int>(midiEvent.size),
  417. static_cast<int>(midiEvent.frame));
  418. }
  419. };
  420. // --------------------------------------------------------------------------------------------------------------------
  421. // unused in cardinal
  422. static constexpr const sendNoteFunc nullSendNoteFunc = nullptr;
  423. // unwanted, juce file dialogs are ugly
  424. static constexpr const fileRequestFunc nullFileRequestFunc = nullptr;
  425. // UI/editor implementation
  426. class CardinalWrapperEditor : public juce::AudioProcessorEditor,
  427. private juce::Timer
  428. {
  429. CardinalWrapperProcessor& cardinalProcessor;
  430. UIExporter* ui;
  431. void* const dspPtr;
  432. public:
  433. CardinalWrapperEditor(CardinalWrapperProcessor& cardinalProc)
  434. : juce::AudioProcessorEditor(cardinalProc),
  435. cardinalProcessor(cardinalProc),
  436. ui(nullptr),
  437. dspPtr(cardinalProc.plugin.getInstancePointer())
  438. {
  439. setOpaque(true);
  440. setResizable(true, false);
  441. // setResizeLimits(648, 538, -1, -1);
  442. setSize(1228, 666);
  443. startTimer(1000.0 / 60.0);
  444. }
  445. ~CardinalWrapperEditor() override
  446. {
  447. stopTimer();
  448. delete ui;
  449. }
  450. protected:
  451. void timerCallback() override
  452. {
  453. if (ui == nullptr)
  454. return;
  455. for (uint32_t i=0; i<cardinalProcessor.parameterCount; ++i)
  456. {
  457. if (cardinalProcessor.updatedParameters[i])
  458. {
  459. cardinalProcessor.updatedParameters[i] = false;
  460. ui->parameterChanged(i, cardinalProcessor.plugin.getParameterValue(i));
  461. }
  462. }
  463. repaint();
  464. }
  465. void paint(juce::Graphics&) override
  466. {
  467. if (ui == nullptr)
  468. {
  469. juce::ComponentPeer* const peer = getPeer();
  470. DISTRHO_SAFE_ASSERT_RETURN(peer != nullptr,);
  471. void* const nativeHandle = peer->getNativeHandle();
  472. DISTRHO_SAFE_ASSERT_RETURN(nativeHandle != nullptr,);
  473. ui = new UIExporter(this,
  474. (uintptr_t)nativeHandle,
  475. cardinalProcessor.getSampleRate(),
  476. editParamFunc,
  477. setParamFunc,
  478. setStateFunc,
  479. nullSendNoteFunc,
  480. setSizeFunc,
  481. nullFileRequestFunc,
  482. nullptr, // bundlePath
  483. dspPtr,
  484. 0.0 // scaleFactor
  485. );
  486. if (cardinalProcessor.wrapperType == juce::AudioProcessor::wrapperType_Standalone)
  487. {
  488. const double scaleFactor = ui->getScaleFactor();
  489. ui->setWindowOffset(4 * scaleFactor, 30 * scaleFactor);
  490. }
  491. }
  492. ui->plugin_idle();
  493. }
  494. private:
  495. static void editParamFunc(void* const ptr, const uint32_t index, const bool started)
  496. {
  497. CardinalWrapperEditor* const editor = static_cast<CardinalWrapperEditor*>(ptr);
  498. DISTRHO_SAFE_ASSERT_RETURN(editor != nullptr,);
  499. CardinalWrapperProcessor& cardinalProcessor(editor->cardinalProcessor);
  500. if (started)
  501. cardinalProcessor.getParameters().getUnchecked(static_cast<int>(index))->beginChangeGesture();
  502. else
  503. cardinalProcessor.getParameters().getUnchecked(static_cast<int>(index))->endChangeGesture();
  504. }
  505. static void setParamFunc(void* const ptr, const uint32_t index, const float value)
  506. {
  507. CardinalWrapperEditor* const editor = static_cast<CardinalWrapperEditor*>(ptr);
  508. DISTRHO_SAFE_ASSERT_RETURN(editor != nullptr,);
  509. CardinalWrapperProcessor& cardinalProcessor(editor->cardinalProcessor);
  510. const juce::Array<juce::AudioProcessorParameter*>& parameters(cardinalProcessor.getParameters());
  511. juce::AudioProcessorParameter* const parameter = parameters.getUnchecked(static_cast<int>(index));
  512. static_cast<ParameterFromDPF*>(parameter)->setValueNotifyingHostFromDPF(value);
  513. }
  514. static void setStateFunc(void* const ptr, const char* const key, const char* const value)
  515. {
  516. CardinalWrapperEditor* const editor = static_cast<CardinalWrapperEditor*>(ptr);
  517. DISTRHO_SAFE_ASSERT_RETURN(editor != nullptr,);
  518. CardinalWrapperProcessor& cardinalProcessor(editor->cardinalProcessor);
  519. cardinalProcessor.plugin.setState(key, value);
  520. }
  521. static void setSizeFunc(void* const ptr, uint width, uint height)
  522. {
  523. CardinalWrapperEditor* const editor = static_cast<CardinalWrapperEditor*>(ptr);
  524. DISTRHO_SAFE_ASSERT_RETURN(editor != nullptr,);
  525. #ifdef DISTRHO_OS_MAC
  526. UIExporter* const ui = editor->ui;
  527. DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,);
  528. const double scaleFactor = ui->getScaleFactor();
  529. width /= scaleFactor;
  530. height /= scaleFactor;
  531. #endif
  532. editor->setSize(static_cast<int>(width), static_cast<int>(height));
  533. }
  534. };
  535. juce::AudioProcessorEditor* CardinalWrapperProcessor::createEditor()
  536. {
  537. return new CardinalWrapperEditor(*this);
  538. }
  539. // --------------------------------------------------------------------------------------------------------------------
  540. END_NAMESPACE_DISTRHO
  541. // --------------------------------------------------------------------------------------------------------------------
  542. juce::AudioProcessor* createPluginFilter()
  543. {
  544. // set valid but dummy values
  545. d_nextBufferSize = 512;
  546. d_nextSampleRate = 48000.0;
  547. return new DISTRHO_NAMESPACE::CardinalWrapperProcessor;
  548. }
  549. // --------------------------------------------------------------------------------------------------------------------