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.

708 lines
23KB

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