From 57d4889f91c60e31ac49b56cda64e02478e9d261 Mon Sep 17 00:00:00 2001 From: jules Date: Thu, 3 Sep 2015 15:36:41 +0100 Subject: [PATCH] Added a new class AudioVisualiserComponent, and updated the demo audio pages to use this. --- .../Source/Demos/AudioLiveScrollingDisplay.h | 82 +------ .../gui/juce_AudioVisualiserComponent.cpp | 220 ++++++++++++++++++ .../gui/juce_AudioVisualiserComponent.h | 135 +++++++++++ modules/juce_audio_utils/juce_audio_utils.cpp | 1 + modules/juce_audio_utils/juce_audio_utils.h | 1 + 5 files changed, 369 insertions(+), 70 deletions(-) create mode 100644 modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.cpp create mode 100644 modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.h diff --git a/examples/Demo/Source/Demos/AudioLiveScrollingDisplay.h b/examples/Demo/Source/Demos/AudioLiveScrollingDisplay.h index bfe5dace09..805059bdff 100644 --- a/examples/Demo/Source/Demos/AudioLiveScrollingDisplay.h +++ b/examples/Demo/Source/Demos/AudioLiveScrollingDisplay.h @@ -32,18 +32,14 @@ /* This component scrolls a continuous waveform showing the audio that's 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: - 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; 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.. 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 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) 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); diff --git a/modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.cpp b/modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.cpp new file mode 100644 index 0000000000..7ec04679ef --- /dev/null +++ b/modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.cpp @@ -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(); + + value = Range(); + 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 (newSample, newSample); + } + else + { + value = value.getUnionWith (newSample); + } + } + + void setBufferSize (int newSize) + { + levels.removeRange (newSize, levels.size()); + levels.insertMultiple (-1, Range(), newSize - levels.size()); + + if (nextSample >= newSize) + nextSample = 0; + } + + AudioVisualiserComponent& owner; + Array > levels; + Range 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 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* 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 bounds, + const Range* 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())); +} diff --git a/modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.h b/modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.h new file mode 100644 index 0000000000..644fdac113 --- /dev/null +++ b/modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.h @@ -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 bounds, + const Range* 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* levels, int numLevels, int nextSample); + + //========================================================================== + /** @internal */ + void paint (Graphics&) override; + +private: + struct ChannelInfo; + friend struct ChannelInfo; + friend struct ContainerDeletePolicy; + + OwnedArray 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 diff --git a/modules/juce_audio_utils/juce_audio_utils.cpp b/modules/juce_audio_utils/juce_audio_utils.cpp index c53ea7c2bb..1bc03efc77 100644 --- a/modules/juce_audio_utils/juce_audio_utils.cpp +++ b/modules/juce_audio_utils/juce_audio_utils.cpp @@ -44,6 +44,7 @@ namespace juce #include "gui/juce_AudioDeviceSelectorComponent.cpp" #include "gui/juce_AudioThumbnail.cpp" #include "gui/juce_AudioThumbnailCache.cpp" +#include "gui/juce_AudioVisualiserComponent.cpp" #include "gui/juce_MidiKeyboardComponent.cpp" #include "gui/juce_AudioAppComponent.cpp" #include "players/juce_AudioProcessorPlayer.cpp" diff --git a/modules/juce_audio_utils/juce_audio_utils.h b/modules/juce_audio_utils/juce_audio_utils.h index 40e9fffb4c..3bfe25d1b7 100644 --- a/modules/juce_audio_utils/juce_audio_utils.h +++ b/modules/juce_audio_utils/juce_audio_utils.h @@ -38,6 +38,7 @@ namespace juce #include "gui/juce_AudioThumbnailBase.h" #include "gui/juce_AudioThumbnail.h" #include "gui/juce_AudioThumbnailCache.h" +#include "gui/juce_AudioVisualiserComponent.h" #include "gui/juce_MidiKeyboardComponent.h" #include "gui/juce_AudioAppComponent.h" #include "players/juce_AudioProcessorPlayer.h"