The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

675 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. The code included in this file is provided under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  7. To use, copy, modify, and/or distribute this software for any purpose with or
  8. without fee is hereby granted provided that the above copyright notice and
  9. this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
  11. WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
  12. PURPOSE, ARE DISCLAIMED.
  13. ==============================================================================
  14. */
  15. #pragma once
  16. using namespace dsp;
  17. //==============================================================================
  18. struct DSPDemoParameterBase : public ChangeBroadcaster
  19. {
  20. DSPDemoParameterBase (const String& labelName) : name (labelName) {}
  21. virtual ~DSPDemoParameterBase() = default;
  22. virtual Component* getComponent() = 0;
  23. virtual int getPreferredHeight() = 0;
  24. virtual int getPreferredWidth() = 0;
  25. String name;
  26. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSPDemoParameterBase)
  27. };
  28. //==============================================================================
  29. struct SliderParameter final : public DSPDemoParameterBase
  30. {
  31. SliderParameter (Range<double> range, double skew, double initialValue,
  32. const String& labelName, const String& suffix = {})
  33. : DSPDemoParameterBase (labelName)
  34. {
  35. slider.setRange (range.getStart(), range.getEnd(), 0.01);
  36. slider.setSkewFactor (skew);
  37. slider.setValue (initialValue);
  38. if (suffix.isNotEmpty())
  39. slider.setTextValueSuffix (suffix);
  40. slider.onValueChange = [this] { sendChangeMessage(); };
  41. }
  42. Component* getComponent() override { return &slider; }
  43. int getPreferredHeight() override { return 40; }
  44. int getPreferredWidth() override { return 500; }
  45. double getCurrentValue() const { return slider.getValue(); }
  46. private:
  47. Slider slider;
  48. };
  49. //==============================================================================
  50. struct ChoiceParameter final : public DSPDemoParameterBase
  51. {
  52. ChoiceParameter (const StringArray& options, int initialId, const String& labelName)
  53. : DSPDemoParameterBase (labelName)
  54. {
  55. parameterBox.addItemList (options, 1);
  56. parameterBox.onChange = [this] { sendChangeMessage(); };
  57. parameterBox.setSelectedId (initialId);
  58. }
  59. Component* getComponent() override { return &parameterBox; }
  60. int getPreferredHeight() override { return 25; }
  61. int getPreferredWidth() override { return 250; }
  62. int getCurrentSelectedID() const { return parameterBox.getSelectedId(); }
  63. private:
  64. ComboBox parameterBox;
  65. };
  66. //==============================================================================
  67. class AudioThumbnailComponent final : public Component,
  68. public FileDragAndDropTarget,
  69. public ChangeBroadcaster,
  70. private ChangeListener,
  71. private Timer
  72. {
  73. public:
  74. AudioThumbnailComponent (AudioDeviceManager& adm, AudioFormatManager& afm)
  75. : audioDeviceManager (adm),
  76. thumbnailCache (5),
  77. thumbnail (128, afm, thumbnailCache)
  78. {
  79. thumbnail.addChangeListener (this);
  80. }
  81. ~AudioThumbnailComponent() override
  82. {
  83. thumbnail.removeChangeListener (this);
  84. }
  85. void paint (Graphics& g) override
  86. {
  87. g.fillAll (Colour (0xff495358));
  88. g.setColour (Colours::white);
  89. if (thumbnail.getTotalLength() > 0.0)
  90. {
  91. thumbnail.drawChannels (g, getLocalBounds().reduced (2),
  92. 0.0, thumbnail.getTotalLength(), 1.0f);
  93. g.setColour (Colours::black);
  94. g.fillRect (static_cast<float> (currentPosition * getWidth()), 0.0f,
  95. 1.0f, static_cast<float> (getHeight()));
  96. }
  97. else
  98. {
  99. g.drawFittedText ("No audio file loaded.\nDrop a file here or click the \"Load File...\" button.", getLocalBounds(),
  100. Justification::centred, 2);
  101. }
  102. }
  103. bool isInterestedInFileDrag (const StringArray&) override { return true; }
  104. void filesDropped (const StringArray& files, int, int) override { loadURL (URL (File (files[0])), true); }
  105. void setCurrentURL (const URL& u)
  106. {
  107. if (currentURL == u)
  108. return;
  109. loadURL (u);
  110. }
  111. URL getCurrentURL() const { return currentURL; }
  112. void setTransportSource (AudioTransportSource* newSource)
  113. {
  114. transportSource = newSource;
  115. struct ResetCallback final : public CallbackMessage
  116. {
  117. ResetCallback (AudioThumbnailComponent& o) : owner (o) {}
  118. void messageCallback() override { owner.reset(); }
  119. AudioThumbnailComponent& owner;
  120. };
  121. (new ResetCallback (*this))->post();
  122. }
  123. private:
  124. AudioDeviceManager& audioDeviceManager;
  125. AudioThumbnailCache thumbnailCache;
  126. AudioThumbnail thumbnail;
  127. AudioTransportSource* transportSource = nullptr;
  128. URL currentURL;
  129. double currentPosition = 0.0;
  130. //==============================================================================
  131. void changeListenerCallback (ChangeBroadcaster*) override { repaint(); }
  132. void reset()
  133. {
  134. currentPosition = 0.0;
  135. repaint();
  136. if (transportSource == nullptr)
  137. stopTimer();
  138. else
  139. startTimerHz (25);
  140. }
  141. void loadURL (const URL& u, bool notify = false)
  142. {
  143. if (currentURL == u)
  144. return;
  145. currentURL = u;
  146. thumbnail.setSource (makeInputSource (u).release());
  147. if (notify)
  148. sendChangeMessage();
  149. }
  150. void timerCallback() override
  151. {
  152. if (transportSource != nullptr)
  153. {
  154. currentPosition = transportSource->getCurrentPosition() / thumbnail.getTotalLength();
  155. repaint();
  156. }
  157. }
  158. void mouseDrag (const MouseEvent& e) override
  159. {
  160. if (transportSource != nullptr)
  161. {
  162. const ScopedLock sl (audioDeviceManager.getAudioCallbackLock());
  163. transportSource->setPosition ((jmax (static_cast<double> (e.x), 0.0) / getWidth())
  164. * thumbnail.getTotalLength());
  165. }
  166. }
  167. };
  168. //==============================================================================
  169. class DemoParametersComponent final : public Component
  170. {
  171. public:
  172. DemoParametersComponent (const std::vector<DSPDemoParameterBase*>& demoParams)
  173. {
  174. parameters = demoParams;
  175. for (auto demoParameter : parameters)
  176. {
  177. addAndMakeVisible (demoParameter->getComponent());
  178. auto* paramLabel = new Label ({}, demoParameter->name);
  179. paramLabel->attachToComponent (demoParameter->getComponent(), true);
  180. paramLabel->setJustificationType (Justification::centredLeft);
  181. addAndMakeVisible (paramLabel);
  182. labels.add (paramLabel);
  183. }
  184. }
  185. void resized() override
  186. {
  187. auto bounds = getLocalBounds();
  188. bounds.removeFromLeft (100);
  189. for (auto* p : parameters)
  190. {
  191. auto* comp = p->getComponent();
  192. comp->setSize (jmin (bounds.getWidth(), p->getPreferredWidth()), p->getPreferredHeight());
  193. auto compBounds = bounds.removeFromTop (p->getPreferredHeight());
  194. comp->setCentrePosition (compBounds.getCentre());
  195. }
  196. }
  197. int getHeightNeeded()
  198. {
  199. auto height = 0;
  200. for (auto* p : parameters)
  201. height += p->getPreferredHeight();
  202. return height + 10;
  203. }
  204. private:
  205. std::vector<DSPDemoParameterBase*> parameters;
  206. OwnedArray<Label> labels;
  207. };
  208. //==============================================================================
  209. template <class DemoType>
  210. struct DSPDemo final : public AudioSource,
  211. public ProcessorWrapper<DemoType>,
  212. private ChangeListener
  213. {
  214. DSPDemo (AudioSource& input)
  215. : inputSource (&input)
  216. {
  217. for (auto* p : getParameters())
  218. p->addChangeListener (this);
  219. }
  220. void prepareToPlay (int blockSize, double sampleRate) override
  221. {
  222. inputSource->prepareToPlay (blockSize, sampleRate);
  223. this->prepare ({ sampleRate, (uint32) blockSize, 2 });
  224. }
  225. void releaseResources() override
  226. {
  227. inputSource->releaseResources();
  228. }
  229. void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
  230. {
  231. if (bufferToFill.buffer == nullptr)
  232. {
  233. jassertfalse;
  234. return;
  235. }
  236. inputSource->getNextAudioBlock (bufferToFill);
  237. AudioBlock<float> block (*bufferToFill.buffer,
  238. (size_t) bufferToFill.startSample);
  239. ScopedLock audioLock (audioCallbackLock);
  240. this->process (ProcessContextReplacing<float> (block));
  241. }
  242. const std::vector<DSPDemoParameterBase*>& getParameters()
  243. {
  244. return this->processor.parameters;
  245. }
  246. void changeListenerCallback (ChangeBroadcaster*) override
  247. {
  248. ScopedLock audioLock (audioCallbackLock);
  249. static_cast<DemoType&> (this->processor).updateParameters();
  250. }
  251. CriticalSection audioCallbackLock;
  252. AudioSource* inputSource;
  253. };
  254. //==============================================================================
  255. template <class DemoType>
  256. class AudioFileReaderComponent final : public Component,
  257. private TimeSliceThread,
  258. private Value::Listener,
  259. private ChangeListener
  260. {
  261. public:
  262. //==============================================================================
  263. AudioFileReaderComponent()
  264. : TimeSliceThread ("Audio File Reader Thread"),
  265. header (audioDeviceManager, formatManager, *this)
  266. {
  267. loopState.addListener (this);
  268. formatManager.registerBasicFormats();
  269. audioDeviceManager.addAudioCallback (&audioSourcePlayer);
  270. #ifndef JUCE_DEMO_RUNNER
  271. audioDeviceManager.initialiseWithDefaultDevices (0, 2);
  272. #endif
  273. init();
  274. startThread();
  275. setOpaque (true);
  276. addAndMakeVisible (header);
  277. setSize (800, 250);
  278. }
  279. ~AudioFileReaderComponent() override
  280. {
  281. signalThreadShouldExit();
  282. stop();
  283. audioDeviceManager.removeAudioCallback (&audioSourcePlayer);
  284. waitForThreadToExit (10000);
  285. }
  286. void paint (Graphics& g) override
  287. {
  288. g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
  289. g.fillRect (getLocalBounds());
  290. }
  291. void resized() override
  292. {
  293. auto r = getLocalBounds();
  294. header.setBounds (r.removeFromTop (120));
  295. r.removeFromTop (20);
  296. if (parametersComponent != nullptr)
  297. parametersComponent->setBounds (r.removeFromTop (parametersComponent->getHeightNeeded()).reduced (20, 0));
  298. }
  299. //==============================================================================
  300. bool loadURL (const URL& fileToPlay)
  301. {
  302. stop();
  303. audioSourcePlayer.setSource (nullptr);
  304. getThumbnailComponent().setTransportSource (nullptr);
  305. transportSource.reset();
  306. readerSource.reset();
  307. auto source = makeInputSource (fileToPlay);
  308. if (source == nullptr)
  309. return false;
  310. auto stream = rawToUniquePtr (source->createInputStream());
  311. if (stream == nullptr)
  312. return false;
  313. reader = rawToUniquePtr (formatManager.createReaderFor (std::move (stream)));
  314. if (reader == nullptr)
  315. return false;
  316. readerSource.reset (new AudioFormatReaderSource (reader.get(), false));
  317. readerSource->setLooping (loopState.getValue());
  318. init();
  319. resized();
  320. return true;
  321. }
  322. void togglePlay()
  323. {
  324. if (playState.getValue())
  325. stop();
  326. else
  327. play();
  328. }
  329. void stop()
  330. {
  331. playState = false;
  332. if (transportSource.get() != nullptr)
  333. {
  334. transportSource->stop();
  335. transportSource->setPosition (0);
  336. }
  337. }
  338. void init()
  339. {
  340. if (transportSource.get() == nullptr)
  341. {
  342. transportSource.reset (new AudioTransportSource());
  343. transportSource->addChangeListener (this);
  344. if (readerSource != nullptr)
  345. {
  346. if (auto* device = audioDeviceManager.getCurrentAudioDevice())
  347. {
  348. transportSource->setSource (readerSource.get(), roundToInt (device->getCurrentSampleRate()), this, reader->sampleRate);
  349. getThumbnailComponent().setTransportSource (transportSource.get());
  350. }
  351. }
  352. }
  353. audioSourcePlayer.setSource (nullptr);
  354. currentDemo.reset();
  355. if (currentDemo.get() == nullptr)
  356. currentDemo.reset (new DSPDemo<DemoType> (*transportSource));
  357. audioSourcePlayer.setSource (currentDemo.get());
  358. auto& parameters = currentDemo->getParameters();
  359. parametersComponent.reset();
  360. if (! parameters.empty())
  361. {
  362. parametersComponent = std::make_unique<DemoParametersComponent> (parameters);
  363. addAndMakeVisible (parametersComponent.get());
  364. }
  365. }
  366. void play()
  367. {
  368. if (readerSource == nullptr)
  369. return;
  370. if (transportSource->getCurrentPosition() >= transportSource->getLengthInSeconds()
  371. || transportSource->getCurrentPosition() < 0)
  372. transportSource->setPosition (0);
  373. transportSource->start();
  374. playState = true;
  375. }
  376. void setLooping (bool shouldLoop)
  377. {
  378. if (readerSource != nullptr)
  379. readerSource->setLooping (shouldLoop);
  380. }
  381. AudioThumbnailComponent& getThumbnailComponent() { return header.thumbnailComp; }
  382. private:
  383. //==============================================================================
  384. class AudioPlayerHeader final : public Component,
  385. private ChangeListener,
  386. private Value::Listener
  387. {
  388. public:
  389. AudioPlayerHeader (AudioDeviceManager& adm,
  390. AudioFormatManager& afm,
  391. AudioFileReaderComponent& afr)
  392. : thumbnailComp (adm, afm),
  393. audioFileReader (afr)
  394. {
  395. setOpaque (true);
  396. addAndMakeVisible (loadButton);
  397. addAndMakeVisible (playButton);
  398. addAndMakeVisible (loopButton);
  399. playButton.setColour (TextButton::buttonColourId, Colour (0xff79ed7f));
  400. playButton.setColour (TextButton::textColourOffId, Colours::black);
  401. loadButton.setColour (TextButton::buttonColourId, Colour (0xff797fed));
  402. loadButton.setColour (TextButton::textColourOffId, Colours::black);
  403. loadButton.onClick = [this] { openFile(); };
  404. playButton.onClick = [this] { audioFileReader.togglePlay(); };
  405. addAndMakeVisible (thumbnailComp);
  406. thumbnailComp.addChangeListener (this);
  407. audioFileReader.playState.addListener (this);
  408. loopButton.getToggleStateValue().referTo (audioFileReader.loopState);
  409. }
  410. ~AudioPlayerHeader() override
  411. {
  412. audioFileReader.playState.removeListener (this);
  413. }
  414. void paint (Graphics& g) override
  415. {
  416. g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker());
  417. g.fillRect (getLocalBounds());
  418. }
  419. void resized() override
  420. {
  421. auto bounds = getLocalBounds();
  422. auto buttonBounds = bounds.removeFromLeft (jmin (250, bounds.getWidth() / 4));
  423. auto loopBounds = buttonBounds.removeFromBottom (30);
  424. loadButton.setBounds (buttonBounds.removeFromTop (buttonBounds.getHeight() / 2));
  425. playButton.setBounds (buttonBounds);
  426. loopButton.setSize (0, 25);
  427. loopButton.changeWidthToFitText();
  428. loopButton.setCentrePosition (loopBounds.getCentre());
  429. thumbnailComp.setBounds (bounds);
  430. }
  431. AudioThumbnailComponent thumbnailComp;
  432. private:
  433. //==============================================================================
  434. void openFile()
  435. {
  436. audioFileReader.stop();
  437. if (fileChooser != nullptr)
  438. return;
  439. if (! RuntimePermissions::isGranted (RuntimePermissions::readExternalStorage))
  440. {
  441. SafePointer<AudioPlayerHeader> safeThis (this);
  442. RuntimePermissions::request (RuntimePermissions::readExternalStorage,
  443. [safeThis] (bool granted) mutable
  444. {
  445. if (safeThis != nullptr && granted)
  446. safeThis->openFile();
  447. });
  448. return;
  449. }
  450. fileChooser.reset (new FileChooser ("Select an audio file...", File(), "*.wav;*.mp3;*.aif"));
  451. fileChooser->launchAsync (FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles,
  452. [this] (const FileChooser& fc) mutable
  453. {
  454. if (fc.getURLResults().size() > 0)
  455. {
  456. const auto u = fc.getURLResult();
  457. if (! audioFileReader.loadURL (u))
  458. {
  459. auto options = MessageBoxOptions().withIconType (MessageBoxIconType::WarningIcon)
  460. .withTitle ("Error loading file")
  461. .withMessage ("Unable to load audio file")
  462. .withButton ("OK");
  463. messageBox = NativeMessageBox::showScopedAsync (options, nullptr);
  464. }
  465. else
  466. {
  467. thumbnailComp.setCurrentURL (u);
  468. }
  469. }
  470. fileChooser = nullptr;
  471. }, nullptr);
  472. }
  473. void changeListenerCallback (ChangeBroadcaster*) override
  474. {
  475. if (audioFileReader.playState.getValue())
  476. audioFileReader.stop();
  477. audioFileReader.loadURL (thumbnailComp.getCurrentURL());
  478. }
  479. void valueChanged (Value& v) override
  480. {
  481. playButton.setButtonText (v.getValue() ? "Stop" : "Play");
  482. playButton.setColour (TextButton::buttonColourId, v.getValue() ? Colour (0xffed797f) : Colour (0xff79ed7f));
  483. }
  484. //==============================================================================
  485. TextButton loadButton { "Load File..." }, playButton { "Play" };
  486. ToggleButton loopButton { "Loop File" };
  487. AudioFileReaderComponent& audioFileReader;
  488. std::unique_ptr<FileChooser> fileChooser;
  489. ScopedMessageBox messageBox;
  490. };
  491. //==============================================================================
  492. void valueChanged (Value& v) override
  493. {
  494. if (readerSource != nullptr)
  495. readerSource->setLooping (v.getValue());
  496. }
  497. void changeListenerCallback (ChangeBroadcaster*) override
  498. {
  499. if (playState.getValue() && ! transportSource->isPlaying())
  500. stop();
  501. }
  502. //==============================================================================
  503. // if this PIP is running inside the demo runner, we'll use the shared device manager instead
  504. #ifndef JUCE_DEMO_RUNNER
  505. AudioDeviceManager audioDeviceManager;
  506. #else
  507. AudioDeviceManager& audioDeviceManager { getSharedAudioDeviceManager (0, 2) };
  508. #endif
  509. AudioFormatManager formatManager;
  510. Value playState { var (false) };
  511. Value loopState { var (false) };
  512. double currentSampleRate = 44100.0;
  513. uint32 currentBlockSize = 512;
  514. uint32 currentNumChannels = 2;
  515. std::unique_ptr<AudioFormatReader> reader;
  516. std::unique_ptr<AudioFormatReaderSource> readerSource;
  517. std::unique_ptr<AudioTransportSource> transportSource;
  518. std::unique_ptr<DSPDemo<DemoType>> currentDemo;
  519. AudioSourcePlayer audioSourcePlayer;
  520. AudioPlayerHeader header;
  521. AudioBuffer<float> fileReadBuffer;
  522. std::unique_ptr<DemoParametersComponent> parametersComponent;
  523. };