mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-02-08 04:20:09 +00:00
Added Inter-App Audio capabilities to standalone plug-ins
This commit is contained in:
parent
00f57d43c5
commit
4c59a920b4
80 changed files with 2949 additions and 163 deletions
|
|
@ -0,0 +1,277 @@
|
|||
#ifndef IAAEFFECTEDITOR_H_INCLUDED
|
||||
#define IAAEFFECTEDITOR_H_INCLUDED
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
#include "IAAEffectProcessor.h"
|
||||
#include "SimpleMeter.h"
|
||||
|
||||
|
||||
class IAAEffectEditor : public AudioProcessorEditor,
|
||||
private IAAEffectProcessor::MeterListener,
|
||||
private ButtonListener,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
IAAEffectEditor (IAAEffectProcessor& p,
|
||||
AudioProcessorValueTreeState& vts)
|
||||
: AudioProcessorEditor (p),
|
||||
processor (p),
|
||||
parameters (vts)
|
||||
{
|
||||
// Register for meter value updates.
|
||||
processor.addMeterListener (*this);
|
||||
|
||||
gainSlider.setSliderStyle (Slider::SliderStyle::LinearVertical);
|
||||
gainSlider.setTextBoxStyle (Slider::TextEntryBoxPosition::TextBoxAbove, false, 60, 20);
|
||||
addAndMakeVisible (gainSlider);
|
||||
|
||||
for (auto& meter : meters)
|
||||
addAndMakeVisible (meter);
|
||||
|
||||
// Configure all the graphics for the transport control.
|
||||
|
||||
transportText.setColour (Label::textColourId, Colours::white);
|
||||
transportText.setFont (Font (Font::getDefaultMonospacedFontName(), 18.0f, Font::plain));
|
||||
transportText.setJustificationType (Justification::topLeft);
|
||||
addChildComponent (transportText);
|
||||
|
||||
Path rewindShape;
|
||||
rewindShape.addRectangle (0.0, 0.0, 5.0, buttonSize);
|
||||
rewindShape.addTriangle (0.0, buttonSize / 2, buttonSize, 0.0, buttonSize, buttonSize);
|
||||
rewindButton.setShape (rewindShape, true, true, false);
|
||||
rewindButton.addListener (this);
|
||||
addChildComponent (rewindButton);
|
||||
|
||||
Path playShape;
|
||||
playShape.addTriangle (0.0, 0.0, 0.0, buttonSize, buttonSize, buttonSize / 2);
|
||||
playButton.setShape (playShape, true, true, false);
|
||||
playButton.addListener (this);
|
||||
addChildComponent (playButton);
|
||||
|
||||
Path recordShape;
|
||||
recordShape.addEllipse (0.0, 0.0, buttonSize, buttonSize);
|
||||
recordButton.setShape (recordShape, true, true, false);
|
||||
recordButton.addListener (this);
|
||||
addChildComponent (recordButton);
|
||||
|
||||
// Configure the switch to host button.
|
||||
|
||||
switchToHostButtonLabel.setColour (Label::textColourId, Colours::white);
|
||||
switchToHostButtonLabel.setFont (Font (Font::getDefaultMonospacedFontName(), 18.0f, Font::plain));
|
||||
switchToHostButtonLabel.setJustificationType (Justification::centredRight);
|
||||
switchToHostButtonLabel.setText ("Switch to\nhost app:", dontSendNotification);
|
||||
addChildComponent (switchToHostButtonLabel);
|
||||
|
||||
switchToHostButton.addListener (this);
|
||||
addChildComponent (switchToHostButton);
|
||||
|
||||
Rectangle<int> screenSize = Desktop::getInstance().getDisplays().getMainDisplay().userArea;
|
||||
setSize (screenSize.getWidth(), screenSize.getHeight());
|
||||
|
||||
resized();
|
||||
|
||||
startTimerHz (60);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (Colours::darkgrey);
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto area = getBounds().reduced (10);
|
||||
|
||||
gainSlider.setBounds (area.removeFromLeft (60));
|
||||
|
||||
for (auto& meter : meters)
|
||||
{
|
||||
area.removeFromLeft (10);
|
||||
meter.setBounds (area.removeFromLeft (20));
|
||||
}
|
||||
|
||||
area.removeFromLeft (20);
|
||||
transportText.setBounds (area.removeFromTop (120));
|
||||
|
||||
auto navigationArea = area.removeFromTop (buttonSize);
|
||||
rewindButton.setTopLeftPosition (navigationArea.getPosition());
|
||||
navigationArea.removeFromLeft (buttonSize + 10);
|
||||
playButton.setTopLeftPosition (navigationArea.getPosition());
|
||||
navigationArea.removeFromLeft (buttonSize + 10);
|
||||
recordButton.setTopLeftPosition (navigationArea.getPosition());
|
||||
|
||||
area.removeFromTop (30);
|
||||
|
||||
auto appSwitchArea = area.removeFromTop (buttonSize);
|
||||
switchToHostButtonLabel.setBounds (appSwitchArea.removeFromLeft (100));
|
||||
appSwitchArea.removeFromLeft (5);
|
||||
switchToHostButton.setBounds (appSwitchArea.removeFromLeft (buttonSize));
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
// Called from the audio thread.
|
||||
void handleNewMeterValue (int channel, float value) override
|
||||
{
|
||||
meters[(size_t) channel].update (value);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void timerCallback () override
|
||||
{
|
||||
auto timeInfoSuccess = processor.updateCurrentTimeInfoFromHost (lastPosInfo);
|
||||
transportText.setVisible (timeInfoSuccess);
|
||||
if (timeInfoSuccess)
|
||||
updateTransportTextDisplay();
|
||||
|
||||
updateTransportButtonsDisplay();
|
||||
|
||||
updateSwitchToHostDisplay();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void buttonClicked (Button* b) override
|
||||
{
|
||||
auto playHead = processor.getPlayHead();
|
||||
if (playHead != nullptr && playHead->canControlTransport())
|
||||
{
|
||||
if (b == &rewindButton)
|
||||
{
|
||||
playHead->transportRewind();
|
||||
}
|
||||
else if (b == &playButton)
|
||||
{
|
||||
playHead->transportPlay(! lastPosInfo.isPlaying);
|
||||
}
|
||||
else if (b == &recordButton)
|
||||
{
|
||||
playHead->transportRecord (! lastPosInfo.isRecording);
|
||||
}
|
||||
else if (b == &switchToHostButton)
|
||||
{
|
||||
PluginHostType hostType;
|
||||
|
||||
hostType.switchToHostApplication();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// quick-and-dirty function to format a timecode string
|
||||
String timeToTimecodeString (double seconds)
|
||||
{
|
||||
auto millisecs = roundToInt (seconds * 1000.0);
|
||||
auto absMillisecs = std::abs (millisecs);
|
||||
|
||||
return String::formatted ("%02d:%02d:%02d.%03d",
|
||||
millisecs / 360000,
|
||||
(absMillisecs / 60000) % 60,
|
||||
(absMillisecs / 1000) % 60,
|
||||
absMillisecs % 1000);
|
||||
}
|
||||
|
||||
// A quick-and-dirty function to format a bars/beats string.
|
||||
String quarterNotePositionToBarsBeatsString (double quarterNotes, int numerator, int denominator)
|
||||
{
|
||||
if (numerator == 0 || denominator == 0)
|
||||
return "1|1|000";
|
||||
|
||||
auto quarterNotesPerBar = (numerator * 4 / denominator);
|
||||
auto beats = (fmod (quarterNotes, quarterNotesPerBar) / quarterNotesPerBar) * numerator;
|
||||
|
||||
auto bar = ((int) quarterNotes) / quarterNotesPerBar + 1;
|
||||
auto beat = ((int) beats) + 1;
|
||||
auto ticks = ((int) (fmod (beats, 1.0) * 960.0 + 0.5));
|
||||
|
||||
return String::formatted ("%d|%d|%03d", bar, beat, ticks);
|
||||
}
|
||||
|
||||
void updateTransportTextDisplay()
|
||||
{
|
||||
MemoryOutputStream displayText;
|
||||
|
||||
displayText << "[" << SystemStats::getJUCEVersion() << "]\n"
|
||||
<< String (lastPosInfo.bpm, 2) << " bpm\n"
|
||||
<< lastPosInfo.timeSigNumerator << '/' << lastPosInfo.timeSigDenominator << "\n"
|
||||
<< timeToTimecodeString (lastPosInfo.timeInSeconds) << "\n"
|
||||
<< quarterNotePositionToBarsBeatsString (lastPosInfo.ppqPosition,
|
||||
lastPosInfo.timeSigNumerator,
|
||||
lastPosInfo.timeSigDenominator) << "\n";
|
||||
|
||||
if (lastPosInfo.isRecording)
|
||||
displayText << "(recording)";
|
||||
else if (lastPosInfo.isPlaying)
|
||||
displayText << "(playing)";
|
||||
|
||||
transportText.setText (displayText.toString(), dontSendNotification);
|
||||
}
|
||||
|
||||
void updateTransportButtonsDisplay()
|
||||
{
|
||||
auto visible = processor.getPlayHead() != nullptr
|
||||
&& processor.getPlayHead()->canControlTransport();
|
||||
|
||||
if (rewindButton.isVisible() != visible)
|
||||
{
|
||||
rewindButton.setVisible (visible);
|
||||
playButton.setVisible (visible);
|
||||
recordButton.setVisible (visible);
|
||||
}
|
||||
|
||||
if (visible)
|
||||
{
|
||||
Colour playColour = lastPosInfo.isPlaying ? Colours::green : defaultButtonColour;
|
||||
playButton.setColours (playColour, playColour, playColour);
|
||||
playButton.repaint();
|
||||
|
||||
Colour recordColour = lastPosInfo.isRecording ? Colours::red : defaultButtonColour;
|
||||
recordButton.setColours (recordColour, recordColour, recordColour);
|
||||
recordButton.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void updateSwitchToHostDisplay()
|
||||
{
|
||||
PluginHostType hostType;
|
||||
const bool visible = hostType.isInterAppAudioConnected();
|
||||
|
||||
if (switchToHostButtonLabel.isVisible() != visible)
|
||||
{
|
||||
switchToHostButtonLabel.setVisible (visible);
|
||||
switchToHostButton.setVisible (visible);
|
||||
if (visible) {
|
||||
auto icon = hostType.getHostIcon (buttonSize);
|
||||
switchToHostButton.setImages(false, true, true,
|
||||
icon, 1.0, Colours::transparentBlack,
|
||||
icon, 1.0, Colours::transparentBlack,
|
||||
icon, 1.0, Colours::transparentBlack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IAAEffectProcessor& processor;
|
||||
AudioProcessorValueTreeState& parameters;
|
||||
|
||||
const int buttonSize = 30;
|
||||
const Colour defaultButtonColour = Colours::lightgrey;
|
||||
ShapeButton rewindButton {"Rewind", defaultButtonColour, defaultButtonColour, defaultButtonColour};
|
||||
ShapeButton playButton {"Play", defaultButtonColour, defaultButtonColour, defaultButtonColour};
|
||||
ShapeButton recordButton {"Record", defaultButtonColour, defaultButtonColour, defaultButtonColour};
|
||||
|
||||
Slider gainSlider;
|
||||
AudioProcessorValueTreeState::SliderAttachment gainAttachment = {parameters, "gain", gainSlider};
|
||||
|
||||
std::array<SimpleMeter, 2> meters;
|
||||
|
||||
ImageButton switchToHostButton;
|
||||
Label transportText, switchToHostButtonLabel;
|
||||
Image hostImage;
|
||||
|
||||
AudioPlayHead::CurrentPositionInfo lastPosInfo;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IAAEffectEditor)
|
||||
};
|
||||
|
||||
|
||||
#endif // IAAEFFECTEDITOR_H_INCLUDED
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
#include "IAAEffectProcessor.h"
|
||||
#include "IAAEffectEditor.h"
|
||||
|
||||
|
||||
IAAEffectProcessor::IAAEffectProcessor()
|
||||
: AudioProcessor (BusesProperties()
|
||||
.withInput ("Input", AudioChannelSet::stereo(), true)
|
||||
.withOutput ("Output", AudioChannelSet::stereo(), true)),
|
||||
parameters (*this, nullptr)
|
||||
{
|
||||
parameters.createAndAddParameter ("gain",
|
||||
"Gain",
|
||||
String(),
|
||||
NormalisableRange<float> (0.0f, 1.0f),
|
||||
(float) (1.0 / 3.14),
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
parameters.state = ValueTree (Identifier ("InterAppAudioEffect"));
|
||||
}
|
||||
|
||||
IAAEffectProcessor::~IAAEffectProcessor()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const String IAAEffectProcessor::getName() const
|
||||
{
|
||||
return JucePlugin_Name;
|
||||
}
|
||||
|
||||
bool IAAEffectProcessor::acceptsMidi() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IAAEffectProcessor::producesMidi() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double IAAEffectProcessor::getTailLengthSeconds() const
|
||||
{
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
int IAAEffectProcessor::getNumPrograms()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int IAAEffectProcessor::getCurrentProgram()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IAAEffectProcessor::setCurrentProgram (int)
|
||||
{
|
||||
}
|
||||
|
||||
const String IAAEffectProcessor::getProgramName (int)
|
||||
{
|
||||
return String();
|
||||
}
|
||||
|
||||
void IAAEffectProcessor::changeProgramName (int, const String&)
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void IAAEffectProcessor::prepareToPlay (double, int)
|
||||
{
|
||||
previousGain = *parameters.getRawParameterValue ("gain");
|
||||
}
|
||||
|
||||
void IAAEffectProcessor::releaseResources()
|
||||
{
|
||||
}
|
||||
|
||||
bool IAAEffectProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
|
||||
{
|
||||
if (layouts.getMainInputChannelSet() != AudioChannelSet::stereo())
|
||||
return false;
|
||||
|
||||
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void IAAEffectProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer&)
|
||||
{
|
||||
const float gain = *parameters.getRawParameterValue ("gain");
|
||||
|
||||
const int totalNumInputChannels = getTotalNumInputChannels();
|
||||
const int totalNumOutputChannels = getTotalNumOutputChannels();
|
||||
|
||||
const int numSamples = buffer.getNumSamples();
|
||||
|
||||
for (int i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
|
||||
buffer.clear (i, 0, buffer.getNumSamples());
|
||||
|
||||
// Apply the gain to the samples using a ramp to avoid discontinuities in
|
||||
// the audio between processed buffers.
|
||||
for (int channel = 0; channel < totalNumInputChannels; ++channel)
|
||||
{
|
||||
buffer.applyGainRamp (channel, 0, numSamples, previousGain, gain);
|
||||
|
||||
meterListeners.call (&IAAEffectProcessor::MeterListener::handleNewMeterValue,
|
||||
channel,
|
||||
buffer.getMagnitude (channel, 0, numSamples));
|
||||
}
|
||||
|
||||
previousGain = gain;
|
||||
|
||||
// Now ask the host for the current time so we can store it to be displayed later.
|
||||
updateCurrentTimeInfoFromHost (lastPosInfo);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool IAAEffectProcessor::hasEditor() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
AudioProcessorEditor* IAAEffectProcessor::createEditor()
|
||||
{
|
||||
return new IAAEffectEditor (*this, parameters);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void IAAEffectProcessor::getStateInformation (MemoryBlock& destData)
|
||||
{
|
||||
auto xml = std::unique_ptr<XmlElement> (parameters.state.createXml());
|
||||
copyXmlToBinary (*xml, destData);
|
||||
}
|
||||
|
||||
void IAAEffectProcessor::setStateInformation (const void* data, int sizeInBytes)
|
||||
{
|
||||
auto xmlState = std::unique_ptr<XmlElement> (getXmlFromBinary (data, sizeInBytes));
|
||||
if (xmlState.get() != nullptr)
|
||||
if (xmlState->hasTagName (parameters.state.getType()))
|
||||
parameters.state = ValueTree::fromXml (*xmlState);
|
||||
}
|
||||
|
||||
bool IAAEffectProcessor::updateCurrentTimeInfoFromHost (AudioPlayHead::CurrentPositionInfo &posInfo)
|
||||
{
|
||||
if (AudioPlayHead* ph = getPlayHead())
|
||||
{
|
||||
AudioPlayHead::CurrentPositionInfo newTime;
|
||||
|
||||
if (ph->getCurrentPosition (newTime))
|
||||
{
|
||||
posInfo = newTime; // Successfully got the current time from the host.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the host fails to provide the current time, we'll just reset our copy to a default.
|
||||
lastPosInfo.resetToDefault();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
||||
{
|
||||
return new IAAEffectProcessor();
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
#ifndef PLUGINPROCESSOR_H_INCLUDED
|
||||
#define PLUGINPROCESSOR_H_INCLUDED
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
|
||||
// A simple Inter-App Audio plug-in with a gain control and some meters.
|
||||
class IAAEffectProcessor : public AudioProcessor
|
||||
{
|
||||
public:
|
||||
IAAEffectProcessor();
|
||||
~IAAEffectProcessor();
|
||||
|
||||
//==============================================================================
|
||||
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
|
||||
void releaseResources() override;
|
||||
|
||||
bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
|
||||
|
||||
void processBlock (AudioSampleBuffer&, MidiBuffer&) override;
|
||||
|
||||
//==============================================================================
|
||||
AudioProcessorEditor* createEditor() override;
|
||||
bool hasEditor() const override;
|
||||
|
||||
//==============================================================================
|
||||
const String getName() const override;
|
||||
|
||||
bool acceptsMidi() const override;
|
||||
bool producesMidi() const override;
|
||||
double getTailLengthSeconds() const override;
|
||||
|
||||
//==============================================================================
|
||||
int getNumPrograms() override;
|
||||
int getCurrentProgram() override;
|
||||
void setCurrentProgram (int index) override;
|
||||
const String getProgramName (int index) override;
|
||||
void changeProgramName (int index, const String& newName) override;
|
||||
|
||||
//==============================================================================
|
||||
void getStateInformation (MemoryBlock& destData) override;
|
||||
void setStateInformation (const void* data, int sizeInBytes) override;
|
||||
|
||||
//==============================================================================
|
||||
bool updateCurrentTimeInfoFromHost (AudioPlayHead::CurrentPositionInfo&);
|
||||
|
||||
// Allow an IAAAudioProcessorEditor to register as a listener to receive new
|
||||
// meter values directly from the audio thread.
|
||||
struct MeterListener
|
||||
{
|
||||
virtual ~MeterListener() {};
|
||||
|
||||
virtual void handleNewMeterValue (int, float) = 0;
|
||||
};
|
||||
|
||||
void addMeterListener (MeterListener& listener) { meterListeners.add (&listener); };
|
||||
void removeMeterListener (MeterListener& listener) { meterListeners.remove (&listener); };
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
AudioProcessorValueTreeState parameters;
|
||||
float previousGain = 0.0;
|
||||
std::array <float, 2> meterValues = { { 0, 0 } };
|
||||
|
||||
// This keeps a copy of the last set of timing info that was acquired during an
|
||||
// audio callback - the UI component will display this.
|
||||
AudioPlayHead::CurrentPositionInfo lastPosInfo;
|
||||
|
||||
ListenerList<MeterListener> meterListeners;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IAAEffectProcessor)
|
||||
};
|
||||
|
||||
|
||||
#endif // PLUGINPROCESSOR_H_INCLUDED
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
#ifndef SIMPLEMETER_H_INCLUDED
|
||||
#define SIMPLEMETER_H_INCLUDED
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
|
||||
// A very simple decaying meter.
|
||||
class SimpleMeter : public Component,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
SimpleMeter()
|
||||
{
|
||||
startTimerHz (30);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll(Colours::transparentBlack);
|
||||
|
||||
auto area = g.getClipBounds();
|
||||
g.setColour (Colours::skyblue);
|
||||
g.fillRoundedRectangle(area.toFloat(), 6.0);
|
||||
|
||||
auto unfilledHeight = area.getHeight() * (1.0 - level);
|
||||
g.reduceClipRegion (area.getX(), area.getY(),
|
||||
area.getWidth(), (int) unfilledHeight);
|
||||
g.setColour (Colours::grey);
|
||||
g.fillRoundedRectangle(area.toFloat(), 6.0);
|
||||
}
|
||||
|
||||
void resized() override {}
|
||||
|
||||
//==============================================================================
|
||||
// Called from the audio thread.
|
||||
void update (float newLevel)
|
||||
{
|
||||
// We don't care if maxLevel gets set to zero (in timerCallback) between the
|
||||
// load and the assignment.
|
||||
maxLevel = jmax (maxLevel.load(), newLevel);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void timerCallback() override
|
||||
{
|
||||
auto callbackLevel = maxLevel.exchange (0.0);
|
||||
|
||||
auto decayFactor = 0.95;
|
||||
if (callbackLevel > level)
|
||||
level = callbackLevel;
|
||||
else if (level > 0.001)
|
||||
level *= decayFactor;
|
||||
else
|
||||
level = 0;
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
std::atomic<float> maxLevel {0.0};
|
||||
float level = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleMeter)
|
||||
};
|
||||
|
||||
#if JUCE_PROJUCER_LIVE_BUILD
|
||||
|
||||
// Animate the meter in the Projucer live build.
|
||||
struct MockSimpleMeter : public Component,
|
||||
private Timer
|
||||
{
|
||||
MockSimpleMeter()
|
||||
{
|
||||
addAndMakeVisible (meter);
|
||||
resized();
|
||||
startTimerHz (100);
|
||||
}
|
||||
|
||||
void paint (Graphics&) override {}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
meter.setBounds (getBounds());
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
meter.update (std::pow (randomNumberGenerator.nextFloat(), 2));
|
||||
}
|
||||
|
||||
SimpleMeter meter;
|
||||
Random randomNumberGenerator;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif // SIMPLEMETER_H_INCLUDED
|
||||
Loading…
Add table
Add a link
Reference in a new issue