/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-12 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "../JuceDemoHeader.h" //============================================================================== class DemoThumbnailComp : public Component, public ChangeListener, public FileDragAndDropTarget, public ChangeBroadcaster, private ScrollBar::Listener, private Timer { public: DemoThumbnailComp (AudioFormatManager& formatManager, AudioTransportSource& transportSource_, Slider& slider) : transportSource (transportSource_), zoomSlider (slider), scrollbar (false), thumbnailCache (5), thumbnail (512, formatManager, thumbnailCache), isFollowingTransport (false) { thumbnail.addChangeListener (this); addAndMakeVisible (scrollbar); scrollbar.setRangeLimits (visibleRange); scrollbar.setAutoHide (false); scrollbar.addListener (this); currentPositionMarker.setFill (Colours::white.withAlpha (0.85f)); addAndMakeVisible (currentPositionMarker); } ~DemoThumbnailComp() { scrollbar.removeListener (this); thumbnail.removeChangeListener (this); } void setFile (const File& file) { if (! file.isDirectory()) { thumbnail.setSource (new FileInputSource (file)); const Range newRange (0.0, thumbnail.getTotalLength()); scrollbar.setRangeLimits (newRange); setRange (newRange); startTimerHz (40); } } File getLastDroppedFile() const noexcept { return lastFileDropped; } void setZoomFactor (double amount) { if (thumbnail.getTotalLength() > 0) { const double newScale = jmax (0.001, thumbnail.getTotalLength() * (1.0 - jlimit (0.0, 0.99, amount))); const double timeAtCentre = xToTime (getWidth() / 2.0f); setRange (Range (timeAtCentre - newScale * 0.5, timeAtCentre + newScale * 0.5)); } } void setRange (Range newRange) { visibleRange = newRange; scrollbar.setCurrentRange (visibleRange); updateCursorPosition(); repaint(); } void setFollowsTransport (bool shouldFollow) { isFollowingTransport = shouldFollow; } void paint (Graphics& g) override { g.fillAll (Colours::darkgrey); g.setColour (Colours::lightblue); if (thumbnail.getTotalLength() > 0.0) { Rectangle thumbArea (getLocalBounds()); thumbArea.removeFromBottom (scrollbar.getHeight() + 4); thumbnail.drawChannels (g, thumbArea.reduced (2), visibleRange.getStart(), visibleRange.getEnd(), 1.0f); } else { g.setFont (14.0f); g.drawFittedText ("(No audio file selected)", getLocalBounds(), Justification::centred, 2); } } void resized() override { scrollbar.setBounds (getLocalBounds().removeFromBottom (14).reduced (2)); } void changeListenerCallback (ChangeBroadcaster*) override { // this method is called by the thumbnail when it has changed, so we should repaint it.. repaint(); } bool isInterestedInFileDrag (const StringArray& /*files*/) override { return true; } void filesDropped (const StringArray& files, int /*x*/, int /*y*/) override { lastFileDropped = File (files[0]); sendChangeMessage(); } void mouseDown (const MouseEvent& e) override { mouseDrag (e); } void mouseDrag (const MouseEvent& e) override { if (canMoveTransport()) transportSource.setPosition (jmax (0.0, xToTime ((float) e.x))); } void mouseUp (const MouseEvent&) override { transportSource.start(); } void mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel) override { if (thumbnail.getTotalLength() > 0.0) { double newStart = visibleRange.getStart() - wheel.deltaX * (visibleRange.getLength()) / 10.0; newStart = jlimit (0.0, jmax (0.0, thumbnail.getTotalLength() - (visibleRange.getLength())), newStart); if (canMoveTransport()) setRange (Range (newStart, newStart + visibleRange.getLength())); if (wheel.deltaY != 0.0f) zoomSlider.setValue (zoomSlider.getValue() - wheel.deltaY); repaint(); } } private: AudioTransportSource& transportSource; Slider& zoomSlider; ScrollBar scrollbar; AudioThumbnailCache thumbnailCache; AudioThumbnail thumbnail; Range visibleRange; bool isFollowingTransport; File lastFileDropped; DrawableRectangle currentPositionMarker; float timeToX (const double time) const { return getWidth() * (float) ((time - visibleRange.getStart()) / (visibleRange.getLength())); } double xToTime (const float x) const { return (x / getWidth()) * (visibleRange.getLength()) + visibleRange.getStart(); } bool canMoveTransport() const noexcept { return ! (isFollowingTransport && transportSource.isPlaying()); } void scrollBarMoved (ScrollBar* scrollBarThatHasMoved, double newRangeStart) override { if (scrollBarThatHasMoved == &scrollbar) if (! (isFollowingTransport && transportSource.isPlaying())) setRange (visibleRange.movedToStartAt (newRangeStart)); } void timerCallback() override { if (canMoveTransport()) updateCursorPosition(); else setRange (visibleRange.movedToStartAt (transportSource.getCurrentPosition() - (visibleRange.getLength() / 2.0))); } void updateCursorPosition() { currentPositionMarker.setVisible (transportSource.isPlaying() || isMouseButtonDown()); currentPositionMarker.setRectangle (Rectangle (timeToX (transportSource.getCurrentPosition()) - 0.75f, 0, 1.5f, (float) (getHeight() - scrollbar.getHeight()))); } }; //============================================================================== class AudioPlaybackDemo : public Component, private FileBrowserListener, private Button::Listener, private Slider::Listener, private ChangeListener { public: AudioPlaybackDemo() : deviceManager (MainAppWindow::getSharedAudioDeviceManager()), thread ("audio file preview"), directoryList (nullptr, thread), fileTreeComp (directoryList) { addAndMakeVisible (zoomLabel); zoomLabel.setText ("zoom:", dontSendNotification); zoomLabel.setFont (Font (15.00f, Font::plain)); zoomLabel.setJustificationType (Justification::centredRight); zoomLabel.setEditable (false, false, false); zoomLabel.setColour (TextEditor::textColourId, Colours::black); zoomLabel.setColour (TextEditor::backgroundColourId, Colour (0x00000000)); addAndMakeVisible (followTransportButton); followTransportButton.setButtonText ("Follow Transport"); followTransportButton.addListener (this); addAndMakeVisible (explanation); explanation.setText ("Select an audio file in the treeview above, and this page will display its waveform, and let you play it..", dontSendNotification); explanation.setFont (Font (14.00f, Font::plain)); explanation.setJustificationType (Justification::bottomRight); explanation.setEditable (false, false, false); explanation.setColour (TextEditor::textColourId, Colours::black); explanation.setColour (TextEditor::backgroundColourId, Colour (0x00000000)); addAndMakeVisible (zoomSlider); zoomSlider.setRange (0, 1, 0); zoomSlider.setSliderStyle (Slider::LinearHorizontal); zoomSlider.setTextBoxStyle (Slider::NoTextBox, false, 80, 20); zoomSlider.addListener (this); zoomSlider.setSkewFactor (2); addAndMakeVisible (thumbnail = new DemoThumbnailComp (formatManager, transportSource, zoomSlider)); thumbnail->addChangeListener (this); addAndMakeVisible (startStopButton); startStopButton.setButtonText ("Play/Stop"); startStopButton.addListener (this); startStopButton.setColour (TextButton::buttonColourId, Colour (0xff79ed7f)); addAndMakeVisible (fileTreeComp); // audio setup formatManager.registerBasicFormats(); directoryList.setDirectory (File::getSpecialLocation (File::userHomeDirectory), true, true); thread.startThread (3); fileTreeComp.setColour (FileTreeComponent::backgroundColourId, Colours::lightgrey.withAlpha (0.6f)); fileTreeComp.addListener (this); deviceManager.addAudioCallback (&audioSourcePlayer); audioSourcePlayer.setSource (&transportSource); setOpaque (true); } ~AudioPlaybackDemo() { transportSource.setSource (nullptr); audioSourcePlayer.setSource (nullptr); deviceManager.removeAudioCallback (&audioSourcePlayer); fileTreeComp.removeListener (this); thumbnail->removeChangeListener (this); followTransportButton.removeListener (this); zoomSlider.removeListener (this); } void paint (Graphics& g) override { fillTiledBackground (g); } void resized() override { Rectangle r (getLocalBounds().reduced (4)); Rectangle controls (r.removeFromBottom (90)); explanation.setBounds (controls.removeFromRight (controls.getWidth() / 3)); Rectangle zoom (controls.removeFromTop (25)); zoomLabel.setBounds (zoom.removeFromLeft (50)); zoomSlider.setBounds (zoom); followTransportButton.setBounds (controls.removeFromTop (25)); startStopButton.setBounds (controls); r.removeFromBottom (6); thumbnail->setBounds (r.removeFromBottom (140)); r.removeFromBottom (6); fileTreeComp.setBounds (r); } private: AudioDeviceManager& deviceManager; AudioFormatManager formatManager; TimeSliceThread thread; DirectoryContentsList directoryList; AudioSourcePlayer audioSourcePlayer; AudioTransportSource transportSource; ScopedPointer currentAudioFileSource; ScopedPointer thumbnail; Label zoomLabel, explanation; Slider zoomSlider; ToggleButton followTransportButton; TextButton startStopButton; FileTreeComponent fileTreeComp; //============================================================================== void showFile (const File& file) { loadFileIntoTransport (file); zoomSlider.setValue (0, dontSendNotification); thumbnail->setFile (file); } void loadFileIntoTransport (const File& audioFile) { // unload the previous file source and delete it.. transportSource.stop(); transportSource.setSource (nullptr); currentAudioFileSource = nullptr; AudioFormatReader* reader = formatManager.createReaderFor (audioFile); if (reader != nullptr) { currentAudioFileSource = new AudioFormatReaderSource (reader, true); // ..and plug it into our transport source transportSource.setSource (currentAudioFileSource, 32768, // tells it to buffer this many samples ahead &thread, // this is the background thread to use for reading-ahead reader->sampleRate); // allows for sample rate correction } } void selectionChanged() override { showFile (fileTreeComp.getSelectedFile()); } void fileClicked (const File&, const MouseEvent&) override {} void fileDoubleClicked (const File&) override {} void browserRootChanged (const File&) override {} void sliderValueChanged (Slider* sliderThatWasMoved) override { if (sliderThatWasMoved == &zoomSlider) thumbnail->setZoomFactor (zoomSlider.getValue()); } void buttonClicked (Button* buttonThatWasClicked) override { if (buttonThatWasClicked == &startStopButton) { if (transportSource.isPlaying()) { transportSource.stop(); } else { transportSource.setPosition (0); transportSource.start(); } } else if (buttonThatWasClicked == &followTransportButton) { thumbnail->setFollowsTransport (followTransportButton.getToggleState()); } } void changeListenerCallback (ChangeBroadcaster* source) override { if (source == thumbnail) showFile (thumbnail->getLastDroppedFile()); } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPlaybackDemo); }; // This static object will register this demo type in a global list of demos.. static JuceDemoType demo ("31 Audio: File Playback");