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.

audio-file.cpp 16KB

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