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.

427 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. return getWidth() * (float) ((time - visibleRange.getStart()) / (visibleRange.getLength()));
  158. }
  159. double xToTime (const float x) const
  160. {
  161. return (x / getWidth()) * (visibleRange.getLength()) + visibleRange.getStart();
  162. }
  163. bool canMoveTransport() const noexcept
  164. {
  165. return ! (isFollowingTransport && transportSource.isPlaying());
  166. }
  167. void scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRangeStart) override
  168. {
  169. if (scrollBarThatHasMoved == &scrollbar)
  170. if (! (isFollowingTransport && transportSource.isPlaying()))
  171. setRange (visibleRange.movedToStartAt (newRangeStart));
  172. }
  173. void timerCallback() override
  174. {
  175. if (canMoveTransport())
  176. updateCursorPosition();
  177. else
  178. setRange (visibleRange.movedToStartAt (transportSource.getCurrentPosition() - (visibleRange.getLength() / 2.0)));
  179. }
  180. void updateCursorPosition()
  181. {
  182. currentPositionMarker.setVisible (transportSource.isPlaying() || isMouseButtonDown());
  183. currentPositionMarker.setRectangle (Rectangle<float> (timeToX (transportSource.getCurrentPosition()) - 0.75f, 0,
  184. 1.5f, (float) (getHeight() - scrollbar.getHeight())));
  185. }
  186. };
  187. //==============================================================================
  188. class AudioPlaybackDemo : public Component,
  189. private FileBrowserListener,
  190. private Button::Listener,
  191. private Slider::Listener,
  192. private ChangeListener
  193. {
  194. public:
  195. AudioPlaybackDemo()
  196. : deviceManager (MainAppWindow::getSharedAudioDeviceManager()),
  197. thread ("audio file preview"),
  198. directoryList (nullptr, thread),
  199. fileTreeComp (directoryList)
  200. {
  201. addAndMakeVisible (zoomLabel);
  202. zoomLabel.setText ("zoom:", dontSendNotification);
  203. zoomLabel.setFont (Font (15.00f, Font::plain));
  204. zoomLabel.setJustificationType (Justification::centredRight);
  205. zoomLabel.setEditable (false, false, false);
  206. zoomLabel.setColour (TextEditor::textColourId, Colours::black);
  207. zoomLabel.setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  208. addAndMakeVisible (followTransportButton);
  209. followTransportButton.setButtonText ("Follow Transport");
  210. followTransportButton.addListener (this);
  211. addAndMakeVisible (explanation);
  212. explanation.setText ("Select an audio file in the treeview above, and this page will display its waveform, and let you play it..", dontSendNotification);
  213. explanation.setFont (Font (14.00f, Font::plain));
  214. explanation.setJustificationType (Justification::bottomRight);
  215. explanation.setEditable (false, false, false);
  216. explanation.setColour (TextEditor::textColourId, Colours::black);
  217. explanation.setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  218. addAndMakeVisible (zoomSlider);
  219. zoomSlider.setRange (0, 1, 0);
  220. zoomSlider.setSliderStyle (Slider::LinearHorizontal);
  221. zoomSlider.setTextBoxStyle (Slider::NoTextBox, false, 80, 20);
  222. zoomSlider.addListener (this);
  223. zoomSlider.setSkewFactor (2);
  224. addAndMakeVisible (thumbnail = new DemoThumbnailComp (formatManager, transportSource, zoomSlider));
  225. thumbnail->addChangeListener (this);
  226. addAndMakeVisible (startStopButton);
  227. startStopButton.setButtonText ("Play/Stop");
  228. startStopButton.addListener (this);
  229. startStopButton.setColour (TextButton::buttonColourId, Colour (0xff79ed7f));
  230. startStopButton.setColour (TextButton::textColourOffId, Colours::black);
  231. addAndMakeVisible (fileTreeComp);
  232. // audio setup
  233. formatManager.registerBasicFormats();
  234. directoryList.setDirectory (File::getSpecialLocation (File::userHomeDirectory), true, true);
  235. thread.startThread (3);
  236. fileTreeComp.setColour (FileTreeComponent::backgroundColourId, Colours::lightgrey.withAlpha (0.6f));
  237. fileTreeComp.addListener (this);
  238. deviceManager.addAudioCallback (&audioSourcePlayer);
  239. audioSourcePlayer.setSource (&transportSource);
  240. setOpaque (true);
  241. }
  242. ~AudioPlaybackDemo()
  243. {
  244. transportSource.setSource (nullptr);
  245. audioSourcePlayer.setSource (nullptr);
  246. deviceManager.removeAudioCallback (&audioSourcePlayer);
  247. fileTreeComp.removeListener (this);
  248. thumbnail->removeChangeListener (this);
  249. followTransportButton.removeListener (this);
  250. zoomSlider.removeListener (this);
  251. }
  252. void paint (Graphics& g) override
  253. {
  254. g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
  255. }
  256. void resized() override
  257. {
  258. Rectangle<int> r (getLocalBounds().reduced (4));
  259. Rectangle<int> controls (r.removeFromBottom (90));
  260. explanation.setBounds (controls.removeFromRight (controls.getWidth() / 3));
  261. Rectangle<int> zoom (controls.removeFromTop (25));
  262. zoomLabel.setBounds (zoom.removeFromLeft (50));
  263. zoomSlider.setBounds (zoom);
  264. followTransportButton.setBounds (controls.removeFromTop (25));
  265. startStopButton.setBounds (controls);
  266. r.removeFromBottom (6);
  267. thumbnail->setBounds (r.removeFromBottom (140));
  268. r.removeFromBottom (6);
  269. fileTreeComp.setBounds (r);
  270. }
  271. private:
  272. AudioDeviceManager& deviceManager;
  273. AudioFormatManager formatManager;
  274. TimeSliceThread thread;
  275. DirectoryContentsList directoryList;
  276. AudioSourcePlayer audioSourcePlayer;
  277. AudioTransportSource transportSource;
  278. ScopedPointer<AudioFormatReaderSource> currentAudioFileSource;
  279. ScopedPointer<DemoThumbnailComp> thumbnail;
  280. Label zoomLabel, explanation;
  281. Slider zoomSlider;
  282. ToggleButton followTransportButton;
  283. TextButton startStopButton;
  284. FileTreeComponent fileTreeComp;
  285. //==============================================================================
  286. void showFile (const File& file)
  287. {
  288. loadFileIntoTransport (file);
  289. zoomSlider.setValue (0, dontSendNotification);
  290. thumbnail->setFile (file);
  291. }
  292. void loadFileIntoTransport (const File& audioFile)
  293. {
  294. // unload the previous file source and delete it..
  295. transportSource.stop();
  296. transportSource.setSource (nullptr);
  297. currentAudioFileSource = nullptr;
  298. AudioFormatReader* reader = formatManager.createReaderFor (audioFile);
  299. if (reader != nullptr)
  300. {
  301. currentAudioFileSource = new AudioFormatReaderSource (reader, true);
  302. // ..and plug it into our transport source
  303. transportSource.setSource (currentAudioFileSource,
  304. 32768, // tells it to buffer this many samples ahead
  305. &thread, // this is the background thread to use for reading-ahead
  306. reader->sampleRate); // allows for sample rate correction
  307. }
  308. }
  309. void selectionChanged() override
  310. {
  311. showFile (fileTreeComp.getSelectedFile());
  312. }
  313. void fileClicked (const File&, const MouseEvent&) override {}
  314. void fileDoubleClicked (const File&) override {}
  315. void browserRootChanged (const File&) override {}
  316. void sliderValueChanged (Slider* sliderThatWasMoved) override
  317. {
  318. if (sliderThatWasMoved == &zoomSlider)
  319. thumbnail->setZoomFactor (zoomSlider.getValue());
  320. }
  321. void buttonClicked (Button* buttonThatWasClicked) override
  322. {
  323. if (buttonThatWasClicked == &startStopButton)
  324. {
  325. if (transportSource.isPlaying())
  326. {
  327. transportSource.stop();
  328. }
  329. else
  330. {
  331. transportSource.setPosition (0);
  332. transportSource.start();
  333. }
  334. }
  335. else if (buttonThatWasClicked == &followTransportButton)
  336. {
  337. thumbnail->setFollowsTransport (followTransportButton.getToggleState());
  338. }
  339. }
  340. void changeListenerCallback (ChangeBroadcaster* source) override
  341. {
  342. if (source == thumbnail)
  343. showFile (thumbnail->getLastDroppedFile());
  344. }
  345. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPlaybackDemo)
  346. };
  347. // This static object will register this demo type in a global list of demos..
  348. static JuceDemoType<AudioPlaybackDemo> demo ("31 Audio: File Playback");