mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Added a new class AudioVisualiserComponent, and updated the demo audio pages to use this.
This commit is contained in:
parent
560b314111
commit
57d4889f91
5 changed files with 369 additions and 70 deletions
|
|
@ -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);
|
||||
|
|
|
|||
220
modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.cpp
Normal file
220
modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.cpp
Normal file
|
|
@ -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()));
|
||||
}
|
||||
135
modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.h
Normal file
135
modules/juce_audio_utils/gui/juce_AudioVisualiserComponent.h
Normal file
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue