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.

425 lines
15KB

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