@@ -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<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); | |||
@@ -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_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" | |||
@@ -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" | |||