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
15KB

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