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.

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