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