| @@ -32,18 +32,14 @@ | |||||
| /* This component scrolls a continuous waveform showing the audio that's | /* This component scrolls a continuous waveform showing the audio that's | ||||
| coming into whatever audio inputs this object is connected to. | coming into whatever audio inputs this object is connected to. | ||||
| */ | */ | ||||
| class LiveScrollingAudioDisplay : public Component, | |||||
| public AudioIODeviceCallback, | |||||
| private Timer | |||||
| class LiveScrollingAudioDisplay : public AudioVisualiserComponent, | |||||
| public AudioIODeviceCallback | |||||
| { | { | ||||
| public: | public: | ||||
| LiveScrollingAudioDisplay() | |||||
| : nextSample (0), subSample (0), accumulator (0) | |||||
| LiveScrollingAudioDisplay() : AudioVisualiserComponent (1) | |||||
| { | { | ||||
| setOpaque (true); | |||||
| clear(); | |||||
| startTimerHz (75); // use a timer to keep repainting this component | |||||
| setSamplesPerBlock (256); | |||||
| setBufferSize (1024); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -66,72 +62,18 @@ public: | |||||
| float inputSample = 0; | float inputSample = 0; | ||||
| for (int chan = 0; chan < numInputChannels; ++chan) | for (int chan = 0; chan < numInputChannels; ++chan) | ||||
| if (inputChannelData[chan] != nullptr) | |||||
| inputSample += std::abs (inputChannelData[chan][i]); // find the sum of all the channels | |||||
| if (const float* inputChannel = inputChannelData[chan]) | |||||
| inputSample += inputChannel[i]; // find the sum of all the channels | |||||
| pushSample (10.0f * inputSample); // boost the level to make it more easily visible. | |||||
| inputSample *= 10.0f; // boost the level to make it more easily visible. | |||||
| pushSample (&inputSample, 1); | |||||
| } | } | ||||
| // We need to clear the output buffers before returning, in case they're full of junk.. | // We need to clear the output buffers before returning, in case they're full of junk.. | ||||
| for (int j = 0; j < numOutputChannels; ++j) | for (int j = 0; j < numOutputChannels; ++j) | ||||
| if (outputChannelData[j] != nullptr) | |||||
| zeromem (outputChannelData[j], sizeof (float) * (size_t) numSamples); | |||||
| } | |||||
| private: | |||||
| float samples[1024]; | |||||
| int nextSample, subSample; | |||||
| float accumulator; | |||||
| void clear() | |||||
| { | |||||
| zeromem (samples, sizeof (samples)); | |||||
| accumulator = 0; | |||||
| subSample = 0; | |||||
| } | |||||
| void paint (Graphics& g) override | |||||
| { | |||||
| g.fillAll (Colours::black); | |||||
| const float midY = getHeight() * 0.5f; | |||||
| int samplesAgo = (nextSample + numElementsInArray (samples) - 1); | |||||
| RectangleList<float> waveform; | |||||
| waveform.ensureStorageAllocated ((int) numElementsInArray (samples)); | |||||
| for (int x = jmin (getWidth(), (int) numElementsInArray (samples)); --x >= 0;) | |||||
| { | |||||
| const float sampleSize = midY * samples [samplesAgo-- % numElementsInArray (samples)]; | |||||
| waveform.addWithoutMerging (Rectangle<float> ((float) x, midY - sampleSize, 1.0f, sampleSize * 2.0f)); | |||||
| } | |||||
| g.setColour (Colours::lightgreen); | |||||
| g.fillRectList (waveform); | |||||
| } | |||||
| void timerCallback() override | |||||
| { | |||||
| repaint(); | |||||
| } | |||||
| void pushSample (const float newSample) | |||||
| { | |||||
| accumulator += newSample; | |||||
| if (subSample == 0) | |||||
| { | |||||
| const int inputSamplesPerPixel = 200; | |||||
| samples[nextSample] = accumulator / inputSamplesPerPixel; | |||||
| nextSample = (nextSample + 1) % numElementsInArray (samples); | |||||
| subSample = inputSamplesPerPixel; | |||||
| accumulator = 0; | |||||
| } | |||||
| else | |||||
| { | |||||
| --subSample; | |||||
| } | |||||
| if (float* outputChannel = outputChannelData[j]) | |||||
| zeromem (outputChannel, sizeof (float) * (size_t) numSamples); | |||||
| } | } | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LiveScrollingAudioDisplay); | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LiveScrollingAudioDisplay); | ||||
| @@ -0,0 +1,220 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2015 - ROLI Ltd. | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found 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.juce.com for more information. | |||||
| ============================================================================== | |||||
| */ | |||||
| struct AudioVisualiserComponent::ChannelInfo | |||||
| { | |||||
| ChannelInfo (AudioVisualiserComponent& o, int bufferSize) | |||||
| : owner (o), nextSample (0), subSample (0) | |||||
| { | |||||
| setBufferSize (bufferSize); | |||||
| clear(); | |||||
| } | |||||
| void clear() noexcept | |||||
| { | |||||
| for (int i = 0; i < levels.size(); ++i) | |||||
| levels.getReference(i) = Range<float>(); | |||||
| value = Range<float>(); | |||||
| subSample = 0; | |||||
| } | |||||
| void pushSamples (const float* inputSamples, const int num) noexcept | |||||
| { | |||||
| for (int i = 0; i < num; ++i) | |||||
| pushSample (inputSamples[i]); | |||||
| } | |||||
| void pushSample (const float newSample) noexcept | |||||
| { | |||||
| if (--subSample <= 0) | |||||
| { | |||||
| nextSample %= levels.size(); | |||||
| levels.getReference (nextSample++) = value; | |||||
| subSample = owner.getSamplesPerBlock(); | |||||
| value = Range<float> (newSample, newSample); | |||||
| } | |||||
| else | |||||
| { | |||||
| value = value.getUnionWith (newSample); | |||||
| } | |||||
| } | |||||
| void setBufferSize (int newSize) | |||||
| { | |||||
| levels.removeRange (newSize, levels.size()); | |||||
| levels.insertMultiple (-1, Range<float>(), newSize - levels.size()); | |||||
| if (nextSample >= newSize) | |||||
| nextSample = 0; | |||||
| } | |||||
| AudioVisualiserComponent& owner; | |||||
| Array<Range<float> > levels; | |||||
| Range<float> value; | |||||
| int nextSample, subSample; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelInfo); | |||||
| }; | |||||
| //============================================================================== | |||||
| AudioVisualiserComponent::AudioVisualiserComponent (const int initialNumChannels) | |||||
| : numSamples (1024), | |||||
| inputSamplesPerBlock (256), | |||||
| backgroundColour (Colours::black), | |||||
| waveformColour (Colours::white) | |||||
| { | |||||
| setOpaque (true); | |||||
| setNumChannels (initialNumChannels); | |||||
| setRepaintRate (60); | |||||
| } | |||||
| AudioVisualiserComponent::~AudioVisualiserComponent() | |||||
| { | |||||
| } | |||||
| void AudioVisualiserComponent::setNumChannels (const int numChannels) | |||||
| { | |||||
| channels.clear(); | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| channels.add (new ChannelInfo (*this, numSamples)); | |||||
| } | |||||
| void AudioVisualiserComponent::setBufferSize (int newNumSamples) | |||||
| { | |||||
| numSamples = newNumSamples; | |||||
| for (int i = 0; i < channels.size(); ++i) | |||||
| channels.getUnchecked(i)->setBufferSize (newNumSamples); | |||||
| } | |||||
| void AudioVisualiserComponent::clear() | |||||
| { | |||||
| for (int i = 0; i < channels.size(); ++i) | |||||
| channels.getUnchecked(i)->clear(); | |||||
| } | |||||
| void AudioVisualiserComponent::pushBuffer (const float** d, int numChannels, int num) | |||||
| { | |||||
| numChannels = jmin (numChannels, channels.size()); | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| channels.getUnchecked(i)->pushSamples (d[i], num); | |||||
| } | |||||
| void AudioVisualiserComponent::pushBuffer (const AudioSampleBuffer& buffer) | |||||
| { | |||||
| pushBuffer (buffer.getArrayOfReadPointers(), | |||||
| buffer.getNumChannels(), | |||||
| buffer.getNumSamples()); | |||||
| } | |||||
| void AudioVisualiserComponent::pushBuffer (const AudioSourceChannelInfo& buffer) | |||||
| { | |||||
| const int numChannels = jmin (buffer.buffer->getNumChannels(), channels.size()); | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| channels.getUnchecked(i)->pushSamples (buffer.buffer->getReadPointer (i, buffer.startSample), | |||||
| buffer.numSamples); | |||||
| } | |||||
| void AudioVisualiserComponent::pushSample (const float* d, int numChannels) | |||||
| { | |||||
| numChannels = jmin (numChannels, channels.size()); | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| channels.getUnchecked(i)->pushSample (d[i]); | |||||
| } | |||||
| void AudioVisualiserComponent::setSamplesPerBlock (int newSamplesPerPixel) noexcept | |||||
| { | |||||
| inputSamplesPerBlock = newSamplesPerPixel; | |||||
| } | |||||
| void AudioVisualiserComponent::setRepaintRate (int frequencyInHz) | |||||
| { | |||||
| startTimerHz (frequencyInHz); | |||||
| } | |||||
| void AudioVisualiserComponent::timerCallback() | |||||
| { | |||||
| repaint(); | |||||
| } | |||||
| void AudioVisualiserComponent::setColours (Colour bk, Colour fg) noexcept | |||||
| { | |||||
| backgroundColour = bk; | |||||
| waveformColour = fg; | |||||
| repaint(); | |||||
| } | |||||
| void AudioVisualiserComponent::paint (Graphics& g) | |||||
| { | |||||
| g.fillAll (backgroundColour); | |||||
| Rectangle<float> r (getLocalBounds().toFloat()); | |||||
| const float channelHeight = r.getHeight() / channels.size(); | |||||
| g.setColour (waveformColour); | |||||
| for (int i = 0; i < channels.size(); ++i) | |||||
| { | |||||
| const ChannelInfo& c = *channels.getUnchecked(i); | |||||
| paintChannel (g, r.removeFromTop (channelHeight), | |||||
| c.levels.begin(), c.levels.size(), c.nextSample); | |||||
| } | |||||
| } | |||||
| void AudioVisualiserComponent::getChannelAsPath (Path& path, const Range<float>* levels, int numLevels, int nextSample) | |||||
| { | |||||
| path.preallocateSpace (4 * numLevels + 8); | |||||
| for (int i = 0; i < numLevels; ++i) | |||||
| { | |||||
| const float level = -(levels[(nextSample + i) % numLevels].getEnd()); | |||||
| if (i == 0) | |||||
| path.startNewSubPath (0.0f, level); | |||||
| else | |||||
| path.lineTo ((float) i, level); | |||||
| } | |||||
| for (int i = numLevels; --i >= 0;) | |||||
| path.lineTo ((float) i, -(levels[(nextSample + i) % numLevels].getStart())); | |||||
| path.closeSubPath(); | |||||
| } | |||||
| void AudioVisualiserComponent::paintChannel (Graphics& g, Rectangle<float> bounds, | |||||
| const Range<float>* levels, int numLevels, int nextSample) | |||||
| { | |||||
| Path p; | |||||
| getChannelAsPath (p, levels, numLevels, nextSample); | |||||
| g.fillPath (p, AffineTransform::fromTargetPoints (0.0f, -1.0f, bounds.getX(), bounds.getY(), | |||||
| 0.0f, 1.0f, bounds.getX(), bounds.getBottom(), | |||||
| numLevels, -1.0f, bounds.getRight(), bounds.getY())); | |||||
| } | |||||
| @@ -0,0 +1,135 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2015 - ROLI Ltd. | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found 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.juce.com for more information. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef JUCE_AUDIOVISUALISER_H_INCLUDED | |||||
| #define JUCE_AUDIOVISUALISER_H_INCLUDED | |||||
| //============================================================================== | |||||
| /** | |||||
| A simple component that can be used to show a scrolling waveform of audio data. | |||||
| This is a handy way to get a quick visualisation of some audio data. Just create | |||||
| one of these, set its size and oversampling rate, and then feed it with incoming | |||||
| data by calling one of its pushBuffer() or pushSample() methods. | |||||
| You can override its paint method for more customised views, but it's only designed | |||||
| as a quick-and-dirty class for simple tasks, so please don't send us feature requests | |||||
| for fancy additional features that you'd like it to support! If you're building a | |||||
| real-world app that requires more powerful waveform display, you'll probably want to | |||||
| create your own component instead. | |||||
| */ | |||||
| class AudioVisualiserComponent : public Component, | |||||
| private Timer | |||||
| { | |||||
| public: | |||||
| /** Creates a visualiser with the given number of channels. */ | |||||
| AudioVisualiserComponent (int initialNumChannels); | |||||
| /** Destructor. */ | |||||
| ~AudioVisualiserComponent(); | |||||
| /** Changes the number of channels that the visualiser stores. */ | |||||
| void setNumChannels (int numChannels); | |||||
| /** Changes the number of samples that the visualiser keeps in its history. | |||||
| Note that this value refers to the number of averaged sample blocks, and each | |||||
| block is calculated as the peak of a number of incoming audio samples. To set | |||||
| the number of incoming samples per block, use setSamplesPerBlock(). | |||||
| */ | |||||
| void setBufferSize (int bufferSize); | |||||
| /** */ | |||||
| void setSamplesPerBlock (int newNumInputSamplesPerBlock) noexcept; | |||||
| /** */ | |||||
| int getSamplesPerBlock() const noexcept { return inputSamplesPerBlock; } | |||||
| /** Clears the contents of the buffers. */ | |||||
| void clear(); | |||||
| /** Pushes a buffer of channels data. | |||||
| The number of channels provided here is expected to match the number of channels | |||||
| that this AudioVisualiserComponent has been told to use. | |||||
| */ | |||||
| void pushBuffer (const AudioSampleBuffer& bufferToPush); | |||||
| /** Pushes a buffer of channels data. | |||||
| The number of channels provided here is expected to match the number of channels | |||||
| that this AudioVisualiserComponent has been told to use. | |||||
| */ | |||||
| void pushBuffer (const AudioSourceChannelInfo& bufferToPush); | |||||
| /** Pushes a buffer of channels data. | |||||
| The number of channels provided here is expected to match the number of channels | |||||
| that this AudioVisualiserComponent has been told to use. | |||||
| */ | |||||
| void pushBuffer (const float** channelData, int numChannels, int numSamples); | |||||
| /** Pushes a single sample (per channel). | |||||
| The number of channels provided here is expected to match the number of channels | |||||
| that this AudioVisualiserComponent has been told to use. | |||||
| */ | |||||
| void pushSample (const float* samplesForEachChannel, int numChannels); | |||||
| /** Sets the colours used to paint the */ | |||||
| void setColours (Colour backgroundColour, Colour waveformColour) noexcept; | |||||
| /** Sets the frequency at which the component repaints itself. */ | |||||
| void setRepaintRate (int frequencyInHz); | |||||
| /** Draws a channel of audio data in the given bounds. | |||||
| The default implementation just calls getChannelAsPath() and fits this into the given | |||||
| area. You may want to override this to draw things differently. | |||||
| */ | |||||
| virtual void paintChannel (Graphics&, Rectangle<float> bounds, | |||||
| const Range<float>* levels, int numLevels, int nextSample); | |||||
| /** Creates a path which contains the waveform shape of a given set of range data. | |||||
| The path is normalised so that -1 and +1 are its upper and lower bounds, and it | |||||
| goes from 0 to numLevels on the X axis. | |||||
| */ | |||||
| void getChannelAsPath (Path& result, const Range<float>* levels, int numLevels, int nextSample); | |||||
| //========================================================================== | |||||
| /** @internal */ | |||||
| void paint (Graphics&) override; | |||||
| private: | |||||
| struct ChannelInfo; | |||||
| friend struct ChannelInfo; | |||||
| friend struct ContainerDeletePolicy<ChannelInfo>; | |||||
| OwnedArray<ChannelInfo> channels; | |||||
| int numSamples, inputSamplesPerBlock; | |||||
| float interpolation; | |||||
| Colour backgroundColour, waveformColour; | |||||
| void timerCallback() override; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioVisualiserComponent); | |||||
| }; | |||||
| #endif // JUCE_AUDIOVISUALISER_H_INCLUDED | |||||
| @@ -44,6 +44,7 @@ namespace juce | |||||
| #include "gui/juce_AudioDeviceSelectorComponent.cpp" | #include "gui/juce_AudioDeviceSelectorComponent.cpp" | ||||
| #include "gui/juce_AudioThumbnail.cpp" | #include "gui/juce_AudioThumbnail.cpp" | ||||
| #include "gui/juce_AudioThumbnailCache.cpp" | #include "gui/juce_AudioThumbnailCache.cpp" | ||||
| #include "gui/juce_AudioVisualiserComponent.cpp" | |||||
| #include "gui/juce_MidiKeyboardComponent.cpp" | #include "gui/juce_MidiKeyboardComponent.cpp" | ||||
| #include "gui/juce_AudioAppComponent.cpp" | #include "gui/juce_AudioAppComponent.cpp" | ||||
| #include "players/juce_AudioProcessorPlayer.cpp" | #include "players/juce_AudioProcessorPlayer.cpp" | ||||
| @@ -38,6 +38,7 @@ namespace juce | |||||
| #include "gui/juce_AudioThumbnailBase.h" | #include "gui/juce_AudioThumbnailBase.h" | ||||
| #include "gui/juce_AudioThumbnail.h" | #include "gui/juce_AudioThumbnail.h" | ||||
| #include "gui/juce_AudioThumbnailCache.h" | #include "gui/juce_AudioThumbnailCache.h" | ||||
| #include "gui/juce_AudioVisualiserComponent.h" | |||||
| #include "gui/juce_MidiKeyboardComponent.h" | #include "gui/juce_MidiKeyboardComponent.h" | ||||
| #include "gui/juce_AudioAppComponent.h" | #include "gui/juce_AudioAppComponent.h" | ||||
| #include "players/juce_AudioProcessorPlayer.h" | #include "players/juce_AudioProcessorPlayer.h" | ||||