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.

428 lines
15KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../JuceDemoHeader.h"
  20. //==============================================================================
  21. class DemoThumbnailComp : public Component,
  22. public ChangeListener,
  23. public FileDragAndDropTarget,
  24. public ChangeBroadcaster,
  25. private ScrollBar::Listener,
  26. private Timer
  27. {
  28. public:
  29. DemoThumbnailComp (AudioFormatManager& formatManager,
  30. AudioTransportSource& transportSource_,
  31. Slider& slider)
  32. : transportSource (transportSource_),
  33. zoomSlider (slider),
  34. scrollbar (false),
  35. thumbnailCache (5),
  36. thumbnail (512, formatManager, thumbnailCache),
  37. isFollowingTransport (false)
  38. {
  39. thumbnail.addChangeListener (this);
  40. addAndMakeVisible (scrollbar);
  41. scrollbar.setRangeLimits (visibleRange);
  42. scrollbar.setAutoHide (false);
  43. scrollbar.addListener (this);
  44. currentPositionMarker.setFill (Colours::white.withAlpha (0.85f));
  45. addAndMakeVisible (currentPositionMarker);
  46. }
  47. ~DemoThumbnailComp()
  48. {
  49. scrollbar.removeListener (this);
  50. thumbnail.removeChangeListener (this);
  51. }
  52. void setFile (const File& file)
  53. {
  54. if (! file.isDirectory())
  55. {
  56. thumbnail.setSource (new FileInputSource (file));
  57. const Range<double> newRange (0.0, thumbnail.getTotalLength());
  58. scrollbar.setRangeLimits (newRange);
  59. setRange (newRange);
  60. startTimerHz (40);
  61. }
  62. }
  63. File getLastDroppedFile() const noexcept { return lastFileDropped; }
  64. void setZoomFactor (double amount)
  65. {
  66. if (thumbnail.getTotalLength() > 0)
  67. {
  68. const double newScale = jmax (0.001, thumbnail.getTotalLength() * (1.0 - jlimit (0.0, 0.99, amount)));
  69. const double timeAtCentre = xToTime (getWidth() / 2.0f);
  70. setRange (Range<double> (timeAtCentre - newScale * 0.5, timeAtCentre + newScale * 0.5));
  71. }
  72. }
  73. void setRange (Range<double> newRange)
  74. {
  75. visibleRange = newRange;
  76. scrollbar.setCurrentRange (visibleRange);
  77. updateCursorPosition();
  78. repaint();
  79. }
  80. void setFollowsTransport (bool shouldFollow)
  81. {
  82. isFollowingTransport = shouldFollow;
  83. }
  84. void paint (Graphics& g) override
  85. {
  86. g.fillAll (Colours::darkgrey);
  87. g.setColour (Colours::lightblue);
  88. if (thumbnail.getTotalLength() > 0.0)
  89. {
  90. Rectangle<int> thumbArea (getLocalBounds());
  91. thumbArea.removeFromBottom (scrollbar.getHeight() + 4);
  92. thumbnail.drawChannels (g, thumbArea.reduced (2),
  93. visibleRange.getStart(), visibleRange.getEnd(), 1.0f);
  94. }
  95. else
  96. {
  97. g.setFont (14.0f);
  98. g.drawFittedText ("(No audio file selected)", getLocalBounds(), Justification::centred, 2);
  99. }
  100. }
  101. void resized() override
  102. {
  103. scrollbar.setBounds (getLocalBounds().removeFromBottom (14).reduced (2));
  104. }
  105. void changeListenerCallback (ChangeBroadcaster*) override
  106. {
  107. // this method is called by the thumbnail when it has changed, so we should repaint it..
  108. repaint();
  109. }
  110. bool isInterestedInFileDrag (const StringArray& /*files*/) override
  111. {
  112. return true;
  113. }
  114. void filesDropped (const StringArray& files, int /*x*/, int /*y*/) override
  115. {
  116. lastFileDropped = File (files[0]);
  117. sendChangeMessage();
  118. }
  119. void mouseDown (const MouseEvent& e) override
  120. {
  121. mouseDrag (e);
  122. }
  123. void mouseDrag (const MouseEvent& e) override
  124. {
  125. if (canMoveTransport())
  126. transportSource.setPosition (jmax (0.0, xToTime ((float) e.x)));
  127. }
  128. void mouseUp (const MouseEvent&) override
  129. {
  130. transportSource.start();
  131. }
  132. void mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel) override
  133. {
  134. if (thumbnail.getTotalLength() > 0.0)
  135. {
  136. double newStart = visibleRange.getStart() - wheel.deltaX * (visibleRange.getLength()) / 10.0;
  137. newStart = jlimit (0.0, jmax (0.0, thumbnail.getTotalLength() - (visibleRange.getLength())), newStart);
  138. if (canMoveTransport())
  139. setRange (Range<double> (newStart, newStart + visibleRange.getLength()));
  140. if (wheel.deltaY != 0.0f)
  141. zoomSlider.setValue (zoomSlider.getValue() - wheel.deltaY);
  142. repaint();
  143. }
  144. }
  145. private:
  146. AudioTransportSource& transportSource;
  147. Slider& zoomSlider;
  148. ScrollBar scrollbar;
  149. AudioThumbnailCache thumbnailCache;
  150. AudioThumbnail thumbnail;
  151. Range<double> visibleRange;
  152. bool isFollowingTransport;
  153. File lastFileDropped;
  154. DrawableRectangle currentPositionMarker;
  155. float timeToX (const double time) const
  156. {
  157. if (visibleRange.getLength() <= 0)
  158. return 0;
  159. return getWidth() * (float) ((time - visibleRange.getStart()) / visibleRange.getLength());
  160. }
  161. double xToTime (const float x) const
  162. {
  163. return (x / getWidth()) * (visibleRange.getLength()) + visibleRange.getStart();
  164. }
  165. bool canMoveTransport() const noexcept
  166. {
  167. return ! (isFollowingTransport && transportSource.isPlaying());
  168. }
  169. void scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRangeStart) override
  170. {
  171. if (scrollBarThatHasMoved == &scrollbar)
  172. if (! (isFollowingTransport && transportSource.isPlaying()))
  173. setRange (visibleRange.movedToStartAt (newRangeStart));
  174. }
  175. void timerCallback() override
  176. {
  177. if (canMoveTransport())
  178. updateCursorPosition();
  179. else
  180. setRange (visibleRange.movedToStartAt (transportSource.getCurrentPosition() - (visibleRange.getLength() / 2.0)));
  181. }
  182. void updateCursorPosition()
  183. {
  184. currentPositionMarker.setVisible (transportSource.isPlaying() || isMouseButtonDown());
  185. currentPositionMarker.setRectangle (Rectangle<float> (timeToX (transportSource.getCurrentPosition()) - 0.75f, 0,
  186. 1.5f, (float) (getHeight() - scrollbar.getHeight())));
  187. }
  188. };
  189. //==============================================================================
  190. class AudioPlaybackDemo : public Component,
  191. private FileBrowserListener,
  192. private Button::Listener,
  193. private Slider::Listener,
  194. private ChangeListener
  195. {
  196. public:
  197. AudioPlaybackDemo()
  198. : deviceManager (MainAppWindow::getSharedAudioDeviceManager()),
  199. thread ("audio file preview"),
  200. directoryList (nullptr, thread),
  201. fileTreeComp (directoryList)
  202. {
  203. addAndMakeVisible (zoomLabel);
  204. zoomLabel.setText ("zoom:", dontSendNotification);
  205. zoomLabel.setFont (Font (15.00f, Font::plain));
  206. zoomLabel.setJustificationType (Justification::centredRight);
  207. zoomLabel.setEditable (false, false, false);
  208. zoomLabel.setColour (TextEditor::textColourId, Colours::black);
  209. zoomLabel.setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  210. addAndMakeVisible (followTransportButton);
  211. followTransportButton.setButtonText ("Follow Transport");
  212. followTransportButton.addListener (this);
  213. addAndMakeVisible (explanation);
  214. explanation.setText ("Select an audio file in the treeview above, and this page will display its waveform, and let you play it..", dontSendNotification);
  215. explanation.setFont (Font (14.00f, Font::plain));
  216. explanation.setJustificationType (Justification::bottomRight);
  217. explanation.setEditable (false, false, false);
  218. explanation.setColour (TextEditor::textColourId, Colours::black);
  219. explanation.setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  220. addAndMakeVisible (zoomSlider);
  221. zoomSlider.setRange (0, 1, 0);
  222. zoomSlider.setSliderStyle (Slider::LinearHorizontal);
  223. zoomSlider.setTextBoxStyle (Slider::NoTextBox, false, 80, 20);
  224. zoomSlider.addListener (this);
  225. zoomSlider.setSkewFactor (2);
  226. addAndMakeVisible (thumbnail = new DemoThumbnailComp (formatManager, transportSource, zoomSlider));
  227. thumbnail->addChangeListener (this);
  228. addAndMakeVisible (startStopButton);
  229. startStopButton.setButtonText ("Play/Stop");
  230. startStopButton.addListener (this);
  231. startStopButton.setColour (TextButton::buttonColourId, Colour (0xff79ed7f));
  232. startStopButton.setColour (TextButton::textColourOffId, Colours::black);
  233. addAndMakeVisible (fileTreeComp);
  234. // audio setup
  235. formatManager.registerBasicFormats();
  236. directoryList.setDirectory (File::getSpecialLocation (File::userHomeDirectory), true, true);
  237. thread.startThread (3);
  238. fileTreeComp.setColour (FileTreeComponent::backgroundColourId, Colours::lightgrey.withAlpha (0.6f));
  239. fileTreeComp.addListener (this);
  240. deviceManager.addAudioCallback (&audioSourcePlayer);
  241. audioSourcePlayer.setSource (&transportSource);
  242. setOpaque (true);
  243. }
  244. ~AudioPlaybackDemo()
  245. {
  246. transportSource.setSource (nullptr);
  247. audioSourcePlayer.setSource (nullptr);
  248. deviceManager.removeAudioCallback (&audioSourcePlayer);
  249. fileTreeComp.removeListener (this);
  250. thumbnail->removeChangeListener (this);
  251. followTransportButton.removeListener (this);
  252. zoomSlider.removeListener (this);
  253. }
  254. void paint (Graphics& g) override
  255. {
  256. g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
  257. }
  258. void resized() override
  259. {
  260. Rectangle<int> r (getLocalBounds().reduced (4));
  261. Rectangle<int> controls (r.removeFromBottom (90));
  262. explanation.setBounds (controls.removeFromRight (controls.getWidth() / 3));
  263. Rectangle<int> zoom (controls.removeFromTop (25));
  264. zoomLabel.setBounds (zoom.removeFromLeft (50));
  265. zoomSlider.setBounds (zoom);
  266. followTransportButton.setBounds (controls.removeFromTop (25));
  267. startStopButton.setBounds (controls);
  268. r.removeFromBottom (6);
  269. thumbnail->setBounds (r.removeFromBottom (140));
  270. r.removeFromBottom (6);
  271. fileTreeComp.setBounds (r);
  272. }
  273. private:
  274. AudioDeviceManager& deviceManager;
  275. AudioFormatManager formatManager;
  276. TimeSliceThread thread;
  277. DirectoryContentsList directoryList;
  278. AudioSourcePlayer audioSourcePlayer;
  279. AudioTransportSource transportSource;
  280. ScopedPointer<AudioFormatReaderSource> currentAudioFileSource;
  281. ScopedPointer<DemoThumbnailComp> thumbnail;
  282. Label zoomLabel, explanation;
  283. Slider zoomSlider;
  284. ToggleButton followTransportButton;
  285. TextButton startStopButton;
  286. FileTreeComponent fileTreeComp;
  287. //==============================================================================
  288. void showFile (const File& file)
  289. {
  290. loadFileIntoTransport (file);
  291. zoomSlider.setValue (0, dontSendNotification);
  292. thumbnail->setFile (file);
  293. }
  294. void loadFileIntoTransport (const File& audioFile)
  295. {
  296. // unload the previous file source and delete it..
  297. transportSource.stop();
  298. transportSource.setSource (nullptr);
  299. currentAudioFileSource.reset();
  300. if (auto* reader = formatManager.createReaderFor (audioFile))
  301. {
  302. currentAudioFileSource = new AudioFormatReaderSource (reader, true);
  303. // ..and plug it into our transport source
  304. transportSource.setSource (currentAudioFileSource,
  305. 32768, // tells it to buffer this many samples ahead
  306. &thread, // this is the background thread to use for reading-ahead
  307. reader->sampleRate); // allows for sample rate correction
  308. }
  309. }
  310. void selectionChanged() override
  311. {
  312. showFile (fileTreeComp.getSelectedFile());
  313. }
  314. void fileClicked (const File&, const MouseEvent&) override {}
  315. void fileDoubleClicked (const File&) override {}
  316. void browserRootChanged (const File&) override {}
  317. void sliderValueChanged (Slider* sliderThatWasMoved) override
  318. {
  319. if (sliderThatWasMoved == &zoomSlider)
  320. thumbnail->setZoomFactor (zoomSlider.getValue());
  321. }
  322. void buttonClicked (Button* buttonThatWasClicked) override
  323. {
  324. if (buttonThatWasClicked == &startStopButton)
  325. {
  326. if (transportSource.isPlaying())
  327. {
  328. transportSource.stop();
  329. }
  330. else
  331. {
  332. transportSource.setPosition (0);
  333. transportSource.start();
  334. }
  335. }
  336. else if (buttonThatWasClicked == &followTransportButton)
  337. {
  338. thumbnail->setFollowsTransport (followTransportButton.getToggleState());
  339. }
  340. }
  341. void changeListenerCallback (ChangeBroadcaster* source) override
  342. {
  343. if (source == thumbnail)
  344. showFile (thumbnail->getLastDroppedFile());
  345. }
  346. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPlaybackDemo)
  347. };
  348. // This static object will register this demo type in a global list of demos..
  349. static JuceDemoType<AudioPlaybackDemo> demo ("31 Audio: File Playback");