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