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.

699 lines
25KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2022 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 3 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 LICENSE file.
  16. */
  17. #include "plugincontext.hpp"
  18. #include "ModuleWidgets.hpp"
  19. #include "extra/Runner.hpp"
  20. #include "CarlaNativePlugin.h"
  21. #ifndef HEADLESS
  22. # include "ImGuiWidget.hpp"
  23. # include "ghc/filesystem.hpp"
  24. #endif
  25. #define BUFFER_SIZE 128
  26. // generates a warning if this is defined as anything else
  27. #define CARLA_API
  28. // --------------------------------------------------------------------------------------------------------------------
  29. std::size_t carla_getNativePluginCount() noexcept;
  30. const NativePluginDescriptor* carla_getNativePluginDescriptor(const std::size_t index) noexcept;
  31. // --------------------------------------------------------------------------------------------------------------------
  32. using namespace CARLA_BACKEND_NAMESPACE;
  33. static uint32_t host_get_buffer_size(NativeHostHandle);
  34. static double host_get_sample_rate(NativeHostHandle);
  35. static bool host_is_offline(NativeHostHandle);
  36. static const NativeTimeInfo* host_get_time_info(NativeHostHandle handle);
  37. static bool host_write_midi_event(NativeHostHandle handle, const NativeMidiEvent* event);
  38. static void host_ui_midi_program_changed(NativeHostHandle handle, uint8_t channel, uint32_t bank, uint32_t program);
  39. static void host_ui_custom_data_changed(NativeHostHandle handle, const char* key, const char* value);
  40. static intptr_t host_dispatcher(NativeHostHandle handle, NativeHostDispatcherOpcode opcode, int32_t index, intptr_t value, void* ptr, float opt);
  41. static void host_ui_parameter_changed(NativeHostHandle, uint32_t, float) {}
  42. static void host_ui_midi_program_changed(NativeHostHandle, uint8_t, uint32_t, uint32_t) {}
  43. static void host_ui_custom_data_changed(NativeHostHandle, const char*, const char*) {}
  44. static const char* host_ui_open_file(NativeHostHandle, bool, const char*, const char*) { return nullptr; }
  45. static const char* host_ui_save_file(NativeHostHandle, bool, const char*, const char*) { return nullptr; }
  46. static void host_ui_closed(NativeHostHandle) {}
  47. // --------------------------------------------------------------------------------------------------------------------
  48. struct CarlaInternalPluginModule : Module, Runner {
  49. enum ParamIds {
  50. NUM_PARAMS
  51. };
  52. enum InputIds {
  53. NUM_INPUTS
  54. };
  55. enum OutputIds {
  56. AUDIO_OUTPUT1,
  57. AUDIO_OUTPUT2,
  58. NUM_OUTPUTS
  59. };
  60. enum LightIds {
  61. NUM_LIGHTS
  62. };
  63. enum Parameters {
  64. kParameterLooping,
  65. kParameterHostSync,
  66. kParameterVolume,
  67. kParameterEnabled,
  68. kParameterInfoChannels,
  69. kParameterInfoBitRate,
  70. kParameterInfoBitDepth,
  71. kParameterInfoSampleRate,
  72. kParameterInfoLength,
  73. kParameterInfoPosition,
  74. kParameterInfoPoolFill,
  75. kParameterCount
  76. };
  77. CardinalPluginContext* const pcontext;
  78. const NativePluginDescriptor* fCarlaPluginDescriptor = nullptr;
  79. NativePluginHandle fCarlaPluginHandle = nullptr;
  80. NativeHostDescriptor fCarlaHostDescriptor = {};
  81. NativeTimeInfo fCarlaTimeInfo;
  82. float dataOut[NUM_OUTPUTS][BUFFER_SIZE];
  83. float* dataOutPtr[NUM_OUTPUTS];
  84. unsigned audioDataFill = 0;
  85. uint32_t lastProcessCounter = 0;
  86. bool fileChanged = false;
  87. std::string currentFile;
  88. struct {
  89. float preview[108];
  90. uint channels; // 4
  91. uint bitDepth; // 6
  92. uint sampleRate; // 7
  93. uint length; // 8
  94. float position; // 9
  95. } audioInfo;
  96. CarlaInternalPluginModule()
  97. : pcontext(static_cast<CardinalPluginContext*>(APP))
  98. {
  99. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  100. configOutput(0, "Audio Left");
  101. configOutput(1, "Audio Right");
  102. dataOutPtr[0] = dataOut[0];
  103. dataOutPtr[1] = dataOut[1];
  104. std::memset(dataOut, 0, sizeof(dataOut));
  105. std::memset(&audioInfo, 0, sizeof(audioInfo));
  106. for (std::size_t i=0, count=carla_getNativePluginCount(); i<count; ++i)
  107. {
  108. const NativePluginDescriptor* const desc = carla_getNativePluginDescriptor(i);
  109. if (std::strcmp(desc->label, "audiofile") != 0)
  110. continue;
  111. fCarlaPluginDescriptor = desc;
  112. break;
  113. }
  114. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginDescriptor != nullptr,);
  115. memset(&fCarlaHostDescriptor, 0, sizeof(fCarlaHostDescriptor));
  116. memset(&fCarlaTimeInfo, 0, sizeof(fCarlaTimeInfo));
  117. fCarlaHostDescriptor.handle = this;
  118. fCarlaHostDescriptor.resourceDir = "";
  119. fCarlaHostDescriptor.uiName = "Cardinal";
  120. fCarlaHostDescriptor.get_buffer_size = host_get_buffer_size;
  121. fCarlaHostDescriptor.get_sample_rate = host_get_sample_rate;
  122. fCarlaHostDescriptor.is_offline = host_is_offline;
  123. fCarlaHostDescriptor.get_time_info = host_get_time_info;
  124. fCarlaHostDescriptor.write_midi_event = host_write_midi_event;
  125. fCarlaHostDescriptor.ui_parameter_changed = host_ui_parameter_changed;
  126. fCarlaHostDescriptor.ui_midi_program_changed = host_ui_midi_program_changed;
  127. fCarlaHostDescriptor.ui_custom_data_changed = host_ui_custom_data_changed;
  128. fCarlaHostDescriptor.ui_closed = host_ui_closed;
  129. fCarlaHostDescriptor.ui_open_file = host_ui_open_file;
  130. fCarlaHostDescriptor.ui_save_file = host_ui_save_file;
  131. fCarlaHostDescriptor.dispatcher = host_dispatcher;
  132. fCarlaPluginHandle = fCarlaPluginDescriptor->instantiate(&fCarlaHostDescriptor);
  133. DISTRHO_SAFE_ASSERT_RETURN(fCarlaPluginHandle != nullptr,);
  134. fCarlaPluginDescriptor->activate(fCarlaPluginHandle);
  135. // host-sync disabled by default
  136. fCarlaPluginDescriptor->set_parameter_value(fCarlaPluginHandle, kParameterHostSync, 0.0f);
  137. startRunner(500);
  138. }
  139. ~CarlaInternalPluginModule() override
  140. {
  141. if (fCarlaPluginHandle == nullptr)
  142. return;
  143. stopRunner();
  144. fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
  145. fCarlaPluginDescriptor->cleanup(fCarlaPluginHandle);
  146. }
  147. bool run() override
  148. {
  149. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_IDLE, 0, 0, nullptr, 0.0f);
  150. return true;
  151. }
  152. const NativeTimeInfo* hostGetTimeInfo() const noexcept
  153. {
  154. return &fCarlaTimeInfo;
  155. }
  156. intptr_t hostDispatcher(const NativeHostDispatcherOpcode opcode,
  157. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  158. {
  159. switch (opcode)
  160. {
  161. // cannnot be supported
  162. case NATIVE_HOST_OPCODE_HOST_IDLE:
  163. break;
  164. // other stuff
  165. case NATIVE_HOST_OPCODE_NULL:
  166. case NATIVE_HOST_OPCODE_UPDATE_PARAMETER:
  167. case NATIVE_HOST_OPCODE_UPDATE_MIDI_PROGRAM:
  168. case NATIVE_HOST_OPCODE_RELOAD_PARAMETERS:
  169. case NATIVE_HOST_OPCODE_RELOAD_MIDI_PROGRAMS:
  170. case NATIVE_HOST_OPCODE_RELOAD_ALL:
  171. case NATIVE_HOST_OPCODE_UI_UNAVAILABLE:
  172. case NATIVE_HOST_OPCODE_INTERNAL_PLUGIN:
  173. case NATIVE_HOST_OPCODE_QUEUE_INLINE_DISPLAY:
  174. case NATIVE_HOST_OPCODE_UI_TOUCH_PARAMETER:
  175. case NATIVE_HOST_OPCODE_UI_RESIZE:
  176. break;
  177. case NATIVE_HOST_OPCODE_PREVIEW_BUFFER_DATA:
  178. std::memcpy(audioInfo.preview, ptr, sizeof(audioInfo.preview));
  179. break;
  180. case NATIVE_HOST_OPCODE_GET_FILE_PATH:
  181. case NATIVE_HOST_OPCODE_REQUEST_IDLE:
  182. break;
  183. }
  184. return 0;
  185. }
  186. json_t* dataToJson() override
  187. {
  188. json_t* const rootJ = json_object();
  189. DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, nullptr);
  190. json_object_set_new(rootJ, "filepath", json_string(currentFile.c_str()));
  191. if (fCarlaPluginHandle != nullptr)
  192. {
  193. const bool looping = fCarlaPluginDescriptor->get_parameter_value(fCarlaPluginHandle,
  194. kParameterLooping) > 0.5f;
  195. const bool hostSync = fCarlaPluginDescriptor->get_parameter_value(fCarlaPluginHandle,
  196. kParameterHostSync) > 0.5f;
  197. json_object_set_new(rootJ, "looping", json_boolean(looping));
  198. json_object_set_new(rootJ, "hostSync", json_boolean(hostSync));
  199. }
  200. return rootJ;
  201. }
  202. void dataFromJson(json_t* const rootJ) override
  203. {
  204. fileChanged = false;
  205. if (json_t* const filepathJ = json_object_get(rootJ, "filepath"))
  206. {
  207. const char* const filepath = json_string_value(filepathJ);
  208. if (filepath[0] != '\0')
  209. {
  210. currentFile = filepath;
  211. fileChanged = true;
  212. if (fCarlaPluginHandle != nullptr)
  213. fCarlaPluginDescriptor->set_custom_data(fCarlaPluginHandle, "file", filepath);
  214. }
  215. }
  216. if (! fileChanged)
  217. {
  218. currentFile.clear();
  219. fileChanged = true;
  220. }
  221. if (fCarlaPluginHandle == nullptr)
  222. return;
  223. if (json_t* const loopingJ = json_object_get(rootJ, "looping"))
  224. {
  225. const float value = json_boolean_value(loopingJ) ? 1.0f : 0.0f;
  226. fCarlaPluginDescriptor->set_parameter_value(fCarlaPluginHandle, kParameterLooping, value);
  227. }
  228. if (json_t* const hostSyncJ = json_object_get(rootJ, "hostSync"))
  229. {
  230. const float value = json_boolean_value(hostSyncJ) ? 1.0f : 0.0f;
  231. fCarlaPluginDescriptor->set_parameter_value(fCarlaPluginHandle, kParameterHostSync, value);
  232. }
  233. }
  234. void process(const ProcessArgs&) override
  235. {
  236. if (fCarlaPluginHandle == nullptr)
  237. return;
  238. const unsigned k = audioDataFill++;
  239. outputs[0].setVoltage(dataOut[0][k] * 10.0f);
  240. outputs[1].setVoltage(dataOut[1][k] * 10.0f);
  241. if (audioDataFill == BUFFER_SIZE)
  242. {
  243. const uint32_t processCounter = pcontext->processCounter;
  244. // Update time position if running a new audio block
  245. if (lastProcessCounter != processCounter)
  246. {
  247. lastProcessCounter = processCounter;
  248. fCarlaTimeInfo.playing = pcontext->playing;
  249. fCarlaTimeInfo.frame = pcontext->frame;
  250. }
  251. // or advance time by BUFFER_SIZE frames if still under the same audio block
  252. else if (fCarlaTimeInfo.playing)
  253. {
  254. fCarlaTimeInfo.frame += BUFFER_SIZE;
  255. }
  256. audioDataFill = 0;
  257. fCarlaPluginDescriptor->process(fCarlaPluginHandle, nullptr, dataOutPtr, BUFFER_SIZE, nullptr, 0);
  258. audioInfo.channels = fCarlaPluginDescriptor->get_parameter_value(fCarlaPluginHandle, 4);
  259. audioInfo.bitDepth = fCarlaPluginDescriptor->get_parameter_value(fCarlaPluginHandle, 6);
  260. audioInfo.sampleRate = fCarlaPluginDescriptor->get_parameter_value(fCarlaPluginHandle, 7);
  261. audioInfo.length = fCarlaPluginDescriptor->get_parameter_value(fCarlaPluginHandle, 8);
  262. audioInfo.position = fCarlaPluginDescriptor->get_parameter_value(fCarlaPluginHandle, 9);
  263. }
  264. }
  265. void onSampleRateChange(const SampleRateChangeEvent& e) override
  266. {
  267. if (fCarlaPluginHandle == nullptr)
  268. return;
  269. fCarlaPluginDescriptor->deactivate(fCarlaPluginHandle);
  270. fCarlaPluginDescriptor->dispatcher(fCarlaPluginHandle, NATIVE_PLUGIN_OPCODE_SAMPLE_RATE_CHANGED,
  271. 0, 0, nullptr, e.sampleRate);
  272. fCarlaPluginDescriptor->activate(fCarlaPluginHandle);
  273. }
  274. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaInternalPluginModule)
  275. };
  276. // -----------------------------------------------------------------------------------------------------------
  277. static uint32_t host_get_buffer_size(NativeHostHandle)
  278. {
  279. return BUFFER_SIZE;
  280. }
  281. static double host_get_sample_rate(const NativeHostHandle handle)
  282. {
  283. CardinalPluginContext* const pcontext = static_cast<CarlaInternalPluginModule*>(handle)->pcontext;
  284. DISTRHO_SAFE_ASSERT_RETURN(pcontext != nullptr, 48000.0);
  285. return pcontext->sampleRate;
  286. }
  287. static bool host_is_offline(NativeHostHandle)
  288. {
  289. return false;
  290. }
  291. static const NativeTimeInfo* host_get_time_info(const NativeHostHandle handle)
  292. {
  293. return static_cast<CarlaInternalPluginModule*>(handle)->hostGetTimeInfo();
  294. }
  295. static bool host_write_midi_event(NativeHostHandle, const NativeMidiEvent*)
  296. {
  297. return false;
  298. }
  299. static intptr_t host_dispatcher(const NativeHostHandle handle, const NativeHostDispatcherOpcode opcode,
  300. const int32_t index, const intptr_t value, void* const ptr, const float opt)
  301. {
  302. return static_cast<CarlaInternalPluginModule*>(handle)->hostDispatcher(opcode, index, value, ptr, opt);
  303. }
  304. // --------------------------------------------------------------------------------------------------------------------
  305. #ifndef HEADLESS
  306. struct AudioFileListWidget : ImGuiWidget {
  307. CarlaInternalPluginModule* const module;
  308. bool showError = false;
  309. String errorMessage;
  310. struct ghcFile {
  311. std::string full, base;
  312. bool operator<(const ghcFile& other) const noexcept { return base < other.base; }
  313. };
  314. std::string currentDirectory;
  315. std::vector<ghcFile> currentFiles;
  316. size_t selectedFile = (size_t)-1;
  317. AudioFileListWidget(CarlaInternalPluginModule* const m)
  318. : ImGuiWidget(),
  319. module(m)
  320. {
  321. if (module->fileChanged)
  322. reloadDir();
  323. }
  324. void drawImGui() override
  325. {
  326. const float scaleFactor = getScaleFactor();
  327. const int flags = ImGuiWindowFlags_NoSavedSettings
  328. | ImGuiWindowFlags_NoTitleBar
  329. | ImGuiWindowFlags_NoResize
  330. | ImGuiWindowFlags_NoCollapse
  331. | ImGuiWindowFlags_NoScrollbar
  332. | ImGuiWindowFlags_NoScrollWithMouse;
  333. ImGui::SetNextWindowPos(ImVec2(0, 0));
  334. ImGui::SetNextWindowSize(ImVec2(box.size.x * scaleFactor, box.size.y * scaleFactor));
  335. if (ImGui::Begin("Plugin List", nullptr, ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize))
  336. {
  337. if (showError)
  338. {
  339. showError = false;
  340. ImGui::OpenPopup("Audio File Error");
  341. }
  342. if (ImGui::BeginPopupModal("Audio File Error", nullptr, flags))
  343. {
  344. ImGui::TextWrapped("Failed to load audio file, error was:\n%s", errorMessage.buffer());
  345. ImGui::Separator();
  346. if (ImGui::Button("Ok"))
  347. ImGui::CloseCurrentPopup();
  348. ImGui::EndPopup();
  349. }
  350. else if (ImGui::BeginTable("pluginlist", 1, ImGuiTableFlags_NoSavedSettings))
  351. {
  352. for (size_t i=0, count=currentFiles.size(); i < count; ++i)
  353. {
  354. bool wasSelected = selectedFile == i;
  355. bool selected = wasSelected;
  356. ImGui::TableNextRow();
  357. ImGui::TableSetColumnIndex(0);
  358. ImGui::Selectable(currentFiles[i].base.c_str(), &selected);
  359. if (selected && ! wasSelected)
  360. {
  361. selectedFile = i;
  362. module->currentFile = currentFiles[i].full;
  363. module->fCarlaPluginDescriptor->set_custom_data(module->fCarlaPluginHandle, "file", currentFiles[i].full.c_str());
  364. }
  365. }
  366. ImGui::EndTable();
  367. }
  368. }
  369. ImGui::End();
  370. }
  371. void step() override
  372. {
  373. if (module->fileChanged)
  374. reloadDir();
  375. ImGuiWidget::step();
  376. }
  377. void reloadDir()
  378. {
  379. module->fileChanged = false;
  380. currentFiles.clear();
  381. selectedFile = (size_t)-1;
  382. static constexpr const char* const supportedExtensions[] = {
  383. #ifdef HAVE_SNDFILE
  384. ".aif",".aifc",".aiff",".au",".bwf",".flac",".htk",".iff",".mat4",".mat5",".oga",".ogg",
  385. ".paf",".pvf",".pvf5",".sd2",".sf",".snd",".svx",".vcc",".w64",".wav",".xi",
  386. #endif
  387. ".mp3"
  388. };
  389. using namespace ghc::filesystem;
  390. const path currentFile = u8path(module->currentFile);
  391. currentDirectory = currentFile.parent_path().generic_u8string();
  392. directory_iterator it;
  393. try {
  394. it = directory_iterator(u8path(currentDirectory));
  395. } DISTRHO_SAFE_EXCEPTION_RETURN("Failed to open current directory",);
  396. for (directory_iterator itb = begin(it), ite=end(it); itb != ite; ++itb)
  397. {
  398. if (! itb->is_regular_file())
  399. continue;
  400. const path filepath = itb->path();
  401. const path extension = filepath.extension();
  402. for (size_t i=0; i<ARRAY_SIZE(supportedExtensions); ++i)
  403. {
  404. if (extension.compare(supportedExtensions[i]) == 0)
  405. {
  406. currentFiles.push_back({ filepath.generic_u8string(), filepath.filename().generic_u8string() });
  407. break;
  408. }
  409. }
  410. }
  411. std::sort(currentFiles.begin(), currentFiles.end());
  412. for (size_t index = 0; index < currentFiles.size(); ++index)
  413. {
  414. if (currentFiles[index].full.compare(currentFile) == 0)
  415. {
  416. selectedFile = index;
  417. break;
  418. }
  419. }
  420. }
  421. };
  422. struct AudioFileWidget : ModuleWidgetWithSideScrews<23> {
  423. static constexpr const float previewBoxHeight = 80.0f;
  424. static constexpr const float previewBoxBottom = 20.0f;
  425. static constexpr const float previewBoxRect[] = {8.0f,
  426. 380.0f - previewBoxHeight - previewBoxBottom,
  427. 15.0f * 23 - 16.0f,
  428. previewBoxHeight};
  429. static constexpr const float startY_list = startY - 2.0f;
  430. static constexpr const float fileListHeight = 380.0f - startY_list - previewBoxHeight - previewBoxBottom * 1.5f;
  431. static constexpr const float startY_preview = startY_list + fileListHeight;
  432. CarlaInternalPluginModule* const module;
  433. bool idleCallbackActive = false;
  434. bool visible = false;
  435. float lastPosition = 0.0f;
  436. AudioFileWidget(CarlaInternalPluginModule* const m)
  437. : module(m)
  438. {
  439. setModule(module);
  440. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/AudioFile.svg")));
  441. addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, 0)));
  442. addChild(createWidget<ScrewBlack>(Vec(box.size.x - 4 * RACK_GRID_WIDTH, 0)));
  443. addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  444. addChild(createWidget<ScrewBlack>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  445. addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY_list * 0.5f - padding + 2.0f), module, 0));
  446. addOutput(createOutput<PJ301MPort>(Vec(startX_Out, startY_list * 0.5f + 2.0f), module, 1));
  447. if (m != nullptr)
  448. {
  449. AudioFileListWidget* const listw = new AudioFileListWidget(m);
  450. listw->box.pos = Vec(0, startY_list);
  451. listw->box.size = Vec(box.size.x, fileListHeight);
  452. addChild(listw);
  453. }
  454. }
  455. void drawLayer(const DrawArgs& args, int layer) override
  456. {
  457. if (layer != 1)
  458. return ModuleWidget::drawLayer(args, layer);
  459. const float alpha = 1.0f - (0.5f - settings::rackBrightness * 0.5f);
  460. char textInfo[0xff];
  461. if (module != nullptr && module->audioInfo.channels != 0)
  462. {
  463. const float audioPreviewBarHeight = previewBoxRect[3] - 20.0f;
  464. const size_t position = (module->audioInfo.position * 0.01f) * ARRAY_SIZE(module->audioInfo.preview);
  465. nvgFillColor(args.vg, nvgRGBAf(0.839f, 0.459f, 0.086f, alpha));
  466. for (size_t i=0; i<ARRAY_SIZE(module->audioInfo.preview); ++i)
  467. {
  468. const float value = module->audioInfo.preview[i];
  469. const float height = std::max(0.01f, value * audioPreviewBarHeight);
  470. const float y = previewBoxRect[1] + audioPreviewBarHeight - height;
  471. if (position == i)
  472. nvgFillColor(args.vg, nvgRGBAf(1.0f, 1.0f, 1.0f, alpha));
  473. nvgBeginPath(args.vg);
  474. nvgRect(args.vg, previewBoxRect[0] + 3 + 3 * i, y + 2, 2, height);
  475. nvgFill(args.vg);
  476. }
  477. std::snprintf(textInfo, sizeof(textInfo), "%s %d-Bit, %.1fkHz, %dm%02ds",
  478. module->audioInfo.channels == 1 ? "Mono" : module->audioInfo.channels == 2 ? "Stereo" : "Other",
  479. module->audioInfo.bitDepth,
  480. static_cast<float>(module->audioInfo.sampleRate)/1000.0f,
  481. module->audioInfo.length / 60,
  482. module->audioInfo.length % 60);
  483. }
  484. else
  485. {
  486. std::strcpy(textInfo, "No file loaded");
  487. }
  488. nvgFillColor(args.vg, nvgRGBAf(1.0f, 1.0f, 1.0f, alpha));
  489. nvgFontFaceId(args.vg, 0);
  490. nvgFontSize(args.vg, 13);
  491. nvgTextAlign(args.vg, NVG_ALIGN_LEFT);
  492. nvgText(args.vg, previewBoxRect[0] + 4, previewBoxRect[1] + previewBoxRect[3] - 6, textInfo, nullptr);
  493. }
  494. void draw(const DrawArgs& args) override
  495. {
  496. drawBackground(args.vg);
  497. drawPreviewBox(args.vg);
  498. drawOutputJacksArea(args.vg);
  499. ModuleWidget::draw(args);
  500. }
  501. void drawPreviewBox(NVGcontext* const vg)
  502. {
  503. nvgBeginPath(vg);
  504. nvgRoundedRect(vg, previewBoxRect[0], previewBoxRect[1], previewBoxRect[2], previewBoxRect[3], 4.0f);
  505. nvgFillColor(vg, nvgRGB(0x75, 0x17, 0x00));
  506. nvgFill(vg);
  507. nvgStrokeWidth(vg, 2.0f);
  508. nvgStrokeColor(vg, nvgRGB(0xd6, 0x75, 0x16));
  509. nvgStroke(vg);
  510. }
  511. void drawOutputJacksArea(NVGcontext* const vg)
  512. {
  513. nvgBeginPath(vg);
  514. nvgRoundedRect(vg, startX_Out - 2.5f, startY_list * 0.5f - padding, padding, padding * 2, 4);
  515. nvgFillColor(vg, nvgRGB(0xd0, 0xd0, 0xd0));
  516. nvgFill(vg);
  517. }
  518. void appendContextMenu(ui::Menu* const menu) override
  519. {
  520. menu->addChild(new ui::MenuSeparator);
  521. const bool looping = module->fCarlaPluginDescriptor->get_parameter_value(module->fCarlaPluginHandle,
  522. CarlaInternalPluginModule::kParameterLooping) > 0.5f;
  523. const bool hostSync = module->fCarlaPluginDescriptor->get_parameter_value(module->fCarlaPluginHandle,
  524. CarlaInternalPluginModule::kParameterHostSync) > 0.5f;
  525. menu->addChild(createMenuItem("Looping", looping ? CHECKMARK_STRING : "",
  526. [=]() { module->fCarlaPluginDescriptor->set_parameter_value(module->fCarlaPluginHandle,
  527. CarlaInternalPluginModule::kParameterLooping, looping ? 0.0f : 1.0f); }
  528. ));
  529. menu->addChild(createMenuItem("Host sync", hostSync ? CHECKMARK_STRING : "",
  530. [=]() { module->fCarlaPluginDescriptor->set_parameter_value(module->fCarlaPluginHandle,
  531. CarlaInternalPluginModule::kParameterHostSync, hostSync ? 0.0f : 1.0f); }
  532. ));
  533. struct LoadAudioFileItem : MenuItem {
  534. CarlaInternalPluginModule* const module;
  535. LoadAudioFileItem(CarlaInternalPluginModule* const m)
  536. : module(m)
  537. {
  538. text = "Load audio file...";
  539. }
  540. void onAction(const event::Action&) override
  541. {
  542. CarlaInternalPluginModule* const module = this->module;
  543. async_dialog_filebrowser(false, nullptr, text.c_str(), [module](char* path)
  544. {
  545. if (path == nullptr)
  546. return;
  547. module->currentFile = path;
  548. module->fileChanged = true;
  549. module->fCarlaPluginDescriptor->set_custom_data(module->fCarlaPluginHandle, "file", path);
  550. std::free(path);
  551. });
  552. }
  553. };
  554. menu->addChild(new LoadAudioFileItem(module));
  555. }
  556. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioFileWidget)
  557. };
  558. #else
  559. struct AudioFileWidget : ModuleWidget {
  560. AudioFileWidget(CarlaInternalPluginModule* const module) {
  561. setModule(module);
  562. addOutput(createOutput<PJ301MPort>({}, module, 0));
  563. addOutput(createOutput<PJ301MPort>({}, module, 1));
  564. }
  565. };
  566. #endif
  567. // --------------------------------------------------------------------------------------------------------------------
  568. Model* modelAudioFile = createModel<CarlaInternalPluginModule, AudioFileWidget>("AudioFile");
  569. // --------------------------------------------------------------------------------------------------------------------