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.

701 lines
23KB

  1. /*
  2. * Carla Native Plugins
  3. * Copyright (C) 2013-2020 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. #include "CarlaNativePrograms.hpp"
  18. #include "CarlaString.hpp"
  19. #include "audio-base.hpp"
  20. static const char* const audiofilesWildcard =
  21. #ifdef HAVE_SNDFILE
  22. "*.aif;*.aifc;*.aiff;*.au;*.bwf;*.flac;*.htk;*.iff;*.mat4;*.mat5;*.oga;*.ogg;"
  23. "*.paf;*.pvf;*.pvf5;*.sd2;*.sf;*.snd;*.svx;*.vcc;*.w64;*.wav;*.xi;"
  24. #endif
  25. #ifdef HAVE_FFMPEG
  26. "*.3g2;*.3gp;*.aac;*.ac3;*.amr;*.ape;*.mp2;*.mp3;*.mpc;*.wma;"
  27. # ifndef HAVE_SNDFILE
  28. "*.flac;*.oga;*.ogg;*.w64;*.wav;"
  29. # endif
  30. #endif
  31. #if !defined(HAVE_SNDFILE) && !defined(HAVE_FFMPEG)
  32. ""
  33. # ifndef BUILDING_FOR_CI
  34. # warning sndfile and ffmpeg libraries missing, no audio file support will be available
  35. # endif
  36. #endif
  37. ;
  38. // -----------------------------------------------------------------------
  39. class AudioFilePlugin : public NativePluginWithMidiPrograms<FileAudio>
  40. {
  41. public:
  42. #ifndef __MOD_DEVICES__
  43. typedef enum _PendingInlineDisplay
  44. # ifdef CARLA_PROPER_CPP11_SUPPORT
  45. : uint8_t
  46. # endif
  47. {
  48. InlineDisplayNotPending,
  49. InlineDisplayNeedRequest,
  50. InlineDisplayRequesting
  51. } PendingInlineDisplay;
  52. #endif
  53. enum Parameters {
  54. kParameterLooping,
  55. kParameterInfoChannels,
  56. kParameterInfoBitRate,
  57. kParameterInfoBitDepth,
  58. kParameterInfoSampleRate,
  59. kParameterInfoLength,
  60. kParameterInfoPosition,
  61. kParameterCount
  62. };
  63. AudioFilePlugin(const NativeHostDescriptor* const host)
  64. : NativePluginWithMidiPrograms<FileAudio>(host, fPrograms, 2),
  65. fLoopMode(true),
  66. fDoProcess(false),
  67. fWasPlayingBefore(false),
  68. fNeedsFileRead(false),
  69. fMaxFrame(0),
  70. fLastPosition(0),
  71. fPool(),
  72. fReader(),
  73. fPrograms(hostGetFilePath("audio"), audiofilesWildcard),
  74. fPreviewData()
  75. #ifndef __MOD_DEVICES__
  76. , fInlineDisplay()
  77. #endif
  78. {
  79. }
  80. ~AudioFilePlugin() override
  81. {
  82. fReader.destroy();
  83. fPool.destroy();
  84. }
  85. protected:
  86. // -------------------------------------------------------------------
  87. // Plugin parameter calls
  88. uint32_t getParameterCount() const override
  89. {
  90. return kParameterCount;
  91. }
  92. const NativeParameter* getParameterInfo(const uint32_t index) const override
  93. {
  94. static NativeParameter param;
  95. param.scalePointCount = 0;
  96. param.scalePoints = nullptr;
  97. param.unit = nullptr;
  98. param.ranges.step = 1.0f;
  99. param.ranges.stepSmall = 1.0f;
  100. param.ranges.stepLarge = 1.0f;
  101. switch (index)
  102. {
  103. case kParameterLooping:
  104. param.name = "Loop Mode";
  105. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMABLE|
  106. NATIVE_PARAMETER_IS_ENABLED|
  107. NATIVE_PARAMETER_IS_BOOLEAN);
  108. param.ranges.def = 1.0f;
  109. param.ranges.min = 0.0f;
  110. param.ranges.max = 1.0f;
  111. break;
  112. case kParameterInfoChannels:
  113. param.name = "Num Channels";
  114. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMABLE|
  115. NATIVE_PARAMETER_IS_ENABLED|
  116. NATIVE_PARAMETER_IS_INTEGER|
  117. NATIVE_PARAMETER_IS_OUTPUT);
  118. param.ranges.def = 0.0f;
  119. param.ranges.min = 0.0f;
  120. param.ranges.max = 2.0f;
  121. break;
  122. case kParameterInfoBitRate:
  123. param.name = "Bit Rate";
  124. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMABLE|
  125. NATIVE_PARAMETER_IS_ENABLED|
  126. NATIVE_PARAMETER_IS_INTEGER|
  127. NATIVE_PARAMETER_IS_OUTPUT);
  128. param.ranges.def = 0.0f;
  129. param.ranges.min = 0.0f;
  130. param.ranges.max = 384000.0f * 32.0f * 2.0f;
  131. break;
  132. case kParameterInfoBitDepth:
  133. param.name = "Bit Depth";
  134. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMABLE|
  135. NATIVE_PARAMETER_IS_ENABLED|
  136. NATIVE_PARAMETER_IS_INTEGER|
  137. NATIVE_PARAMETER_IS_OUTPUT);
  138. param.ranges.def = 0.0f;
  139. param.ranges.min = 0.0f;
  140. param.ranges.max = 32.0f;
  141. break;
  142. case kParameterInfoSampleRate:
  143. param.name = "Sample Rate";
  144. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMABLE|
  145. NATIVE_PARAMETER_IS_ENABLED|
  146. NATIVE_PARAMETER_IS_INTEGER|
  147. NATIVE_PARAMETER_IS_OUTPUT);
  148. param.ranges.def = 0.0f;
  149. param.ranges.min = 0.0f;
  150. param.ranges.max = 384000.0f;
  151. break;
  152. case kParameterInfoLength:
  153. param.name = "Length";
  154. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMABLE|
  155. NATIVE_PARAMETER_IS_ENABLED|
  156. NATIVE_PARAMETER_IS_OUTPUT);
  157. param.ranges.def = 0.0f;
  158. param.ranges.min = 0.0f;
  159. param.ranges.max = (float)INT64_MAX;
  160. param.unit = "s";
  161. break;
  162. case kParameterInfoPosition:
  163. param.name = "Position";
  164. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMABLE|
  165. NATIVE_PARAMETER_IS_ENABLED|
  166. NATIVE_PARAMETER_IS_OUTPUT);
  167. param.ranges.def = 0.0f;
  168. param.ranges.min = 0.0f;
  169. param.ranges.max = 100.0f;
  170. param.unit = "%";
  171. break;
  172. default:
  173. return nullptr;
  174. }
  175. return &param;
  176. }
  177. float getParameterValue(const uint32_t index) const override
  178. {
  179. if (index == kParameterLooping)
  180. return fLoopMode ? 1.0f : 0.0f;
  181. const ADInfo nfo = fReader.getFileInfo();
  182. switch (index)
  183. {
  184. case kParameterInfoChannels:
  185. return static_cast<float>(nfo.channels);
  186. case kParameterInfoBitRate:
  187. return static_cast<float>(nfo.bit_rate);
  188. case kParameterInfoBitDepth:
  189. return static_cast<float>(nfo.bit_depth);
  190. case kParameterInfoSampleRate:
  191. return static_cast<float>(nfo.sample_rate);
  192. case kParameterInfoLength:
  193. return static_cast<float>(nfo.length)/1000.0f;
  194. case kParameterInfoPosition:
  195. return fLastPosition;
  196. default:
  197. return 0.0f;
  198. }
  199. }
  200. // -------------------------------------------------------------------
  201. // Plugin state calls
  202. void setParameterValue(const uint32_t index, const float value) override
  203. {
  204. if (index != kParameterLooping)
  205. return;
  206. bool b = (value > 0.5f);
  207. if (b == fLoopMode)
  208. return;
  209. fLoopMode = b;
  210. fReader.setLoopingMode(b);
  211. }
  212. void setCustomData(const char* const key, const char* const value) override
  213. {
  214. if (std::strcmp(key, "file") != 0)
  215. return;
  216. invalidateNextFilename();
  217. loadFilename(value);
  218. }
  219. // -------------------------------------------------------------------
  220. // Plugin process calls
  221. void process2(const float* const*, float** const outBuffer, const uint32_t frames,
  222. const NativeMidiEvent*, uint32_t) override
  223. {
  224. const NativeTimeInfo* const timePos(getTimeInfo());
  225. float* out1 = outBuffer[0];
  226. float* out2 = outBuffer[1];
  227. bool needsIdleRequest = false;
  228. if (! fDoProcess)
  229. {
  230. // carla_stderr("P: no process");
  231. carla_zeroFloats(out1, frames);
  232. carla_zeroFloats(out2, frames);
  233. fLastPosition = 0.0f;
  234. return;
  235. }
  236. // not playing
  237. if (! timePos->playing)
  238. {
  239. // carla_stderr("P: not playing");
  240. if (timePos->frame == 0 && fWasPlayingBefore)
  241. fReader.setNeedsRead(timePos->frame);
  242. carla_zeroFloats(out1, frames);
  243. carla_zeroFloats(out2, frames);
  244. fWasPlayingBefore = false;
  245. return;
  246. }
  247. else
  248. {
  249. fWasPlayingBefore = true;
  250. }
  251. // out of reach
  252. if ((timePos->frame < fPool.startFrame || timePos->frame >= fMaxFrame) && !fLoopMode)
  253. {
  254. if (timePos->frame < fPool.startFrame)
  255. {
  256. needsIdleRequest = true;
  257. fNeedsFileRead = true;
  258. fReader.setNeedsRead(timePos->frame);
  259. }
  260. carla_zeroFloats(out1, frames);
  261. carla_zeroFloats(out2, frames);
  262. #ifndef __MOD_DEVICES__
  263. if (fInlineDisplay.writtenValues < 32)
  264. {
  265. fInlineDisplay.lastValuesL[fInlineDisplay.writtenValues] = 0.0f;
  266. fInlineDisplay.lastValuesR[fInlineDisplay.writtenValues] = 0.0f;
  267. ++fInlineDisplay.writtenValues;
  268. }
  269. if (fInlineDisplay.pending == InlineDisplayNotPending)
  270. {
  271. needsIdleRequest = true;
  272. fInlineDisplay.pending = InlineDisplayNeedRequest;
  273. }
  274. #endif
  275. if (needsIdleRequest)
  276. hostRequestIdle();
  277. if (timePos->frame == 0)
  278. fLastPosition = 0.0f;
  279. else if (timePos->frame >= fMaxFrame)
  280. fLastPosition = 100.0f;
  281. else
  282. fLastPosition = static_cast<float>(timePos->frame) / static_cast<float>(fMaxFrame) * 100.0f;
  283. return;
  284. }
  285. if (fReader.isEntireFileLoaded())
  286. {
  287. // NOTE: timePos->frame is always < fMaxFrame (or looping)
  288. uint32_t targetStartFrame = static_cast<uint32_t>(fLoopMode ? timePos->frame % fMaxFrame : timePos->frame);
  289. for (uint32_t framesDone=0, framesToDo=frames, remainingFrames; framesDone < frames;)
  290. {
  291. if (targetStartFrame + framesToDo <= fMaxFrame)
  292. {
  293. // everything fits together
  294. carla_copyFloats(out1+framesDone, fPool.buffer[0]+targetStartFrame, framesToDo);
  295. carla_copyFloats(out2+framesDone, fPool.buffer[1]+targetStartFrame, framesToDo);
  296. break;
  297. }
  298. remainingFrames = std::min(fMaxFrame - targetStartFrame, framesToDo);
  299. carla_copyFloats(out1+framesDone, fPool.buffer[0]+targetStartFrame, remainingFrames);
  300. carla_copyFloats(out2+framesDone, fPool.buffer[1]+targetStartFrame, remainingFrames);
  301. framesDone += remainingFrames;
  302. framesToDo -= remainingFrames;
  303. if (! fLoopMode)
  304. {
  305. // not looping, stop here
  306. if (framesToDo != 0)
  307. {
  308. carla_zeroFloats(out1+framesDone, framesToDo);
  309. carla_zeroFloats(out2+framesDone, framesToDo);
  310. }
  311. break;
  312. }
  313. // reset for next loop
  314. targetStartFrame = 0;
  315. }
  316. fLastPosition = static_cast<float>(targetStartFrame) / static_cast<float>(fMaxFrame) * 100.0f;
  317. }
  318. else
  319. {
  320. const bool offline = isOffline();
  321. if (! fReader.tryPutData(out1, out2, timePos->frame, frames, offline, needsIdleRequest))
  322. {
  323. carla_zeroFloats(out1, frames);
  324. carla_zeroFloats(out2, frames);
  325. }
  326. if (needsIdleRequest)
  327. {
  328. fNeedsFileRead = true;
  329. if (isOffline())
  330. {
  331. needsIdleRequest = false;
  332. fReader.readPoll();
  333. if (! fReader.tryPutData(out1, out2, timePos->frame, frames, offline, needsIdleRequest))
  334. {
  335. carla_zeroFloats(out1, frames);
  336. carla_zeroFloats(out2, frames);
  337. }
  338. if (needsIdleRequest)
  339. fNeedsFileRead = true;
  340. }
  341. }
  342. fLastPosition = static_cast<float>(timePos->frame % fMaxFrame) / static_cast<float>(fMaxFrame) * 100.0f;
  343. }
  344. #ifndef __MOD_DEVICES__
  345. if (fInlineDisplay.writtenValues < 32)
  346. {
  347. fInlineDisplay.lastValuesL[fInlineDisplay.writtenValues] = carla_findMaxNormalizedFloat(out1, frames);
  348. fInlineDisplay.lastValuesR[fInlineDisplay.writtenValues] = carla_findMaxNormalizedFloat(out2, frames);
  349. ++fInlineDisplay.writtenValues;
  350. }
  351. if (fInlineDisplay.pending == InlineDisplayNotPending)
  352. {
  353. needsIdleRequest = true;
  354. fInlineDisplay.pending = InlineDisplayNeedRequest;
  355. }
  356. #endif
  357. if (needsIdleRequest)
  358. hostRequestIdle();
  359. }
  360. // -------------------------------------------------------------------
  361. // Plugin UI calls
  362. void uiShow(const bool show) override
  363. {
  364. if (! show)
  365. return;
  366. if (const char* const filename = uiOpenFile(false, "Open Audio File", ""))
  367. uiCustomDataChanged("file", filename);
  368. uiClosed();
  369. }
  370. // -------------------------------------------------------------------
  371. // Plugin state calls
  372. void setStateFromFile(const char* const filename) override
  373. {
  374. loadFilename(filename);
  375. }
  376. // -------------------------------------------------------------------
  377. // Plugin dispatcher calls
  378. void idle() override
  379. {
  380. NativePluginWithMidiPrograms<FileAudio>::idle();
  381. if (fNeedsFileRead)
  382. {
  383. fReader.readPoll();
  384. fNeedsFileRead = false;
  385. }
  386. #ifndef __MOD_DEVICES__
  387. if (fInlineDisplay.pending == InlineDisplayNeedRequest)
  388. {
  389. fInlineDisplay.pending = InlineDisplayRequesting;
  390. hostQueueDrawInlineDisplay();
  391. }
  392. #endif
  393. }
  394. #ifndef __MOD_DEVICES__
  395. const NativeInlineDisplayImageSurface* renderInlineDisplay(const uint32_t rwidth, const uint32_t height) override
  396. {
  397. CARLA_SAFE_ASSERT_RETURN(height > 4, nullptr);
  398. const uint32_t width = rwidth == height ? height * 4 : rwidth;
  399. /* NOTE the code is this function is not optimized, still learning my way through pixels...
  400. */
  401. const size_t stride = width * 4;
  402. const size_t dataSize = stride * height;
  403. const uint pxToMove = fDoProcess ? fInlineDisplay.writtenValues : 0;
  404. uchar* data = fInlineDisplay.data;
  405. if (fInlineDisplay.dataSize != dataSize || data == nullptr)
  406. {
  407. delete[] data;
  408. data = new uchar[dataSize];
  409. std::memset(data, 0, dataSize);
  410. fInlineDisplay.data = data;
  411. fInlineDisplay.dataSize = dataSize;
  412. }
  413. else if (pxToMove != 0)
  414. {
  415. // shift all previous values to the left
  416. for (uint w=0; w < width - pxToMove; ++w)
  417. for (uint h=0; h < height; ++h)
  418. std::memmove(&data[h * stride + w * 4], &data[h * stride + (w+pxToMove) * 4], 4);
  419. }
  420. fInlineDisplay.width = static_cast<int>(width);
  421. fInlineDisplay.height = static_cast<int>(height);
  422. fInlineDisplay.stride = static_cast<int>(stride);
  423. if (pxToMove != 0)
  424. {
  425. const uint h2 = height / 2;
  426. // clear current line
  427. for (uint w=width-pxToMove; w < width; ++w)
  428. for (uint h=0; h < height; ++h)
  429. memset(&data[h * stride + w * 4], 0, 4);
  430. // draw upper/left
  431. for (uint i=0; i < pxToMove && i < 32; ++i)
  432. {
  433. const float valueL = fInlineDisplay.lastValuesL[i];
  434. const float valueR = fInlineDisplay.lastValuesR[i];
  435. const uint h2L = static_cast<uint>(valueL * (float)h2);
  436. const uint h2R = static_cast<uint>(valueR * (float)h2);
  437. const uint w = width - pxToMove + i;
  438. for (uint h=0; h < h2L; ++h)
  439. {
  440. // -30dB
  441. //if (valueL < 0.032f)
  442. // continue;
  443. data[(h2 - h) * stride + w * 4 + 3] = 160;
  444. // -12dB
  445. if (valueL < 0.25f)
  446. {
  447. data[(h2 - h) * stride + w * 4 + 1] = 255;
  448. }
  449. // -3dB
  450. else if (valueL < 0.70f)
  451. {
  452. data[(h2 - h) * stride + w * 4 + 2] = 255;
  453. data[(h2 - h) * stride + w * 4 + 1] = 255;
  454. }
  455. else
  456. {
  457. data[(h2 - h) * stride + w * 4 + 2] = 255;
  458. }
  459. }
  460. for (uint h=0; h < h2R; ++h)
  461. {
  462. // -30dB
  463. //if (valueR < 0.032f)
  464. // continue;
  465. data[(h2 + h) * stride + w * 4 + 3] = 160;
  466. // -12dB
  467. if (valueR < 0.25f)
  468. {
  469. data[(h2 + h) * stride + w * 4 + 1] = 255;
  470. }
  471. // -3dB
  472. else if (valueR < 0.70f)
  473. {
  474. data[(h2 + h) * stride + w * 4 + 2] = 255;
  475. data[(h2 + h) * stride + w * 4 + 1] = 255;
  476. }
  477. else
  478. {
  479. data[(h2 + h) * stride + w * 4 + 2] = 255;
  480. }
  481. }
  482. }
  483. }
  484. fInlineDisplay.writtenValues = 0;
  485. fInlineDisplay.pending = InlineDisplayNotPending;
  486. return (NativeInlineDisplayImageSurface*)(NativeInlineDisplayImageSurfaceCompat*)&fInlineDisplay;
  487. }
  488. #endif
  489. // -------------------------------------------------------------------
  490. private:
  491. bool fLoopMode;
  492. bool fDoProcess;
  493. bool fWasPlayingBefore;
  494. volatile bool fNeedsFileRead;
  495. uint32_t fMaxFrame;
  496. float fLastPosition;
  497. AudioFilePool fPool;
  498. AudioFileReader fReader;
  499. NativeMidiPrograms fPrograms;
  500. float fPreviewData[300];
  501. #ifndef __MOD_DEVICES__
  502. struct InlineDisplay : NativeInlineDisplayImageSurfaceCompat {
  503. float lastValuesL[32];
  504. float lastValuesR[32];
  505. volatile PendingInlineDisplay pending;
  506. volatile uint8_t writtenValues;
  507. InlineDisplay()
  508. : NativeInlineDisplayImageSurfaceCompat(),
  509. # ifdef CARLA_PROPER_CPP11_SUPPORT
  510. lastValuesL{0.0f},
  511. lastValuesR{0.0f},
  512. # endif
  513. pending(InlineDisplayNotPending),
  514. writtenValues(0)
  515. {
  516. # ifndef CARLA_PROPER_CPP11_SUPPORT
  517. carla_zeroFloats(lastValuesL, 32);
  518. carla_zeroFloats(lastValuesR, 32);
  519. # endif
  520. }
  521. ~InlineDisplay()
  522. {
  523. if (data != nullptr)
  524. {
  525. delete[] data;
  526. data = nullptr;
  527. }
  528. }
  529. CARLA_DECLARE_NON_COPY_STRUCT(InlineDisplay)
  530. CARLA_PREVENT_HEAP_ALLOCATION
  531. } fInlineDisplay;
  532. #endif
  533. void loadFilename(const char* const filename)
  534. {
  535. CARLA_ASSERT(filename != nullptr);
  536. carla_debug("AudioFilePlugin::loadFilename(\"%s\")", filename);
  537. fDoProcess = false;
  538. fReader.destroy();
  539. fPool.destroy();
  540. if (filename == nullptr || *filename == '\0')
  541. {
  542. fMaxFrame = 0;
  543. return;
  544. }
  545. const uint32_t previewDataSize = sizeof(fPreviewData)/sizeof(float);
  546. if (fReader.loadFilename(filename, static_cast<uint32_t>(getSampleRate()), previewDataSize, fPreviewData))
  547. {
  548. fMaxFrame = fReader.getMaxFrame();
  549. if (fReader.isEntireFileLoaded())
  550. {
  551. fReader.putAndSwapAllData(fPool);
  552. }
  553. else
  554. {
  555. fPool.create(fReader.getPoolNumFrames(), false);
  556. fReader.readPoll();
  557. }
  558. fDoProcess = true;
  559. hostSendPreviewBufferData('f', previewDataSize, fPreviewData);
  560. }
  561. else
  562. {
  563. fMaxFrame = 0;
  564. }
  565. }
  566. PluginClassEND(AudioFilePlugin)
  567. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioFilePlugin)
  568. };
  569. // -----------------------------------------------------------------------
  570. static const NativePluginDescriptor audiofileDesc = {
  571. /* category */ NATIVE_PLUGIN_CATEGORY_UTILITY,
  572. /* hints */ static_cast<NativePluginHints>(NATIVE_PLUGIN_IS_RTSAFE
  573. |NATIVE_PLUGIN_HAS_UI
  574. #ifndef __MOD_DEVICES__
  575. |NATIVE_PLUGIN_HAS_INLINE_DISPLAY
  576. #endif
  577. |NATIVE_PLUGIN_REQUESTS_IDLE
  578. |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE
  579. |NATIVE_PLUGIN_USES_TIME),
  580. /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
  581. /* audioIns */ 0,
  582. /* audioOuts */ 2,
  583. /* midiIns */ 0,
  584. /* midiOuts */ 0,
  585. /* paramIns */ 1,
  586. /* paramOuts */ 0,
  587. /* name */ "Audio File",
  588. /* label */ "audiofile",
  589. /* maker */ "falkTX",
  590. /* copyright */ "GNU GPL v2+",
  591. PluginDescriptorFILL(AudioFilePlugin)
  592. };
  593. // -----------------------------------------------------------------------
  594. CARLA_EXPORT
  595. void carla_register_native_plugin_audiofile();
  596. CARLA_EXPORT
  597. void carla_register_native_plugin_audiofile()
  598. {
  599. carla_register_native_plugin(&audiofileDesc);
  600. }
  601. // -----------------------------------------------------------------------