@@ -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" | ||||