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.

666 lines
21KB

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