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.

484 lines
15KB

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