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.

424 lines
14KB

  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 Slider::Listener,
  193. private ChangeListener
  194. {
  195. public:
  196. AudioPlaybackDemo()
  197. : deviceManager (MainAppWindow::getSharedAudioDeviceManager()),
  198. thread ("audio file preview"),
  199. directoryList (nullptr, thread),
  200. fileTreeComp (directoryList)
  201. {
  202. addAndMakeVisible (zoomLabel);
  203. zoomLabel.setText ("zoom:", dontSendNotification);
  204. zoomLabel.setFont (Font (15.00f, Font::plain));
  205. zoomLabel.setJustificationType (Justification::centredRight);
  206. zoomLabel.setEditable (false, false, false);
  207. zoomLabel.setColour (TextEditor::textColourId, Colours::black);
  208. zoomLabel.setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  209. addAndMakeVisible (followTransportButton);
  210. followTransportButton.setButtonText ("Follow Transport");
  211. followTransportButton.onClick = [this] { updateFollowTransportState(); };
  212. addAndMakeVisible (explanation);
  213. explanation.setText ("Select an audio file in the treeview above, and this page will display its waveform, and let you play it..", dontSendNotification);
  214. explanation.setFont (Font (14.00f, Font::plain));
  215. explanation.setJustificationType (Justification::bottomRight);
  216. explanation.setEditable (false, false, false);
  217. explanation.setColour (TextEditor::textColourId, Colours::black);
  218. explanation.setColour (TextEditor::backgroundColourId, Colour (0x00000000));
  219. addAndMakeVisible (zoomSlider);
  220. zoomSlider.setRange (0, 1, 0);
  221. zoomSlider.setSliderStyle (Slider::LinearHorizontal);
  222. zoomSlider.setTextBoxStyle (Slider::NoTextBox, false, 80, 20);
  223. zoomSlider.addListener (this);
  224. zoomSlider.setSkewFactor (2);
  225. addAndMakeVisible (thumbnail = new DemoThumbnailComp (formatManager, transportSource, zoomSlider));
  226. thumbnail->addChangeListener (this);
  227. addAndMakeVisible (startStopButton);
  228. startStopButton.setButtonText ("Play/Stop");
  229. startStopButton.setColour (TextButton::buttonColourId, Colour (0xff79ed7f));
  230. startStopButton.setColour (TextButton::textColourOffId, Colours::black);
  231. startStopButton.onClick = [this] { startOrStop(); };
  232. addAndMakeVisible (fileTreeComp);
  233. // audio setup
  234. formatManager.registerBasicFormats();
  235. directoryList.setDirectory (File::getSpecialLocation (File::userHomeDirectory), true, true);
  236. thread.startThread (3);
  237. fileTreeComp.setColour (FileTreeComponent::backgroundColourId, Colours::lightgrey.withAlpha (0.6f));
  238. fileTreeComp.addListener (this);
  239. deviceManager.addAudioCallback (&audioSourcePlayer);
  240. audioSourcePlayer.setSource (&transportSource);
  241. setOpaque (true);
  242. }
  243. ~AudioPlaybackDemo()
  244. {
  245. transportSource.setSource (nullptr);
  246. audioSourcePlayer.setSource (nullptr);
  247. deviceManager.removeAudioCallback (&audioSourcePlayer);
  248. fileTreeComp.removeListener (this);
  249. thumbnail->removeChangeListener (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.reset();
  298. if (auto* reader = formatManager.createReaderFor (audioFile))
  299. {
  300. currentAudioFileSource = new AudioFormatReaderSource (reader, true);
  301. // ..and plug it into our transport source
  302. transportSource.setSource (currentAudioFileSource,
  303. 32768, // tells it to buffer this many samples ahead
  304. &thread, // this is the background thread to use for reading-ahead
  305. reader->sampleRate); // allows for sample rate correction
  306. }
  307. }
  308. void startOrStop()
  309. {
  310. if (transportSource.isPlaying())
  311. {
  312. transportSource.stop();
  313. }
  314. else
  315. {
  316. transportSource.setPosition (0);
  317. transportSource.start();
  318. }
  319. }
  320. void updateFollowTransportState()
  321. {
  322. thumbnail->setFollowsTransport (followTransportButton.getToggleState());
  323. }
  324. void selectionChanged() override
  325. {
  326. showFile (fileTreeComp.getSelectedFile());
  327. }
  328. void fileClicked (const File&, const MouseEvent&) override {}
  329. void fileDoubleClicked (const File&) override {}
  330. void browserRootChanged (const File&) override {}
  331. void sliderValueChanged (Slider* sliderThatWasMoved) override
  332. {
  333. if (sliderThatWasMoved == &zoomSlider)
  334. thumbnail->setZoomFactor (zoomSlider.getValue());
  335. }
  336. void changeListenerCallback (ChangeBroadcaster* source) override
  337. {
  338. if (source == thumbnail)
  339. showFile (thumbnail->getLastDroppedFile());
  340. }
  341. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPlaybackDemo)
  342. };
  343. // This static object will register this demo type in a global list of demos..
  344. static JuceDemoType<AudioPlaybackDemo> demo ("31 Audio: File Playback");