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.

538 lines
16KB

  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 : uint8_t {
  44. InlineDisplayNotPending,
  45. InlineDisplayNeedRequest,
  46. InlineDisplayRequesting
  47. } PendingInlineDisplay;
  48. #endif
  49. AudioFilePlugin(const NativeHostDescriptor* const host)
  50. : NativePluginWithMidiPrograms<FileAudio>(host, fPrograms, 2),
  51. fLoopMode(true),
  52. fDoProcess(false),
  53. fWasPlayingBefore(false),
  54. fMaxFrame(0),
  55. fPool(),
  56. fThread(),
  57. fPrograms(hostGetFilePath("audio"), audiofilesWildcard)
  58. #ifndef __MOD_DEVICES__
  59. , fInlineDisplay()
  60. #endif
  61. {
  62. }
  63. ~AudioFilePlugin() override
  64. {
  65. fThread.stopNow();
  66. fPool.destroy();
  67. }
  68. protected:
  69. // -------------------------------------------------------------------
  70. // Plugin parameter calls
  71. uint32_t getParameterCount() const override
  72. {
  73. return 1;
  74. }
  75. const NativeParameter* getParameterInfo(const uint32_t index) const override
  76. {
  77. if (index != 0)
  78. return nullptr;
  79. static NativeParameter param;
  80. param.name = "Loop Mode";
  81. param.unit = nullptr;
  82. param.hints = static_cast<NativeParameterHints>(NATIVE_PARAMETER_IS_AUTOMABLE|NATIVE_PARAMETER_IS_ENABLED|NATIVE_PARAMETER_IS_BOOLEAN);
  83. param.ranges.def = 1.0f;
  84. param.ranges.min = 0.0f;
  85. param.ranges.max = 1.0f;
  86. param.ranges.step = 1.0f;
  87. param.ranges.stepSmall = 1.0f;
  88. param.ranges.stepLarge = 1.0f;
  89. param.scalePointCount = 0;
  90. param.scalePoints = nullptr;
  91. return &param;
  92. }
  93. float getParameterValue(const uint32_t index) const override
  94. {
  95. if (index != 0)
  96. return 0.0f;
  97. return fLoopMode ? 1.0f : 0.0f;
  98. }
  99. // -------------------------------------------------------------------
  100. // Plugin state calls
  101. void setParameterValue(const uint32_t index, const float value) override
  102. {
  103. if (index != 0)
  104. return;
  105. bool b = (value > 0.5f);
  106. if (b == fLoopMode)
  107. return;
  108. fLoopMode = b;
  109. fThread.setLoopingMode(b);
  110. }
  111. void setCustomData(const char* const key, const char* const value) override
  112. {
  113. if (std::strcmp(key, "file") != 0)
  114. return;
  115. invalidateNextFilename();
  116. loadFilename(value);
  117. }
  118. // -------------------------------------------------------------------
  119. // Plugin process calls
  120. void process2(const float* const*, float** const outBuffer, const uint32_t frames,
  121. const NativeMidiEvent*, uint32_t) override
  122. {
  123. const NativeTimeInfo* const timePos(getTimeInfo());
  124. float* out1 = outBuffer[0];
  125. float* out2 = outBuffer[1];
  126. if (! fDoProcess)
  127. {
  128. // carla_stderr("P: no process");
  129. carla_zeroFloats(out1, frames);
  130. carla_zeroFloats(out2, frames);
  131. return;
  132. }
  133. // not playing
  134. if (! timePos->playing)
  135. {
  136. // carla_stderr("P: not playing");
  137. if (timePos->frame == 0 && fWasPlayingBefore)
  138. fThread.setNeedsRead(timePos->frame);
  139. carla_zeroFloats(out1, frames);
  140. carla_zeroFloats(out2, frames);
  141. fWasPlayingBefore = false;
  142. return;
  143. }
  144. else
  145. {
  146. fWasPlayingBefore = true;
  147. }
  148. // out of reach
  149. if ((timePos->frame < fPool.startFrame || timePos->frame >= fMaxFrame) && !fLoopMode)
  150. {
  151. if (timePos->frame < fPool.startFrame)
  152. fThread.setNeedsRead(timePos->frame);
  153. carla_zeroFloats(out1, frames);
  154. carla_zeroFloats(out2, frames);
  155. #ifndef __MOD_DEVICES__
  156. if (fInlineDisplay.writtenValues < 32)
  157. {
  158. fInlineDisplay.lastValuesL[fInlineDisplay.writtenValues] = 0.0f;
  159. fInlineDisplay.lastValuesR[fInlineDisplay.writtenValues] = 0.0f;
  160. ++fInlineDisplay.writtenValues;
  161. }
  162. if (fInlineDisplay.pending == InlineDisplayNotPending)
  163. {
  164. fInlineDisplay.pending = InlineDisplayNeedRequest;
  165. hostRequestIdle();
  166. }
  167. #endif
  168. return;
  169. }
  170. if (fThread.isEntireFileLoaded())
  171. {
  172. // NOTE: timePos->frame is always < fMaxFrame (or looping)
  173. uint32_t targetStartFrame = static_cast<uint32_t>(fLoopMode ? timePos->frame % fMaxFrame : timePos->frame);
  174. for (uint32_t framesDone=0, framesToDo=frames, remainingFrames; framesDone < frames;)
  175. {
  176. if (targetStartFrame + framesToDo <= fMaxFrame)
  177. {
  178. // everything fits together
  179. carla_copyFloats(out1+framesDone, fPool.buffer[0]+targetStartFrame, framesToDo);
  180. carla_copyFloats(out2+framesDone, fPool.buffer[1]+targetStartFrame, framesToDo);
  181. break;
  182. }
  183. remainingFrames = std::min(fMaxFrame - targetStartFrame, framesToDo);
  184. carla_copyFloats(out1+framesDone, fPool.buffer[0]+targetStartFrame, remainingFrames);
  185. carla_copyFloats(out2+framesDone, fPool.buffer[1]+targetStartFrame, remainingFrames);
  186. framesDone += remainingFrames;
  187. framesToDo -= remainingFrames;
  188. if (! fLoopMode)
  189. {
  190. // not looping, stop here
  191. if (framesToDo != 0)
  192. {
  193. carla_zeroFloats(out1+framesDone, framesToDo);
  194. carla_zeroFloats(out2+framesDone, framesToDo);
  195. }
  196. break;
  197. }
  198. // reset for next loop
  199. targetStartFrame = 0;
  200. }
  201. }
  202. else
  203. {
  204. if (! fThread.tryPutData(out1, out2, timePos->frame, frames))
  205. {
  206. carla_zeroFloats(out1, frames);
  207. carla_zeroFloats(out2, frames);
  208. }
  209. }
  210. #ifndef __MOD_DEVICES__
  211. if (fInlineDisplay.writtenValues < 32)
  212. {
  213. fInlineDisplay.lastValuesL[fInlineDisplay.writtenValues] = carla_findMaxNormalizedFloat(out1, frames);
  214. fInlineDisplay.lastValuesR[fInlineDisplay.writtenValues] = carla_findMaxNormalizedFloat(out2, frames);
  215. ++fInlineDisplay.writtenValues;
  216. }
  217. if (fInlineDisplay.pending == InlineDisplayNotPending)
  218. {
  219. fInlineDisplay.pending = InlineDisplayNeedRequest;
  220. hostRequestIdle();
  221. }
  222. #endif
  223. }
  224. // -------------------------------------------------------------------
  225. // Plugin UI calls
  226. void uiShow(const bool show) override
  227. {
  228. if (! show)
  229. return;
  230. if (const char* const filename = uiOpenFile(false, "Open Audio File", ""))
  231. uiCustomDataChanged("file", filename);
  232. uiClosed();
  233. }
  234. // -------------------------------------------------------------------
  235. // Plugin state calls
  236. void setStateFromFile(const char* const filename) override
  237. {
  238. loadFilename(filename);
  239. }
  240. // -------------------------------------------------------------------
  241. // Plugin dispatcher calls
  242. void idle() override
  243. {
  244. NativePluginWithMidiPrograms<FileAudio>::idle();
  245. #ifndef __MOD_DEVICES__
  246. if (fInlineDisplay.pending == InlineDisplayNeedRequest)
  247. {
  248. fInlineDisplay.pending = InlineDisplayRequesting;
  249. hostQueueDrawInlineDisplay();
  250. }
  251. #endif
  252. }
  253. #ifndef __MOD_DEVICES__
  254. const NativeInlineDisplayImageSurface* renderInlineDisplay(const uint32_t rwidth, const uint32_t height) override
  255. {
  256. CARLA_SAFE_ASSERT_RETURN(height > 4, nullptr);
  257. const uint32_t width = rwidth == height ? height * 4 : rwidth;
  258. /* NOTE the code is this function is not optimized, still learning my way through pixels...
  259. */
  260. const size_t stride = width * 4;
  261. const size_t dataSize = stride * height;
  262. const uint pxToMove = fDoProcess ? fInlineDisplay.writtenValues : 0;
  263. uchar* data = fInlineDisplay.data;
  264. if (fInlineDisplay.dataSize != dataSize || data == nullptr)
  265. {
  266. delete[] data;
  267. data = new uchar[dataSize];
  268. std::memset(data, 0, dataSize);
  269. fInlineDisplay.data = data;
  270. fInlineDisplay.dataSize = dataSize;
  271. }
  272. else if (pxToMove != 0)
  273. {
  274. // shift all previous values to the left
  275. for (uint w=0; w < width - pxToMove; ++w)
  276. for (uint h=0; h < height; ++h)
  277. std::memmove(&data[h * stride + w * 4], &data[h * stride + (w+pxToMove) * 4], 4);
  278. }
  279. fInlineDisplay.width = static_cast<int>(width);
  280. fInlineDisplay.height = static_cast<int>(height);
  281. fInlineDisplay.stride = static_cast<int>(stride);
  282. if (pxToMove != 0)
  283. {
  284. const uint h2 = height / 2;
  285. // clear current line
  286. for (uint w=width-pxToMove; w < width; ++w)
  287. for (uint h=0; h < height; ++h)
  288. memset(&data[h * stride + w * 4], 0, 4);
  289. // draw upper/left
  290. for (uint i=0; i < pxToMove && i < 32; ++i)
  291. {
  292. const float valueL = fInlineDisplay.lastValuesL[i];
  293. const float valueR = fInlineDisplay.lastValuesR[i];
  294. const uint h2L = static_cast<uint>(valueL * (float)h2);
  295. const uint h2R = static_cast<uint>(valueR * (float)h2);
  296. const uint w = width - pxToMove + i;
  297. for (uint h=0; h < h2L; ++h)
  298. {
  299. // -30dB
  300. //if (valueL < 0.032f)
  301. // continue;
  302. data[(h2 - h) * stride + w * 4 + 3] = 160;
  303. // -12dB
  304. if (valueL < 0.25f)
  305. {
  306. data[(h2 - h) * stride + w * 4 + 1] = 255;
  307. }
  308. // -3dB
  309. else if (valueL < 0.70f)
  310. {
  311. data[(h2 - h) * stride + w * 4 + 2] = 255;
  312. data[(h2 - h) * stride + w * 4 + 1] = 255;
  313. }
  314. else
  315. {
  316. data[(h2 - h) * stride + w * 4 + 2] = 255;
  317. }
  318. }
  319. for (uint h=0; h < h2R; ++h)
  320. {
  321. // -30dB
  322. //if (valueR < 0.032f)
  323. // continue;
  324. data[(h2 + h) * stride + w * 4 + 3] = 160;
  325. // -12dB
  326. if (valueR < 0.25f)
  327. {
  328. data[(h2 + h) * stride + w * 4 + 1] = 255;
  329. }
  330. // -3dB
  331. else if (valueR < 0.70f)
  332. {
  333. data[(h2 + h) * stride + w * 4 + 2] = 255;
  334. data[(h2 + h) * stride + w * 4 + 1] = 255;
  335. }
  336. else
  337. {
  338. data[(h2 + h) * stride + w * 4 + 2] = 255;
  339. }
  340. }
  341. }
  342. }
  343. fInlineDisplay.writtenValues = 0;
  344. fInlineDisplay.pending = InlineDisplayNotPending;
  345. return (NativeInlineDisplayImageSurface*)(NativeInlineDisplayImageSurfaceCompat*)&fInlineDisplay;
  346. }
  347. #endif
  348. // -------------------------------------------------------------------
  349. private:
  350. bool fLoopMode;
  351. bool fDoProcess;
  352. bool fWasPlayingBefore;
  353. uint32_t fMaxFrame;
  354. AudioFilePool fPool;
  355. AudioFileThread fThread;
  356. NativeMidiPrograms fPrograms;
  357. #ifndef __MOD_DEVICES__
  358. struct InlineDisplay : NativeInlineDisplayImageSurfaceCompat {
  359. float lastValuesL[32];
  360. float lastValuesR[32];
  361. volatile PendingInlineDisplay pending;
  362. volatile uint8_t writtenValues;
  363. InlineDisplay()
  364. : NativeInlineDisplayImageSurfaceCompat(),
  365. # ifdef CARLA_PROPER_CPP11_SUPPORT
  366. lastValuesL{0.0f},
  367. lastValuesR{0.0f},
  368. # endif
  369. pending(InlineDisplayNotPending),
  370. writtenValues(0)
  371. {
  372. # ifndef CARLA_PROPER_CPP11_SUPPORT
  373. carla_zeroFloats(lastValuesL, 32);
  374. carla_zeroFloats(lastValuesR, 32);
  375. # endif
  376. }
  377. ~InlineDisplay()
  378. {
  379. if (data != nullptr)
  380. {
  381. delete[] data;
  382. data = nullptr;
  383. }
  384. }
  385. CARLA_DECLARE_NON_COPY_STRUCT(InlineDisplay)
  386. CARLA_PREVENT_HEAP_ALLOCATION
  387. } fInlineDisplay;
  388. #endif
  389. void loadFilename(const char* const filename)
  390. {
  391. CARLA_ASSERT(filename != nullptr);
  392. carla_debug("AudioFilePlugin::loadFilename(\"%s\")", filename);
  393. fThread.stopNow();
  394. fPool.destroy();
  395. if (filename == nullptr || *filename == '\0')
  396. {
  397. fDoProcess = false;
  398. fMaxFrame = 0;
  399. return;
  400. }
  401. if (fThread.loadFilename(filename, static_cast<uint32_t>(getSampleRate())))
  402. {
  403. fPool.create(fThread.getPoolNumFrames());
  404. fMaxFrame = fThread.getMaxFrame();
  405. if (fThread.isEntireFileLoaded())
  406. fThread.putAllData(fPool);
  407. else
  408. fThread.startNow();
  409. fDoProcess = true;
  410. }
  411. else
  412. {
  413. fDoProcess = false;
  414. fMaxFrame = 0;
  415. }
  416. }
  417. PluginClassEND(AudioFilePlugin)
  418. CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioFilePlugin)
  419. };
  420. // -----------------------------------------------------------------------
  421. static const NativePluginDescriptor audiofileDesc = {
  422. /* category */ NATIVE_PLUGIN_CATEGORY_UTILITY,
  423. /* hints */ static_cast<NativePluginHints>(NATIVE_PLUGIN_IS_RTSAFE
  424. |NATIVE_PLUGIN_HAS_UI
  425. #ifndef __MOD_DEVICES__
  426. |NATIVE_PLUGIN_HAS_INLINE_DISPLAY
  427. #endif
  428. |NATIVE_PLUGIN_REQUESTS_IDLE
  429. |NATIVE_PLUGIN_NEEDS_UI_OPEN_SAVE
  430. |NATIVE_PLUGIN_USES_TIME),
  431. /* supports */ NATIVE_PLUGIN_SUPPORTS_NOTHING,
  432. /* audioIns */ 0,
  433. /* audioOuts */ 2,
  434. /* midiIns */ 0,
  435. /* midiOuts */ 0,
  436. /* paramIns */ 1,
  437. /* paramOuts */ 0,
  438. /* name */ "Audio File",
  439. /* label */ "audiofile",
  440. /* maker */ "falkTX",
  441. /* copyright */ "GNU GPL v2+",
  442. PluginDescriptorFILL(AudioFilePlugin)
  443. };
  444. // -----------------------------------------------------------------------
  445. CARLA_EXPORT
  446. void carla_register_native_plugin_audiofile();
  447. CARLA_EXPORT
  448. void carla_register_native_plugin_audiofile()
  449. {
  450. carla_register_native_plugin(&audiofileDesc);
  451. }
  452. // -----------------------------------------------------------------------