|  | /*
  ==============================================================================
   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<double> newRange (0.0, thumbnail.getTotalLength());
            scrollbar.setRangeLimits (newRange);
            setRange (newRange);
            startTimer (1000 / 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<double> (timeAtCentre - newScale * 0.5, timeAtCentre + newScale * 0.5));
        }
    }
    void setRange (Range<double> 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<int> 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<double> (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<double> 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<float> (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
    {
        fillBrushedAluminiumBackground (g);
    }
    void resized() override
    {
        Rectangle<int> r (getLocalBounds().reduced (4));
        Rectangle<int> controls (r.removeFromBottom (90));
        explanation.setBounds (controls.removeFromRight (controls.getWidth() / 3));
        Rectangle<int> 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<AudioFormatReaderSource> currentAudioFileSource;
    ScopedPointer<DemoThumbnailComp> 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<AudioPlaybackDemo> demo ("31 Audio: File Playback");
 |