build(deps): vendor JUCE 7.0.12

This commit is contained in:
2026-09-08 14:55:21 +02:00
parent 5b99f51bd1
commit d6705ab29f
3591 changed files with 1218267 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+477
View File
@@ -0,0 +1,477 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2022 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: AUv3SynthPlugin
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: AUv3 synthesiser audio plugin.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, xcode_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: AUv3SynthProcessor
useLocalCopy: 1
pluginCharacteristics: pluginIsSynth, pluginWantsMidiIn
extraPluginFormats: AUv3
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
class MaterialLookAndFeel final : public LookAndFeel_V4
{
public:
//==============================================================================
MaterialLookAndFeel()
{
setColour (ResizableWindow::backgroundColourId, windowBackgroundColour);
setColour (TextButton::buttonOnColourId, brightButtonColour);
setColour (TextButton::buttonColourId, disabledButtonColour);
}
//==============================================================================
void drawButtonBackground (Graphics& g,
Button& button,
const Colour& /*backgroundColour*/,
bool /*isMouseOverButton*/,
bool isButtonDown) override
{
auto buttonRect = button.getLocalBounds().toFloat();
if (isButtonDown)
g.setColour (brightButtonColour.withAlpha (0.7f));
else if (! button.isEnabled())
g.setColour (disabledButtonColour);
else
g.setColour (brightButtonColour);
g.fillRoundedRectangle (buttonRect, 5.0f);
}
//==============================================================================
void drawButtonText (Graphics& g, TextButton& button, bool isMouseOverButton, bool isButtonDown) override
{
ignoreUnused (isMouseOverButton, isButtonDown);
Font font (getTextButtonFont (button, button.getHeight()));
g.setFont (font);
if (button.isEnabled())
g.setColour (Colours::white);
else
g.setColour (backgroundColour);
g.drawFittedText (button.getButtonText(), 0, 0,
button.getWidth(),
button.getHeight(),
Justification::centred, 2);
}
//==============================================================================
void drawLinearSlider (Graphics& g, int x, int y, int width, int height,
float sliderPos, float minSliderPos, float maxSliderPos,
Slider::SliderStyle style, Slider& slider) override
{
ignoreUnused (style, minSliderPos, maxSliderPos);
auto r = Rectangle<int> (x + haloRadius, y, width - (haloRadius * 2), height);
auto backgroundBar = r.withSizeKeepingCentre (r.getWidth(), 2);
sliderPos = (sliderPos - minSliderPos) / static_cast<float> (width);
auto knobPos = static_cast<int> (sliderPos * (float) r.getWidth());
g.setColour (sliderActivePart);
g.fillRect (backgroundBar.removeFromLeft (knobPos));
g.setColour (sliderInactivePart);
g.fillRect (backgroundBar);
if (slider.isMouseOverOrDragging())
{
auto haloBounds = r.withTrimmedLeft (knobPos - haloRadius)
.withWidth (haloRadius * 2)
.withSizeKeepingCentre (haloRadius * 2, haloRadius * 2);
g.setColour (sliderActivePart.withAlpha (0.5f));
g.fillEllipse (haloBounds.toFloat());
}
auto knobRadius = slider.isMouseOverOrDragging() ? knobActiveRadius : knobInActiveRadius;
auto knobBounds = r.withTrimmedLeft (knobPos - knobRadius)
.withWidth (knobRadius * 2)
.withSizeKeepingCentre (knobRadius * 2, knobRadius * 2);
g.setColour (sliderActivePart);
g.fillEllipse (knobBounds.toFloat());
}
//==============================================================================
Font getTextButtonFont (TextButton& button, int buttonHeight) override
{
return LookAndFeel_V3::getTextButtonFont (button, buttonHeight).withHeight (buttonFontSize);
}
Font getLabelFont (Label& label) override
{
return LookAndFeel_V3::getLabelFont (label).withHeight (labelFontSize);
}
//==============================================================================
enum
{
labelFontSize = 12,
buttonFontSize = 15
};
//==============================================================================
enum
{
knobActiveRadius = 12,
knobInActiveRadius = 8,
haloRadius = 18
};
//==============================================================================
const Colour windowBackgroundColour = Colour (0xff262328);
const Colour backgroundColour = Colour (0xff4d4d4d);
const Colour brightButtonColour = Colour (0xff80cbc4);
const Colour disabledButtonColour = Colour (0xffe4e4e4);
const Colour sliderInactivePart = Colour (0xff545d62);
const Colour sliderActivePart = Colour (0xff80cbc4);
};
//==============================================================================
class AUv3SynthEditor final : public AudioProcessorEditor,
private Timer
{
public:
//==============================================================================
AUv3SynthEditor (AudioProcessor& processorIn)
: AudioProcessorEditor (processorIn),
roomSizeSlider (Slider::LinearHorizontal, Slider::NoTextBox)
{
setLookAndFeel (&materialLookAndFeel);
roomSizeSlider.setValue (getParameterValue ("roomSize"), NotificationType::dontSendNotification);
recordButton.onClick = [this] { startRecording(); };
addAndMakeVisible (recordButton);
roomSizeSlider.onValueChange = [this] { setParameterValue ("roomSize", (float) roomSizeSlider.getValue()); };
roomSizeSlider.setRange (0.0, 1.0);
addAndMakeVisible (roomSizeSlider);
if (auto fileStream = createAssetInputStream ("proaudio.path"))
{
Path proAudioPath;
proAudioPath.loadPathFromStream (*fileStream);
proAudioIcon.setPath (proAudioPath);
addAndMakeVisible (proAudioIcon);
auto proAudioIconColour = findColour (TextButton::buttonOnColourId);
proAudioIcon.setFill (FillType (proAudioIconColour));
}
setSize (600, 400);
startTimer (100);
}
~AUv3SynthEditor() override
{
setLookAndFeel (nullptr);
}
//==============================================================================
void paint (Graphics& g) override
{
g.fillAll (findColour (ResizableWindow::backgroundColourId));
}
void resized() override
{
auto r = getLocalBounds();
auto guiElementAreaHeight = r.getHeight() / 3;
proAudioIcon.setTransformToFit (r.removeFromLeft (proportionOfWidth (0.25))
.withSizeKeepingCentre (guiElementAreaHeight, guiElementAreaHeight)
.toFloat(),
RectanglePlacement::fillDestination);
auto margin = guiElementAreaHeight / 4;
r.reduce (margin, margin);
auto buttonHeight = guiElementAreaHeight - margin;
recordButton .setBounds (r.removeFromTop (guiElementAreaHeight).withSizeKeepingCentre (r.getWidth(), buttonHeight));
roomSizeSlider.setBounds (r.removeFromTop (guiElementAreaHeight).withSizeKeepingCentre (r.getWidth(), buttonHeight));
}
//==============================================================================
void startRecording()
{
recordButton.setEnabled (false);
setParameterValue ("isRecording", 1.0f);
}
private:
//==============================================================================
void timerCallback() override
{
auto isRecordingNow = (getParameterValue ("isRecording") >= 0.5f);
recordButton.setEnabled (! isRecordingNow);
roomSizeSlider.setValue (getParameterValue ("roomSize"), NotificationType::dontSendNotification);
}
//==============================================================================
AudioProcessorParameter* getParameter (const String& paramId)
{
if (auto* audioProcessor = getAudioProcessor())
{
auto& params = audioProcessor->getParameters();
for (auto p : params)
{
if (auto* param = dynamic_cast<AudioProcessorParameterWithID*> (p))
{
if (param->paramID == paramId)
return param;
}
}
}
return nullptr;
}
//==============================================================================
float getParameterValue (const String& paramId)
{
if (auto* param = getParameter (paramId))
return param->getValue();
return 0.0f;
}
void setParameterValue (const String& paramId, float value)
{
if (auto* param = getParameter (paramId))
param->setValueNotifyingHost (value);
}
//==============================================================================
MaterialLookAndFeel materialLookAndFeel;
//==============================================================================
TextButton recordButton { "Record" };
Slider roomSizeSlider;
DrawablePath proAudioIcon;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AUv3SynthEditor)
};
//==============================================================================
class AUv3SynthProcessor final : public AudioProcessor
{
public:
AUv3SynthProcessor()
: AudioProcessor (BusesProperties().withOutput ("Output", AudioChannelSet::stereo(), true)),
currentRecording (1, 1), currentProgram (0)
{
// initialize parameters
addParameter (isRecordingParam = new AudioParameterBool ({ "isRecording", 1 }, "Is Recording", false));
addParameter (roomSizeParam = new AudioParameterFloat ({ "roomSize", 1 }, "Room Size", 0.0f, 1.0f, 0.5f));
formatManager.registerBasicFormats();
for (auto i = 0; i < maxNumVoices; ++i)
synth.addVoice (new SamplerVoice());
loadNewSample (createAssetInputStream ("singing.ogg"), "ogg");
}
//==============================================================================
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
return (layouts.getMainOutputChannels() <= 2);
}
void prepareToPlay (double sampleRate, int estimatedMaxSizeOfBuffer) override
{
ignoreUnused (estimatedMaxSizeOfBuffer);
lastSampleRate = sampleRate;
currentRecording.setSize (1, static_cast<int> (std::ceil (maxDurationOfRecording * lastSampleRate)));
samplesRecorded = 0;
synth.setCurrentPlaybackSampleRate (lastSampleRate);
reverb.setSampleRate (lastSampleRate);
}
void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
{
Reverb::Parameters reverbParameters;
reverbParameters.roomSize = roomSizeParam->get();
reverb.setParameters (reverbParameters);
synth.renderNextBlock (buffer, midiMessages, 0, buffer.getNumSamples());
if (getMainBusNumOutputChannels() == 1)
reverb.processMono (buffer.getWritePointer (0), buffer.getNumSamples());
else if (getMainBusNumOutputChannels() == 2)
reverb.processStereo (buffer.getWritePointer (0), buffer.getWritePointer (1), buffer.getNumSamples());
}
using AudioProcessor::processBlock;
//==============================================================================
void releaseResources() override { currentRecording.setSize (1, 1); }
//==============================================================================
bool acceptsMidi() const override { return true; }
bool producesMidi() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
//==============================================================================
AudioProcessorEditor* createEditor() override { return new AUv3SynthEditor (*this); }
bool hasEditor() const override { return true; }
//==============================================================================
const String getName() const override { return "AUv3 Synth"; }
int getNumPrograms() override { return 4; }
int getCurrentProgram() override { return currentProgram; }
void setCurrentProgram (int index) override { currentProgram = index; }
const String getProgramName (int index) override
{
switch (index)
{
case 0: return "Piano";
case 1: return "Singing";
case 2: return "Pinched Balloon";
case 3: return "Gazeebo";
default: break;
}
return "<Unknown>";
}
//==============================================================================
void changeProgramName (int /*index*/, const String& /*name*/) override {}
//==============================================================================
void getStateInformation (MemoryBlock& destData) override
{
MemoryOutputStream stream (destData, true);
stream.writeFloat (*isRecordingParam);
stream.writeFloat (*roomSizeParam);
}
void setStateInformation (const void* data, int sizeInBytes) override
{
MemoryInputStream stream (data, static_cast<size_t> (sizeInBytes), false);
isRecordingParam->setValueNotifyingHost (stream.readFloat());
roomSizeParam->setValueNotifyingHost (stream.readFloat());
}
private:
//==============================================================================
void loadNewSampleBinary (const void* data, int dataSize, const char* format)
{
auto soundBuffer = std::make_unique<MemoryInputStream> (data, static_cast<std::size_t> (dataSize), false);
loadNewSample (std::move (soundBuffer), format);
}
void loadNewSample (std::unique_ptr<InputStream> soundBuffer, const char* format)
{
std::unique_ptr<AudioFormatReader> formatReader (formatManager.findFormatForFileExtension (format)->createReaderFor (soundBuffer.release(), true));
BigInteger midiNotes;
midiNotes.setRange (0, 126, true);
SynthesiserSound::Ptr newSound = new SamplerSound ("Voice", *formatReader, midiNotes, 0x40, 0.0, 0.0, 10.0);
synth.removeSound (0);
sound = newSound;
synth.addSound (sound);
}
void swapSamples()
{
MemoryBlock mb;
auto* stream = new MemoryOutputStream (mb, true);
{
std::unique_ptr<AudioFormatWriter> writer (formatManager.findFormatForFileExtension ("wav")->createWriterFor (stream, lastSampleRate, 1, 16,
StringPairArray(), 0));
writer->writeFromAudioSampleBuffer (currentRecording, 0, currentRecording.getNumSamples());
writer->flush();
stream->flush();
}
loadNewSampleBinary (mb.getData(), static_cast<int> (mb.getSize()), "wav");
}
//==============================================================================
static constexpr int maxNumVoices = 5;
static constexpr double maxDurationOfRecording = 1.0;
//==============================================================================
AudioFormatManager formatManager;
int samplesRecorded;
double lastSampleRate;
AudioBuffer<float> currentRecording;
Reverb reverb;
Synthesiser synth;
SynthesiserSound::Ptr sound;
AudioParameterBool* isRecordingParam;
AudioParameterFloat* roomSizeParam;
int currentProgram;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AUv3SynthProcessor)
};
@@ -0,0 +1,166 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2022 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: ArpeggiatorPlugin
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Arpeggiator audio plugin.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2022
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: Arpeggiator
useLocalCopy: 1
pluginCharacteristics: pluginWantsMidiIn, pluginProducesMidiOut, pluginIsMidiEffectPlugin
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class Arpeggiator final : public AudioProcessor
{
public:
//==============================================================================
Arpeggiator()
: AudioProcessor (BusesProperties()) // add no audio buses at all
{
addParameter (speed = new AudioParameterFloat ({ "speed", 1 }, "Arpeggiator Speed", 0.0, 1.0, 0.5));
}
//==============================================================================
void prepareToPlay (double sampleRate, int samplesPerBlock) override
{
ignoreUnused (samplesPerBlock);
notes.clear();
currentNote = 0;
lastNoteValue = -1;
time = 0;
rate = static_cast<float> (sampleRate);
}
void releaseResources() override {}
void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midi) override
{
// A pure MIDI plugin shouldn't be provided any audio data
jassert (buffer.getNumChannels() == 0);
// however we use the buffer to get timing information
auto numSamples = buffer.getNumSamples();
// get note duration
auto noteDuration = static_cast<int> (std::ceil (rate * 0.25f * (0.1f + (1.0f - (*speed)))));
for (const auto metadata : midi)
{
const auto msg = metadata.getMessage();
if (msg.isNoteOn()) notes.add (msg.getNoteNumber());
else if (msg.isNoteOff()) notes.removeValue (msg.getNoteNumber());
}
midi.clear();
if ((time + numSamples) >= noteDuration)
{
auto offset = jmax (0, jmin ((int) (noteDuration - time), numSamples - 1));
if (lastNoteValue > 0)
{
midi.addEvent (MidiMessage::noteOff (1, lastNoteValue), offset);
lastNoteValue = -1;
}
if (notes.size() > 0)
{
currentNote = (currentNote + 1) % notes.size();
lastNoteValue = notes[currentNote];
midi.addEvent (MidiMessage::noteOn (1, lastNoteValue, (uint8) 127), offset);
}
}
time = (time + numSamples) % noteDuration;
}
using AudioProcessor::processBlock;
//==============================================================================
bool isMidiEffect() const override { return true; }
//==============================================================================
AudioProcessorEditor* createEditor() override { return new GenericAudioProcessorEditor (*this); }
bool hasEditor() const override { return true; }
//==============================================================================
const String getName() const override { return "Arpeggiator"; }
bool acceptsMidi() const override { return true; }
bool producesMidi() const override { return true; }
double getTailLengthSeconds() const override { return 0; }
//==============================================================================
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return "None"; }
void changeProgramName (int, const String&) override {}
//==============================================================================
void getStateInformation (MemoryBlock& destData) override
{
MemoryOutputStream (destData, true).writeFloat (*speed);
}
void setStateInformation (const void* data, int sizeInBytes) override
{
speed->setValueNotifyingHost (MemoryInputStream (data, static_cast<size_t> (sizeInBytes), false).readFloat());
}
private:
//==============================================================================
AudioParameterFloat* speed;
int currentNote, lastNoteValue;
int time;
float rate;
SortedSet<int> notes;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Arpeggiator)
};
+665
View File
@@ -0,0 +1,665 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2022 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: AudioPluginDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Synthesiser audio plugin.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2017, vs2022, linux_make, xcode_iphone, androidstudio
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: JuceDemoPluginAudioProcessor
useLocalCopy: 1
pluginCharacteristics: pluginIsSynth, pluginWantsMidiIn, pluginProducesMidiOut,
pluginEditorRequiresKeys
extraPluginFormats: AUv3
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
/** A demo synth sound that's just a basic sine wave.. */
class SineWaveSound final : public SynthesiserSound
{
public:
SineWaveSound() {}
bool appliesToNote (int /*midiNoteNumber*/) override { return true; }
bool appliesToChannel (int /*midiChannel*/) override { return true; }
};
//==============================================================================
/** A simple demo synth voice that just plays a sine wave.. */
class SineWaveVoice final : public SynthesiserVoice
{
public:
SineWaveVoice() {}
bool canPlaySound (SynthesiserSound* sound) override
{
return dynamic_cast<SineWaveSound*> (sound) != nullptr;
}
void startNote (int midiNoteNumber, float velocity,
SynthesiserSound* /*sound*/,
int /*currentPitchWheelPosition*/) override
{
currentAngle = 0.0;
level = velocity * 0.15;
tailOff = 0.0;
auto cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
auto cyclesPerSample = cyclesPerSecond / getSampleRate();
angleDelta = cyclesPerSample * MathConstants<double>::twoPi;
}
void stopNote (float /*velocity*/, bool allowTailOff) override
{
if (allowTailOff)
{
// start a tail-off by setting this flag. The render callback will pick up on
// this and do a fade out, calling clearCurrentNote() when it's finished.
if (approximatelyEqual (tailOff, 0.0)) // we only need to begin a tail-off if it's not already doing so - the
// stopNote method could be called more than once.
tailOff = 1.0;
}
else
{
// we're being told to stop playing immediately, so reset everything..
clearCurrentNote();
angleDelta = 0.0;
}
}
void pitchWheelMoved (int /*newValue*/) override
{
// not implemented for the purposes of this demo!
}
void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override
{
// not implemented for the purposes of this demo!
}
void renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
{
if (! approximatelyEqual (angleDelta, 0.0))
{
if (tailOff > 0.0)
{
while (--numSamples >= 0)
{
auto currentSample = (float) (sin (currentAngle) * level * tailOff);
for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
outputBuffer.addSample (i, startSample, currentSample);
currentAngle += angleDelta;
++startSample;
tailOff *= 0.99;
if (tailOff <= 0.005)
{
// tells the synth that this voice has stopped
clearCurrentNote();
angleDelta = 0.0;
break;
}
}
}
else
{
while (--numSamples >= 0)
{
auto currentSample = (float) (sin (currentAngle) * level);
for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
outputBuffer.addSample (i, startSample, currentSample);
currentAngle += angleDelta;
++startSample;
}
}
}
}
using SynthesiserVoice::renderNextBlock;
private:
double currentAngle = 0.0;
double angleDelta = 0.0;
double level = 0.0;
double tailOff = 0.0;
};
//==============================================================================
/** As the name suggest, this class does the actual audio processing. */
class JuceDemoPluginAudioProcessor final : public AudioProcessor
{
public:
//==============================================================================
JuceDemoPluginAudioProcessor()
: AudioProcessor (getBusesProperties()),
state (*this, nullptr, "state",
{ std::make_unique<AudioParameterFloat> (ParameterID { "gain", 1 }, "Gain", NormalisableRange<float> (0.0f, 1.0f), 0.9f),
std::make_unique<AudioParameterFloat> (ParameterID { "delay", 1 }, "Delay Feedback", NormalisableRange<float> (0.0f, 1.0f), 0.5f) })
{
// Add a sub-tree to store the state of our UI
state.state.addChild ({ "uiState", { { "width", 400 }, { "height", 200 } }, {} }, -1, nullptr);
initialiseSynth();
}
~JuceDemoPluginAudioProcessor() override = default;
//==============================================================================
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
// Only mono/stereo and input/output must have same layout
const auto& mainOutput = layouts.getMainOutputChannelSet();
const auto& mainInput = layouts.getMainInputChannelSet();
// input and output layout must either be the same or the input must be disabled altogether
if (! mainInput.isDisabled() && mainInput != mainOutput)
return false;
// only allow stereo and mono
if (mainOutput.size() > 2)
return false;
return true;
}
void prepareToPlay (double newSampleRate, int /*samplesPerBlock*/) override
{
// Use this method as the place to do any pre-playback
// initialisation that you need..
synth.setCurrentPlaybackSampleRate (newSampleRate);
keyboardState.reset();
if (isUsingDoublePrecision())
{
delayBufferDouble.setSize (2, 12000);
delayBufferFloat .setSize (1, 1);
}
else
{
delayBufferFloat .setSize (2, 12000);
delayBufferDouble.setSize (1, 1);
}
reset();
}
void releaseResources() override
{
// When playback stops, you can use this as an opportunity to free up any
// spare memory, etc.
keyboardState.reset();
}
void reset() override
{
// Use this method as the place to clear any delay lines, buffers, etc, as it
// means there's been a break in the audio's continuity.
delayBufferFloat .clear();
delayBufferDouble.clear();
}
//==============================================================================
void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
{
jassert (! isUsingDoublePrecision());
process (buffer, midiMessages, delayBufferFloat);
}
void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) override
{
jassert (isUsingDoublePrecision());
process (buffer, midiMessages, delayBufferDouble);
}
//==============================================================================
bool hasEditor() const override { return true; }
AudioProcessorEditor* createEditor() override
{
return new JuceDemoPluginAudioProcessorEditor (*this);
}
//==============================================================================
const String getName() const override { return "AudioPluginDemo"; }
bool acceptsMidi() const override { return true; }
bool producesMidi() const override { return true; }
double getTailLengthSeconds() const override { return 0.0; }
//==============================================================================
int getNumPrograms() override { return 0; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return "None"; }
void changeProgramName (int, const String&) override {}
//==============================================================================
void getStateInformation (MemoryBlock& destData) override
{
// Store an xml representation of our state.
if (auto xmlState = state.copyState().createXml())
copyXmlToBinary (*xmlState, destData);
}
void setStateInformation (const void* data, int sizeInBytes) override
{
// Restore our plug-in's state from the xml representation stored in the above
// method.
if (auto xmlState = getXmlFromBinary (data, sizeInBytes))
state.replaceState (ValueTree::fromXml (*xmlState));
}
//==============================================================================
void updateTrackProperties (const TrackProperties& properties) override
{
{
const ScopedLock sl (trackPropertiesLock);
trackProperties = properties;
}
MessageManager::callAsync ([this]
{
if (auto* editor = dynamic_cast<JuceDemoPluginAudioProcessorEditor*> (getActiveEditor()))
editor->updateTrackProperties();
});
}
TrackProperties getTrackProperties() const
{
const ScopedLock sl (trackPropertiesLock);
return trackProperties;
}
class SpinLockedPosInfo
{
public:
// Wait-free, but setting new info may fail if the main thread is currently
// calling `get`. This is unlikely to matter in practice because
// we'll be calling `set` much more frequently than `get`.
void set (const AudioPlayHead::PositionInfo& newInfo)
{
const juce::SpinLock::ScopedTryLockType lock (mutex);
if (lock.isLocked())
info = newInfo;
}
AudioPlayHead::PositionInfo get() const noexcept
{
const juce::SpinLock::ScopedLockType lock (mutex);
return info;
}
private:
juce::SpinLock mutex;
AudioPlayHead::PositionInfo info;
};
//==============================================================================
// These properties are public so that our editor component can access them
// A bit of a hacky way to do it, but it's only a demo! Obviously in your own
// code you'll do this much more neatly..
// this is kept up to date with the midi messages that arrive, and the UI component
// registers with it so it can represent the incoming messages
MidiKeyboardState keyboardState;
// this keeps a copy of the last set of time info that was acquired during an audio
// callback - the UI component will read this and display it.
SpinLockedPosInfo lastPosInfo;
// Our plug-in's current state
AudioProcessorValueTreeState state;
private:
//==============================================================================
/** This is the editor component that our filter will display. */
class JuceDemoPluginAudioProcessorEditor final : public AudioProcessorEditor,
private Timer,
private Value::Listener
{
public:
JuceDemoPluginAudioProcessorEditor (JuceDemoPluginAudioProcessor& owner)
: AudioProcessorEditor (owner),
midiKeyboard (owner.keyboardState, MidiKeyboardComponent::horizontalKeyboard),
gainAttachment (owner.state, "gain", gainSlider),
delayAttachment (owner.state, "delay", delaySlider)
{
// add some sliders..
addAndMakeVisible (gainSlider);
gainSlider.setSliderStyle (Slider::Rotary);
addAndMakeVisible (delaySlider);
delaySlider.setSliderStyle (Slider::Rotary);
// add some labels for the sliders..
gainLabel.attachToComponent (&gainSlider, false);
gainLabel.setFont (Font (11.0f));
delayLabel.attachToComponent (&delaySlider, false);
delayLabel.setFont (Font (11.0f));
// add the midi keyboard component..
addAndMakeVisible (midiKeyboard);
// add a label that will display the current timecode and status..
addAndMakeVisible (timecodeDisplayLabel);
timecodeDisplayLabel.setFont (Font (Font::getDefaultMonospacedFontName(), 15.0f, Font::plain));
// set resize limits for this plug-in
setResizeLimits (400, 200, 1024, 700);
setResizable (true, owner.wrapperType != wrapperType_AudioUnitv3);
lastUIWidth .referTo (owner.state.state.getChildWithName ("uiState").getPropertyAsValue ("width", nullptr));
lastUIHeight.referTo (owner.state.state.getChildWithName ("uiState").getPropertyAsValue ("height", nullptr));
// set our component's initial size to be the last one that was stored in the filter's settings
setSize (lastUIWidth.getValue(), lastUIHeight.getValue());
lastUIWidth. addListener (this);
lastUIHeight.addListener (this);
updateTrackProperties();
// start a timer which will keep our timecode display updated
startTimerHz (30);
}
~JuceDemoPluginAudioProcessorEditor() override {}
//==============================================================================
void paint (Graphics& g) override
{
g.setColour (backgroundColour);
g.fillAll();
}
void resized() override
{
// This lays out our child components...
auto r = getLocalBounds().reduced (8);
timecodeDisplayLabel.setBounds (r.removeFromTop (26));
midiKeyboard .setBounds (r.removeFromBottom (70));
r.removeFromTop (20);
auto sliderArea = r.removeFromTop (60);
gainSlider.setBounds (sliderArea.removeFromLeft (jmin (180, sliderArea.getWidth() / 2)));
delaySlider.setBounds (sliderArea.removeFromLeft (jmin (180, sliderArea.getWidth())));
lastUIWidth = getWidth();
lastUIHeight = getHeight();
}
void timerCallback() override
{
updateTimecodeDisplay (getProcessor().lastPosInfo.get());
}
void hostMIDIControllerIsAvailable (bool controllerIsAvailable) override
{
midiKeyboard.setVisible (! controllerIsAvailable);
}
int getControlParameterIndex (Component& control) override
{
if (&control == &gainSlider)
return 0;
if (&control == &delaySlider)
return 1;
return -1;
}
void updateTrackProperties()
{
auto trackColour = getProcessor().getTrackProperties().colour;
auto& lf = getLookAndFeel();
backgroundColour = (trackColour == Colour() ? lf.findColour (ResizableWindow::backgroundColourId)
: trackColour.withAlpha (1.0f).withBrightness (0.266f));
repaint();
}
private:
MidiKeyboardComponent midiKeyboard;
Label timecodeDisplayLabel,
gainLabel { {}, "Throughput level:" },
delayLabel { {}, "Delay:" };
Slider gainSlider, delaySlider;
AudioProcessorValueTreeState::SliderAttachment gainAttachment, delayAttachment;
Colour backgroundColour;
// these are used to persist the UI's size - the values are stored along with the
// filter's other parameters, and the UI component will update them when it gets
// resized.
Value lastUIWidth, lastUIHeight;
//==============================================================================
JuceDemoPluginAudioProcessor& getProcessor() const
{
return static_cast<JuceDemoPluginAudioProcessor&> (processor);
}
//==============================================================================
// quick-and-dirty function to format a timecode string
static String timeToTimecodeString (double seconds)
{
auto millisecs = roundToInt (seconds * 1000.0);
auto absMillisecs = std::abs (millisecs);
return String::formatted ("%02d:%02d:%02d.%03d",
millisecs / 3600000,
(absMillisecs / 60000) % 60,
(absMillisecs / 1000) % 60,
absMillisecs % 1000);
}
// quick-and-dirty function to format a bars/beats string
static String quarterNotePositionToBarsBeatsString (double quarterNotes, AudioPlayHead::TimeSignature sig)
{
if (sig.numerator == 0 || sig.denominator == 0)
return "1|1|000";
auto quarterNotesPerBar = (sig.numerator * 4 / sig.denominator);
auto beats = (fmod (quarterNotes, quarterNotesPerBar) / quarterNotesPerBar) * sig.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);
}
// Updates the text in our position label.
void updateTimecodeDisplay (const AudioPlayHead::PositionInfo& pos)
{
MemoryOutputStream displayText;
const auto sig = pos.getTimeSignature().orFallback (AudioPlayHead::TimeSignature{});
displayText << "[" << SystemStats::getJUCEVersion() << "] "
<< String (pos.getBpm().orFallback (120.0), 2) << " bpm, "
<< sig.numerator << '/' << sig.denominator
<< " - " << timeToTimecodeString (pos.getTimeInSeconds().orFallback (0.0))
<< " - " << quarterNotePositionToBarsBeatsString (pos.getPpqPosition().orFallback (0.0), sig);
if (pos.getIsRecording())
displayText << " (recording)";
else if (pos.getIsPlaying())
displayText << " (playing)";
timecodeDisplayLabel.setText (displayText.toString(), dontSendNotification);
}
// called when the stored window size changes
void valueChanged (Value&) override
{
setSize (lastUIWidth.getValue(), lastUIHeight.getValue());
}
};
//==============================================================================
template <typename FloatType>
void process (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages, AudioBuffer<FloatType>& delayBuffer)
{
auto gainParamValue = state.getParameter ("gain") ->getValue();
auto delayParamValue = state.getParameter ("delay")->getValue();
auto numSamples = buffer.getNumSamples();
// In case we have more outputs than inputs, we'll clear any output
// channels that didn't contain input data, (because these aren't
// guaranteed to be empty - they may contain garbage).
for (auto i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i)
buffer.clear (i, 0, numSamples);
// Now pass any incoming midi messages to our keyboard state object, and let it
// add messages to the buffer if the user is clicking on the on-screen keys
keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true);
// and now get our synth to process these midi events and generate its output.
synth.renderNextBlock (buffer, midiMessages, 0, numSamples);
// Apply our delay effect to the new output..
applyDelay (buffer, delayBuffer, delayParamValue);
// Apply our gain change to the outgoing data..
applyGain (buffer, delayBuffer, gainParamValue);
// Now ask the host for the current time so we can store it to be displayed later...
updateCurrentTimeInfoFromHost();
}
template <typename FloatType>
void applyGain (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer, float gainLevel)
{
ignoreUnused (delayBuffer);
for (auto channel = 0; channel < getTotalNumOutputChannels(); ++channel)
buffer.applyGain (channel, 0, buffer.getNumSamples(), gainLevel);
}
template <typename FloatType>
void applyDelay (AudioBuffer<FloatType>& buffer, AudioBuffer<FloatType>& delayBuffer, float delayLevel)
{
auto numSamples = buffer.getNumSamples();
auto delayPos = 0;
for (auto channel = 0; channel < getTotalNumOutputChannels(); ++channel)
{
auto channelData = buffer.getWritePointer (channel);
auto delayData = delayBuffer.getWritePointer (jmin (channel, delayBuffer.getNumChannels() - 1));
delayPos = delayPosition;
for (auto i = 0; i < numSamples; ++i)
{
auto in = channelData[i];
channelData[i] += delayData[delayPos];
delayData[delayPos] = (delayData[delayPos] + in) * delayLevel;
if (++delayPos >= delayBuffer.getNumSamples())
delayPos = 0;
}
}
delayPosition = delayPos;
}
AudioBuffer<float> delayBufferFloat;
AudioBuffer<double> delayBufferDouble;
int delayPosition = 0;
Synthesiser synth;
CriticalSection trackPropertiesLock;
TrackProperties trackProperties;
void initialiseSynth()
{
auto numVoices = 8;
// Add some voices...
for (auto i = 0; i < numVoices; ++i)
synth.addVoice (new SineWaveVoice());
// ..and give the synth a sound to play
synth.addSound (new SineWaveSound());
}
void updateCurrentTimeInfoFromHost()
{
const auto newInfo = [&]
{
if (auto* ph = getPlayHead())
if (auto result = ph->getPosition())
return *result;
// If the host fails to provide the current time, we'll just use default values
return AudioPlayHead::PositionInfo{};
}();
lastPosInfo.set (newInfo);
}
static BusesProperties getBusesProperties()
{
return BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true)
.withOutput ("Output", AudioChannelSet::stereo(), true);
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceDemoPluginAudioProcessor)
};
+24
View File
@@ -0,0 +1,24 @@
# ==============================================================================
#
# This file is part of the JUCE library.
# Copyright (c) 2022 - Raw Material Software Limited
#
# JUCE is an open source library subject to commercial or open-source
# licensing.
#
# By using JUCE, you agree to the terms of both the JUCE 7 End-User License
# Agreement and JUCE Privacy Policy.
#
# End User License Agreement: www.juce.com/juce-7-licence
# Privacy Policy: www.juce.com/juce-privacy-policy
#
# Or: You may also use this code under the terms of the GPL v3 (see
# www.gnu.org/licenses).
#
# JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
# EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
# DISCLAIMED.
#
# ==============================================================================
_juce_add_pips()
File diff suppressed because it is too large Load Diff
+122
View File
@@ -0,0 +1,122 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2022 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: GainPlugin
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Gain audio plugin.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2022
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: GainProcessor
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class GainProcessor final : public AudioProcessor
{
public:
//==============================================================================
GainProcessor()
: AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo())
.withOutput ("Output", AudioChannelSet::stereo()))
{
addParameter (gain = new AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f));
}
//==============================================================================
void prepareToPlay (double, int) override {}
void releaseResources() override {}
void processBlock (AudioBuffer<float>& buffer, MidiBuffer&) override
{
buffer.applyGain (*gain);
}
void processBlock (AudioBuffer<double>& buffer, MidiBuffer&) override
{
buffer.applyGain ((float) *gain);
}
//==============================================================================
AudioProcessorEditor* createEditor() override { return new GenericAudioProcessorEditor (*this); }
bool hasEditor() const override { return true; }
//==============================================================================
const String getName() const override { return "Gain PlugIn"; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
double getTailLengthSeconds() const override { return 0; }
//==============================================================================
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return "None"; }
void changeProgramName (int, const String&) override {}
//==============================================================================
void getStateInformation (MemoryBlock& destData) override
{
MemoryOutputStream (destData, true).writeFloat (*gain);
}
void setStateInformation (const void* data, int sizeInBytes) override
{
gain->setValueNotifyingHost (MemoryInputStream (data, static_cast<size_t> (sizeInBytes), false).readFloat());
}
//==============================================================================
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
const auto& mainInLayout = layouts.getChannelSet (true, 0);
const auto& mainOutLayout = layouts.getChannelSet (false, 0);
return (mainInLayout == mainOutLayout && (! mainInLayout.isDisabled()));
}
private:
//==============================================================================
AudioParameterFloat* gain;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GainProcessor)
};
+584
View File
@@ -0,0 +1,584 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2022 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: HostPluginDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Plugin that can host other plugins
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2022, linux_make
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
JUCE_PLUGINHOST_LV2=1
JUCE_PLUGINHOST_VST3=1
JUCE_PLUGINHOST_VST=0
JUCE_PLUGINHOST_AU=1
type: AudioProcessor
mainClass: HostAudioProcessor
useLocalCopy: 1
pluginCharacteristics: pluginIsSynth, pluginWantsMidiIn, pluginProducesMidiOut,
pluginEditorRequiresKeys
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
enum class EditorStyle { thisWindow, newWindow };
class HostAudioProcessorImpl : public AudioProcessor,
private ChangeListener
{
public:
HostAudioProcessorImpl()
: AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true)
.withOutput ("Output", AudioChannelSet::stereo(), true))
{
appProperties.setStorageParameters ([&]
{
PropertiesFile::Options opt;
opt.applicationName = getName();
opt.commonToAllUsers = false;
opt.doNotSave = false;
opt.filenameSuffix = ".props";
opt.ignoreCaseOfKeyNames = false;
opt.storageFormat = PropertiesFile::StorageFormat::storeAsXML;
opt.osxLibrarySubFolder = "Application Support";
return opt;
}());
pluginFormatManager.addDefaultFormats();
if (auto savedPluginList = appProperties.getUserSettings()->getXmlValue ("pluginList"))
pluginList.recreateFromXml (*savedPluginList);
MessageManagerLock lock;
pluginList.addChangeListener (this);
}
bool isBusesLayoutSupported (const BusesLayout& layouts) const final
{
const auto& mainOutput = layouts.getMainOutputChannelSet();
const auto& mainInput = layouts.getMainInputChannelSet();
if (! mainInput.isDisabled() && mainInput != mainOutput)
return false;
if (mainOutput.size() > 2)
return false;
return true;
}
void prepareToPlay (double sr, int bs) final
{
const ScopedLock sl (innerMutex);
active = true;
if (inner != nullptr)
{
inner->setRateAndBufferSizeDetails (sr, bs);
inner->prepareToPlay (sr, bs);
}
}
void releaseResources() final
{
const ScopedLock sl (innerMutex);
active = false;
if (inner != nullptr)
inner->releaseResources();
}
void reset() final
{
const ScopedLock sl (innerMutex);
if (inner != nullptr)
inner->reset();
}
// In this example, we don't actually pass any audio through the inner processor.
// In a 'real' plugin, we'd need to add some synchronisation to ensure that the inner
// plugin instance was never modified (deleted, replaced etc.) during a call to processBlock.
void processBlock (AudioBuffer<float>&, MidiBuffer&) final
{
jassert (! isUsingDoublePrecision());
}
void processBlock (AudioBuffer<double>&, MidiBuffer&) final
{
jassert (isUsingDoublePrecision());
}
bool hasEditor() const override { return false; }
AudioProcessorEditor* createEditor() override { return nullptr; }
const String getName() const final { return "HostPluginDemo"; }
bool acceptsMidi() const final { return true; }
bool producesMidi() const final { return true; }
double getTailLengthSeconds() const final { return 0.0; }
int getNumPrograms() final { return 0; }
int getCurrentProgram() final { return 0; }
void setCurrentProgram (int) final {}
const String getProgramName (int) final { return "None"; }
void changeProgramName (int, const String&) final {}
void getStateInformation (MemoryBlock& destData) final
{
const ScopedLock sl (innerMutex);
XmlElement xml ("state");
if (inner != nullptr)
{
xml.setAttribute (editorStyleTag, (int) editorStyle);
xml.addChildElement (inner->getPluginDescription().createXml().release());
xml.addChildElement ([this]
{
MemoryBlock innerState;
inner->getStateInformation (innerState);
auto stateNode = std::make_unique<XmlElement> (innerStateTag);
stateNode->addTextElement (innerState.toBase64Encoding());
return stateNode.release();
}());
}
const auto text = xml.toString();
destData.replaceAll (text.toRawUTF8(), text.getNumBytesAsUTF8());
}
void setStateInformation (const void* data, int sizeInBytes) final
{
const ScopedLock sl (innerMutex);
auto xml = XmlDocument::parse (String (CharPointer_UTF8 (static_cast<const char*> (data)), (size_t) sizeInBytes));
if (auto* pluginNode = xml->getChildByName ("PLUGIN"))
{
PluginDescription pd;
pd.loadFromXml (*pluginNode);
MemoryBlock innerState;
innerState.fromBase64Encoding (xml->getChildElementAllSubText (innerStateTag, {}));
setNewPlugin (pd,
(EditorStyle) xml->getIntAttribute (editorStyleTag, 0),
innerState);
}
}
void setNewPlugin (const PluginDescription& pd, EditorStyle where, const MemoryBlock& mb = {})
{
const ScopedLock sl (innerMutex);
const auto callback = [this, where, mb] (std::unique_ptr<AudioPluginInstance> instance, const String& error)
{
if (error.isNotEmpty())
{
auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon,
"Plugin Load Failed",
error);
messageBox = AlertWindow::showScopedAsync (options, nullptr);
return;
}
inner = std::move (instance);
editorStyle = where;
if (inner != nullptr && ! mb.isEmpty())
inner->setStateInformation (mb.getData(), (int) mb.getSize());
// In a 'real' plugin, we'd also need to set the bus configuration of the inner plugin.
// One possibility would be to match the bus configuration of the wrapper plugin, but
// the inner plugin isn't guaranteed to support the same layout. Alternatively, we
// could try to apply a reasonably similar layout, and maintain a mapping between the
// inner/outer channel layouts.
//
// In any case, it is essential that the inner plugin is told about the bus
// configuration that will be used. The AudioBuffer passed to the inner plugin must also
// exactly match this layout.
if (active)
{
inner->setRateAndBufferSizeDetails (getSampleRate(), getBlockSize());
inner->prepareToPlay (getSampleRate(), getBlockSize());
}
NullCheckedInvocation::invoke (pluginChanged);
};
pluginFormatManager.createPluginInstanceAsync (pd, getSampleRate(), getBlockSize(), callback);
}
void clearPlugin()
{
const ScopedLock sl (innerMutex);
inner = nullptr;
NullCheckedInvocation::invoke (pluginChanged);
}
bool isPluginLoaded() const
{
const ScopedLock sl (innerMutex);
return inner != nullptr;
}
std::unique_ptr<AudioProcessorEditor> createInnerEditor() const
{
const ScopedLock sl (innerMutex);
return rawToUniquePtr (inner->hasEditor() ? inner->createEditorIfNeeded() : nullptr);
}
EditorStyle getEditorStyle() const noexcept { return editorStyle; }
ApplicationProperties appProperties;
AudioPluginFormatManager pluginFormatManager;
KnownPluginList pluginList;
std::function<void()> pluginChanged;
private:
CriticalSection innerMutex;
std::unique_ptr<AudioPluginInstance> inner;
EditorStyle editorStyle = EditorStyle{};
bool active = false;
ScopedMessageBox messageBox;
static constexpr const char* innerStateTag = "inner_state";
static constexpr const char* editorStyleTag = "editor_style";
void changeListenerCallback (ChangeBroadcaster* source) final
{
if (source != &pluginList)
return;
if (auto savedPluginList = pluginList.createXml())
{
appProperties.getUserSettings()->setValue ("pluginList", savedPluginList.get());
appProperties.saveIfNeeded();
}
}
};
//==============================================================================
constexpr auto margin = 10;
static void doLayout (Component* main, Component& bottom, int bottomHeight, Rectangle<int> bounds)
{
Grid grid;
grid.setGap (Grid::Px { margin });
grid.templateColumns = { Grid::TrackInfo { Grid::Fr { 1 } } };
grid.templateRows = { Grid::TrackInfo { Grid::Fr { 1 } },
Grid::TrackInfo { Grid::Px { bottomHeight }} };
grid.items = { GridItem { main }, GridItem { bottom }.withMargin ({ 0, margin, margin, margin }) };
grid.performLayout (bounds);
}
class PluginLoaderComponent final : public Component
{
public:
template <typename Callback>
PluginLoaderComponent (AudioPluginFormatManager& manager,
KnownPluginList& list,
Callback&& callback)
: pluginListComponent (manager, list, {}, {})
{
pluginListComponent.getTableListBox().setMultipleSelectionEnabled (false);
addAndMakeVisible (pluginListComponent);
addAndMakeVisible (buttons);
const auto getCallback = [this, &list, cb = std::forward<Callback> (callback)] (EditorStyle style)
{
return [this, &list, cb, style]
{
const auto index = pluginListComponent.getTableListBox().getSelectedRow();
const auto& types = list.getTypes();
if (isPositiveAndBelow (index, types.size()))
NullCheckedInvocation::invoke (cb, types.getReference (index), style);
};
};
buttons.thisWindowButton.onClick = getCallback (EditorStyle::thisWindow);
buttons.newWindowButton .onClick = getCallback (EditorStyle::newWindow);
}
void resized() override
{
doLayout (&pluginListComponent, buttons, 80, getLocalBounds());
}
private:
struct Buttons final : public Component
{
Buttons()
{
label.setJustificationType (Justification::centred);
addAndMakeVisible (label);
addAndMakeVisible (thisWindowButton);
addAndMakeVisible (newWindowButton);
}
void resized() override
{
Grid vertical;
vertical.autoFlow = Grid::AutoFlow::row;
vertical.setGap (Grid::Px { margin });
vertical.autoRows = vertical.autoColumns = Grid::TrackInfo { Grid::Fr { 1 } };
vertical.items.insertMultiple (0, GridItem{}, 2);
vertical.performLayout (getLocalBounds());
label.setBounds (vertical.items[0].currentBounds.toNearestInt());
Grid grid;
grid.autoFlow = Grid::AutoFlow::column;
grid.setGap (Grid::Px { margin });
grid.autoRows = grid.autoColumns = Grid::TrackInfo { Grid::Fr { 1 } };
grid.items = { GridItem { thisWindowButton },
GridItem { newWindowButton } };
grid.performLayout (vertical.items[1].currentBounds.toNearestInt());
}
Label label { "", "Select a plugin from the list, then display it using the buttons below." };
TextButton thisWindowButton { "Open In This Window" };
TextButton newWindowButton { "Open In New Window" };
};
PluginListComponent pluginListComponent;
Buttons buttons;
};
//==============================================================================
class PluginEditorComponent final : public Component
{
public:
template <typename Callback>
PluginEditorComponent (std::unique_ptr<AudioProcessorEditor> editorIn, Callback&& onClose)
: editor (std::move (editorIn))
{
addAndMakeVisible (editor.get());
addAndMakeVisible (closeButton);
childBoundsChanged (editor.get());
closeButton.onClick = std::forward<Callback> (onClose);
}
void setScaleFactor (float scale)
{
if (editor != nullptr)
editor->setScaleFactor (scale);
}
void resized() override
{
doLayout (editor.get(), closeButton, buttonHeight, getLocalBounds());
}
void childBoundsChanged (Component* child) override
{
if (child != editor.get())
return;
const auto size = editor != nullptr ? editor->getLocalBounds()
: Rectangle<int>();
setSize (size.getWidth(), margin + buttonHeight + size.getHeight());
}
private:
static constexpr auto buttonHeight = 40;
std::unique_ptr<AudioProcessorEditor> editor;
TextButton closeButton { "Close Plugin" };
};
//==============================================================================
class ScaledDocumentWindow final : public DocumentWindow
{
public:
ScaledDocumentWindow (Colour bg, float scale)
: DocumentWindow ("Editor", bg, 0), desktopScale (scale) {}
float getDesktopScaleFactor() const override { return Desktop::getInstance().getGlobalScaleFactor() * desktopScale; }
private:
float desktopScale = 1.0f;
};
//==============================================================================
class HostAudioProcessorEditor final : public AudioProcessorEditor
{
public:
explicit HostAudioProcessorEditor (HostAudioProcessorImpl& owner)
: AudioProcessorEditor (owner),
hostProcessor (owner),
loader (owner.pluginFormatManager,
owner.pluginList,
[&owner] (const PluginDescription& pd,
EditorStyle editorStyle)
{
owner.setNewPlugin (pd, editorStyle);
}),
scopedCallback (owner.pluginChanged, [this] { pluginChanged(); })
{
setSize (500, 500);
setResizable (false, false);
addAndMakeVisible (closeButton);
addAndMakeVisible (loader);
hostProcessor.pluginChanged();
closeButton.onClick = [this] { clearPlugin(); };
}
void paint (Graphics& g) override
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker());
}
void resized() override
{
closeButton.setBounds (getLocalBounds().withSizeKeepingCentre (200, buttonHeight));
loader.setBounds (getLocalBounds());
}
void childBoundsChanged (Component* child) override
{
if (child != editor.get())
return;
const auto size = editor != nullptr ? editor->getLocalBounds()
: Rectangle<int>();
setSize (size.getWidth(), size.getHeight());
}
void setScaleFactor (float scale) override
{
currentScaleFactor = scale;
AudioProcessorEditor::setScaleFactor (scale);
[[maybe_unused]] const auto posted = MessageManager::callAsync ([ref = SafePointer<HostAudioProcessorEditor> (this), scale]
{
if (auto* r = ref.getComponent())
if (auto* e = r->currentEditorComponent)
e->setScaleFactor (scale);
});
jassert (posted);
}
private:
void pluginChanged()
{
loader.setVisible (! hostProcessor.isPluginLoaded());
closeButton.setVisible (hostProcessor.isPluginLoaded());
if (hostProcessor.isPluginLoaded())
{
auto editorComponent = std::make_unique<PluginEditorComponent> (hostProcessor.createInnerEditor(), [this]
{
[[maybe_unused]] const auto posted = MessageManager::callAsync ([this] { clearPlugin(); });
jassert (posted);
});
editorComponent->setScaleFactor (currentScaleFactor);
currentEditorComponent = editorComponent.get();
editor = [&]() -> std::unique_ptr<Component>
{
switch (hostProcessor.getEditorStyle())
{
case EditorStyle::thisWindow:
addAndMakeVisible (editorComponent.get());
setSize (editorComponent->getWidth(), editorComponent->getHeight());
return std::move (editorComponent);
case EditorStyle::newWindow:
const auto bg = getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker();
auto window = std::make_unique<ScaledDocumentWindow> (bg, currentScaleFactor);
window->setAlwaysOnTop (true);
window->setContentOwned (editorComponent.release(), true);
window->centreAroundComponent (this, window->getWidth(), window->getHeight());
window->setVisible (true);
return window;
}
jassertfalse;
return nullptr;
}();
}
else
{
editor = nullptr;
setSize (500, 500);
}
}
void clearPlugin()
{
currentEditorComponent = nullptr;
editor = nullptr;
hostProcessor.clearPlugin();
}
static constexpr auto buttonHeight = 30;
HostAudioProcessorImpl& hostProcessor;
PluginLoaderComponent loader;
std::unique_ptr<Component> editor;
PluginEditorComponent* currentEditorComponent = nullptr;
ScopedValueSetter<std::function<void()>> scopedCallback;
TextButton closeButton { "Close Plugin" };
float currentScaleFactor = 1.0f;
};
//==============================================================================
class HostAudioProcessor final : public HostAudioProcessorImpl
{
public:
bool hasEditor() const override { return true; }
AudioProcessorEditor* createEditor() override { return new HostAudioProcessorEditor (*this); }
};
+351
View File
@@ -0,0 +1,351 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2022 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: MIDILogger
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Logs incoming MIDI messages.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2022, linux_make
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: MidiLoggerPluginDemoProcessor
useLocalCopy: 1
pluginCharacteristics: pluginWantsMidiIn, pluginProducesMidiOut, pluginIsMidiEffectPlugin
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include <iterator>
class MidiQueue
{
public:
void push (const MidiBuffer& buffer)
{
for (const auto metadata : buffer)
fifo.write (1).forEach ([&] (int dest) { messages[(size_t) dest] = metadata.getMessage(); });
}
template <typename OutputIt>
void pop (OutputIt out)
{
fifo.read (fifo.getNumReady()).forEach ([&] (int source) { *out++ = messages[(size_t) source]; });
}
private:
static constexpr auto queueSize = 1 << 14;
AbstractFifo fifo { queueSize };
std::vector<MidiMessage> messages = std::vector<MidiMessage> (queueSize);
};
// Stores the last N messages. Safe to access from the message thread only.
class MidiListModel
{
public:
template <typename It>
void addMessages (It begin, It end)
{
if (begin == end)
return;
const auto numNewMessages = (int) std::distance (begin, end);
const auto numToAdd = juce::jmin (numToStore, numNewMessages);
const auto numToRemove = jmax (0, (int) messages.size() + numToAdd - numToStore);
messages.erase (messages.begin(), std::next (messages.begin(), numToRemove));
messages.insert (messages.end(), std::prev (end, numToAdd), end);
NullCheckedInvocation::invoke (onChange);
}
void clear()
{
messages.clear();
NullCheckedInvocation::invoke (onChange);
}
const MidiMessage& operator[] (size_t ind) const { return messages[ind]; }
size_t size() const { return messages.size(); }
std::function<void()> onChange;
private:
static constexpr auto numToStore = 1000;
std::vector<MidiMessage> messages;
};
//==============================================================================
class MidiTable final : public Component,
private TableListBoxModel
{
public:
MidiTable (MidiListModel& m)
: messages (m)
{
addAndMakeVisible (table);
table.setModel (this);
table.setClickingTogglesRowSelection (false);
table.setHeader ([&]
{
auto header = std::make_unique<TableHeaderComponent>();
header->addColumn ("Message", messageColumn, 200, 30, -1, TableHeaderComponent::notSortable);
header->addColumn ("Time", timeColumn, 100, 30, -1, TableHeaderComponent::notSortable);
header->addColumn ("Channel", channelColumn, 100, 30, -1, TableHeaderComponent::notSortable);
header->addColumn ("Data", dataColumn, 200, 30, -1, TableHeaderComponent::notSortable);
return header;
}());
messages.onChange = [&] { table.updateContent(); };
}
~MidiTable() override { messages.onChange = nullptr; }
void resized() override { table.setBounds (getLocalBounds()); }
private:
enum
{
messageColumn = 1,
timeColumn,
channelColumn,
dataColumn
};
int getNumRows() override { return (int) messages.size(); }
void paintRowBackground (Graphics&, int, int, int, bool) override {}
void paintCell (Graphics&, int, int, int, int, bool) override {}
Component* refreshComponentForCell (int rowNumber,
int columnId,
bool,
Component* existingComponentToUpdate) override
{
delete existingComponentToUpdate;
const auto index = (int) messages.size() - 1 - rowNumber;
const auto message = messages[(size_t) index];
return new Label ({}, [&]
{
switch (columnId)
{
case messageColumn: return getEventString (message);
case timeColumn: return String (message.getTimeStamp());
case channelColumn: return String (message.getChannel());
case dataColumn: return getDataString (message);
default: break;
}
jassertfalse;
return String();
}());
}
static String getEventString (const MidiMessage& m)
{
if (m.isNoteOn()) return "Note on";
if (m.isNoteOff()) return "Note off";
if (m.isProgramChange()) return "Program change";
if (m.isPitchWheel()) return "Pitch wheel";
if (m.isAftertouch()) return "Aftertouch";
if (m.isChannelPressure()) return "Channel pressure";
if (m.isAllNotesOff()) return "All notes off";
if (m.isAllSoundOff()) return "All sound off";
if (m.isMetaEvent()) return "Meta event";
if (m.isController())
{
const auto* name = MidiMessage::getControllerName (m.getControllerNumber());
return "Controller " + (name == nullptr ? String (m.getControllerNumber()) : String (name));
}
return String::toHexString (m.getRawData(), m.getRawDataSize());
}
static String getDataString (const MidiMessage& m)
{
if (m.isNoteOn()) return MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3) + " Velocity " + String (m.getVelocity());
if (m.isNoteOff()) return MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3) + " Velocity " + String (m.getVelocity());
if (m.isProgramChange()) return String (m.getProgramChangeNumber());
if (m.isPitchWheel()) return String (m.getPitchWheelValue());
if (m.isAftertouch()) return MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3) + ": " + String (m.getAfterTouchValue());
if (m.isChannelPressure()) return String (m.getChannelPressureValue());
if (m.isController()) return String (m.getControllerValue());
return {};
}
MidiListModel& messages;
TableListBox table;
};
//==============================================================================
class MidiLoggerPluginDemoProcessor final : public AudioProcessor,
private Timer
{
public:
MidiLoggerPluginDemoProcessor()
: AudioProcessor (getBusesLayout())
{
state.addChild ({ "uiState", { { "width", 600 }, { "height", 300 } }, {} }, -1, nullptr);
startTimerHz (60);
}
~MidiLoggerPluginDemoProcessor() override { stopTimer(); }
void processBlock (AudioBuffer<float>& audio, MidiBuffer& midi) override { process (audio, midi); }
void processBlock (AudioBuffer<double>& audio, MidiBuffer& midi) override { process (audio, midi); }
bool isBusesLayoutSupported (const BusesLayout&) const override { return true; }
bool isMidiEffect() const override { return true; }
bool hasEditor() const override { return true; }
AudioProcessorEditor* createEditor() override { return new Editor (*this); }
const String getName() const override { return "MIDI Logger"; }
bool acceptsMidi() const override { return true; }
bool producesMidi() const override { return true; }
double getTailLengthSeconds() const override { return 0.0; }
int getNumPrograms() override { return 0; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return "None"; }
void changeProgramName (int, const String&) override {}
void prepareToPlay (double, int) override {}
void releaseResources() override {}
void getStateInformation (MemoryBlock& destData) override
{
if (auto xmlState = state.createXml())
copyXmlToBinary (*xmlState, destData);
}
void setStateInformation (const void* data, int size) override
{
if (auto xmlState = getXmlFromBinary (data, size))
state = ValueTree::fromXml (*xmlState);
}
private:
class Editor final : public AudioProcessorEditor,
private Value::Listener
{
public:
explicit Editor (MidiLoggerPluginDemoProcessor& ownerIn)
: AudioProcessorEditor (ownerIn),
owner (ownerIn),
table (owner.model)
{
addAndMakeVisible (table);
addAndMakeVisible (clearButton);
setResizable (true, true);
lastUIWidth .referTo (owner.state.getChildWithName ("uiState").getPropertyAsValue ("width", nullptr));
lastUIHeight.referTo (owner.state.getChildWithName ("uiState").getPropertyAsValue ("height", nullptr));
setSize (lastUIWidth.getValue(), lastUIHeight.getValue());
lastUIWidth. addListener (this);
lastUIHeight.addListener (this);
clearButton.onClick = [&] { owner.model.clear(); };
}
void paint (Graphics& g) override
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
void resized() override
{
auto bounds = getLocalBounds();
clearButton.setBounds (bounds.removeFromBottom (30).withSizeKeepingCentre (50, 24));
table.setBounds (bounds);
lastUIWidth = getWidth();
lastUIHeight = getHeight();
}
private:
void valueChanged (Value&) override
{
setSize (lastUIWidth.getValue(), lastUIHeight.getValue());
}
MidiLoggerPluginDemoProcessor& owner;
MidiTable table;
TextButton clearButton { "Clear" };
Value lastUIWidth, lastUIHeight;
};
void timerCallback() override
{
std::vector<MidiMessage> messages;
queue.pop (std::back_inserter (messages));
model.addMessages (messages.begin(), messages.end());
}
template <typename Element>
void process (AudioBuffer<Element>& audio, MidiBuffer& midi)
{
audio.clear();
queue.push (midi);
}
static BusesProperties getBusesLayout()
{
// Live and Cakewalk don't like to load midi-only plugins, so we add an audio output there.
const PluginHostType host;
return host.isAbletonLive() || host.isSonar()
? BusesProperties().withOutput ("out", AudioChannelSet::stereo())
: BusesProperties();
}
ValueTree state { "state" };
MidiQueue queue;
MidiListModel model; // The data to show in the UI. We keep it around in the processor so that
// the view is persistent even when the plugin UI is closed and reopened.
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiLoggerPluginDemoProcessor)
};
@@ -0,0 +1,208 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2022 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: MultiOutSynthPlugin
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Multi-out synthesiser audio plugin.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2022
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: MultiOutSynth
useLocalCopy: 1
pluginCharacteristics: pluginIsSynth, pluginWantsMidiIn
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include "../Assets/DemoUtilities.h"
//==============================================================================
class MultiOutSynth final : public AudioProcessor
{
public:
enum
{
maxMidiChannel = 16,
maxNumberOfVoices = 5
};
//==============================================================================
MultiOutSynth()
: AudioProcessor (BusesProperties()
.withOutput ("Output #1", AudioChannelSet::stereo(), true)
.withOutput ("Output #2", AudioChannelSet::stereo(), false)
.withOutput ("Output #3", AudioChannelSet::stereo(), false)
.withOutput ("Output #4", AudioChannelSet::stereo(), false)
.withOutput ("Output #5", AudioChannelSet::stereo(), false)
.withOutput ("Output #6", AudioChannelSet::stereo(), false)
.withOutput ("Output #7", AudioChannelSet::stereo(), false)
.withOutput ("Output #8", AudioChannelSet::stereo(), false)
.withOutput ("Output #9", AudioChannelSet::stereo(), false)
.withOutput ("Output #10", AudioChannelSet::stereo(), false)
.withOutput ("Output #11", AudioChannelSet::stereo(), false)
.withOutput ("Output #12", AudioChannelSet::stereo(), false)
.withOutput ("Output #13", AudioChannelSet::stereo(), false)
.withOutput ("Output #14", AudioChannelSet::stereo(), false)
.withOutput ("Output #15", AudioChannelSet::stereo(), false)
.withOutput ("Output #16", AudioChannelSet::stereo(), false))
{
// initialize other stuff (not related to buses)
formatManager.registerBasicFormats();
for (int midiChannel = 0; midiChannel < maxMidiChannel; ++midiChannel)
{
synth.add (new Synthesiser());
for (int i = 0; i < maxNumberOfVoices; ++i)
synth[midiChannel]->addVoice (new SamplerVoice());
}
loadNewSample (createAssetInputStream ("singing.ogg"), "ogg");
}
//==============================================================================
bool canAddBus (bool isInput) const override { return ! isInput; }
bool canRemoveBus (bool isInput) const override { return ! isInput; }
//==============================================================================
void prepareToPlay (double newSampleRate, int samplesPerBlock) override
{
ignoreUnused (samplesPerBlock);
for (auto* s : synth)
s->setCurrentPlaybackSampleRate (newSampleRate);
}
void releaseResources() override {}
void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiBuffer) override
{
auto busCount = getBusCount (false);
for (auto busNr = 0; busNr < busCount; ++busNr)
{
if (synth.size() <= busNr)
continue;
auto midiChannelBuffer = filterMidiMessagesForChannel (midiBuffer, busNr + 1);
auto audioBusBuffer = getBusBuffer (buffer, false, busNr);
// Voices add to the contents of the buffer. Make sure the buffer is clear before
// rendering, just in case the host left old data in the buffer.
audioBusBuffer.clear();
synth [busNr]->renderNextBlock (audioBusBuffer, midiChannelBuffer, 0, audioBusBuffer.getNumSamples());
}
}
using AudioProcessor::processBlock;
//==============================================================================
AudioProcessorEditor* createEditor() override { return new GenericAudioProcessorEditor (*this); }
bool hasEditor() const override { return true; }
//==============================================================================
const String getName() const override { return "Multi Out Synth PlugIn"; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
double getTailLengthSeconds() const override { return 0; }
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return "None"; }
void changeProgramName (int, const String&) override {}
bool isBusesLayoutSupported (const BusesLayout& layout) const override
{
const auto& outputs = layout.outputBuses;
return layout.inputBuses.isEmpty()
&& 1 <= outputs.size()
&& std::all_of (outputs.begin(), outputs.end(), [] (const auto& bus)
{
return bus.isDisabled() || bus == AudioChannelSet::stereo();
});
}
//==============================================================================
void getStateInformation (MemoryBlock&) override {}
void setStateInformation (const void*, int) override {}
private:
//==============================================================================
static MidiBuffer filterMidiMessagesForChannel (const MidiBuffer& input, int channel)
{
MidiBuffer output;
for (const auto metadata : input)
{
const auto message = metadata.getMessage();
if (message.getChannel() == channel)
output.addEvent (message, metadata.samplePosition);
}
return output;
}
void loadNewSample (std::unique_ptr<InputStream> soundBuffer, const char* format)
{
std::unique_ptr<AudioFormatReader> formatReader (formatManager.findFormatForFileExtension (format)->createReaderFor (soundBuffer.release(), true));
BigInteger midiNotes;
midiNotes.setRange (0, 126, true);
SynthesiserSound::Ptr newSound = new SamplerSound ("Voice", *formatReader, midiNotes, 0x40, 0.0, 0.0, 10.0);
for (auto* s : synth)
s->removeSound (0);
sound = newSound;
for (auto* s : synth)
s->addSound (sound);
}
//==============================================================================
AudioFormatManager formatManager;
OwnedArray<Synthesiser> synth;
SynthesiserSound::Ptr sound;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiOutSynth)
};
+151
View File
@@ -0,0 +1,151 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2022 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: NoiseGatePlugin
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Noise gate audio plugin.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2022
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: NoiseGate
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class NoiseGate final : public AudioProcessor
{
public:
//==============================================================================
NoiseGate()
: AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo())
.withOutput ("Output", AudioChannelSet::stereo())
.withInput ("Sidechain", AudioChannelSet::stereo()))
{
addParameter (threshold = new AudioParameterFloat ({ "threshold", 1 }, "Threshold", 0.0f, 1.0f, 0.5f));
addParameter (alpha = new AudioParameterFloat ({ "alpha", 1 }, "Alpha", 0.0f, 1.0f, 0.8f));
}
//==============================================================================
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
// the sidechain can take any layout, the main bus needs to be the same on the input and output
return layouts.getMainInputChannelSet() == layouts.getMainOutputChannelSet()
&& ! layouts.getMainInputChannelSet().isDisabled();
}
//==============================================================================
void prepareToPlay (double, int) override { lowPassCoeff = 0.0f; sampleCountDown = 0; }
void releaseResources() override {}
void processBlock (AudioBuffer<float>& buffer, MidiBuffer&) override
{
auto mainInputOutput = getBusBuffer (buffer, true, 0);
auto sideChainInput = getBusBuffer (buffer, true, 1);
auto alphaCopy = alpha->get();
auto thresholdCopy = threshold->get();
for (int j = 0; j < buffer.getNumSamples(); ++j)
{
auto mixedSamples = 0.0f;
for (int i = 0; i < sideChainInput.getNumChannels(); ++i)
mixedSamples += sideChainInput.getReadPointer (i)[j];
mixedSamples /= static_cast<float> (sideChainInput.getNumChannels());
lowPassCoeff = (alphaCopy * lowPassCoeff) + ((1.0f - alphaCopy) * mixedSamples);
if (lowPassCoeff >= thresholdCopy)
sampleCountDown = (int) getSampleRate();
// very in-effective way of doing this
for (int i = 0; i < mainInputOutput.getNumChannels(); ++i)
*mainInputOutput.getWritePointer (i, j) = sampleCountDown > 0 ? *mainInputOutput.getReadPointer (i, j) : 0.0f;
if (sampleCountDown > 0)
--sampleCountDown;
}
}
using AudioProcessor::processBlock;
//==============================================================================
AudioProcessorEditor* createEditor() override { return new GenericAudioProcessorEditor (*this); }
bool hasEditor() const override { return true; }
const String getName() const override { return "NoiseGate"; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return "None"; }
void changeProgramName (int, const String&) override {}
bool isVST2() const noexcept { return (wrapperType == wrapperType_VST); }
//==============================================================================
void getStateInformation (MemoryBlock& destData) override
{
MemoryOutputStream stream (destData, true);
stream.writeFloat (*threshold);
stream.writeFloat (*alpha);
}
void setStateInformation (const void* data, int sizeInBytes) override
{
MemoryInputStream stream (data, static_cast<size_t> (sizeInBytes), false);
threshold->setValueNotifyingHost (stream.readFloat());
alpha->setValueNotifyingHost (stream.readFloat());
}
private:
//==============================================================================
AudioParameterFloat* threshold;
AudioParameterFloat* alpha;
int sampleCountDown;
float lowPassCoeff;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NoiseGate)
};
@@ -0,0 +1,447 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2022 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: ReaperEmbeddedViewDemo
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: An audio plugin which embeds a secondary view in VST2 and
VST3 formats in REAPER
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2022, linux_make
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: ReaperEmbeddedViewDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
/* This demo shows how to use the VST2ClientExtensions and VST3ClientExtensions
classes to provide extended functionality in compatible VST/VST3 hosts.
If this project is built as a VST or VST3 plugin and loaded in REAPER
6.29 or higher, it will provide an embedded level meter in the track
control panel. To enable the embedded view, right-click on the plugin
and select "Show embedded UI in TCP".
The plugin's editor also include a button which can be used to toggle
all inserts on and off.
*/
#pragma once
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wshadow-field-in-constructor",
"-Wnon-virtual-dtor")
#include <pluginterfaces/base/ftypes.h>
#include <pluginterfaces/base/funknown.h>
#include <pluginterfaces/vst/ivsthostapplication.h>
#include <pluginterfaces/vst2.x/aeffect.h>
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
namespace reaper
{
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wzero-as-null-pointer-constant",
"-Wunused-parameter",
"-Wnon-virtual-dtor")
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4100)
using namespace Steinberg;
using INT_PTR = pointer_sized_int;
using uint32 = Steinberg::uint32;
#include "extern/reaper_plugin_fx_embed.h"
#include "extern/reaper_vst3_interfaces.h"
//==============================================================================
/* These should live in a file which is guaranteed to be compiled only once
(i.e. a .cpp file, normally). This demo is a bit special, because we know
that this header will only be included in a single translation unit.
*/
DEF_CLASS_IID (IReaperHostApplication)
DEF_CLASS_IID (IReaperUIEmbedInterface)
JUCE_END_IGNORE_WARNINGS_MSVC
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
//==============================================================================
struct EmbeddedViewListener
{
virtual ~EmbeddedViewListener() = default;
virtual Steinberg::TPtrInt handledEmbeddedUIMessage (int msg,
Steinberg::TPtrInt parm2,
Steinberg::TPtrInt parm3) = 0;
virtual void setGlobalBypassFunction (void (*) (int)) = 0;
};
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnon-virtual-dtor")
//==============================================================================
class EmbeddedUI final : public reaper::IReaperUIEmbedInterface
{
public:
explicit EmbeddedUI (EmbeddedViewListener& demo) : listener (demo) {}
Steinberg::TPtrInt embed_message (int msg,
Steinberg::TPtrInt parm2,
Steinberg::TPtrInt parm3) override
{
return listener.handledEmbeddedUIMessage (msg, parm2, parm3);
}
Steinberg::uint32 PLUGIN_API addRef() override { return ++refCount; }
Steinberg::uint32 PLUGIN_API release() override { return --refCount; }
Steinberg::tresult PLUGIN_API queryInterface (const Steinberg::TUID tuid, void** obj) override
{
if (std::memcmp (tuid, iid, sizeof (Steinberg::TUID)) == 0)
{
++refCount;
*obj = this;
return Steinberg::kResultOk;
}
*obj = nullptr;
return Steinberg::kNoInterface;
}
private:
EmbeddedViewListener& listener;
std::atomic<Steinberg::uint32> refCount { 1 };
};
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
class VST2Extensions final : public VST2ClientExtensions
{
public:
explicit VST2Extensions (EmbeddedViewListener& l)
: listener (l) {}
pointer_sized_int handleVstPluginCanDo (int32, pointer_sized_int, void* ptr, float) override
{
if (auto* str = static_cast<const char*> (ptr))
for (auto* key : { "hasCockosEmbeddedUI", "hasCockosExtensions" })
if (strcmp (str, key) == 0)
return (pointer_sized_int) 0xbeef0000;
return 0;
}
pointer_sized_int handleVstManufacturerSpecific (int32 index,
pointer_sized_int value,
void* ptr,
float opt) override
{
// The docstring at the top of reaper_plugin_fx_embed.h specifies
// that the index will always be effEditDraw, which is now deprecated.
if (index != __effEditDrawDeprecated)
return 0;
return (pointer_sized_int) listener.handledEmbeddedUIMessage ((int) opt,
(Steinberg::TPtrInt) value,
(Steinberg::TPtrInt) ptr);
}
void handleVstHostCallbackAvailable (std::function<VstHostCallbackType>&& hostcb) override
{
char functionName[] = "BypassFxAllTracks";
listener.setGlobalBypassFunction (reinterpret_cast<void (*) (int)> (hostcb ((int32_t) 0xdeadbeef, (int32_t) 0xdeadf00d, 0, functionName, 0.0)));
}
private:
EmbeddedViewListener& listener;
};
class VST3Extensions final : public VST3ClientExtensions
{
public:
explicit VST3Extensions (EmbeddedViewListener& l)
: listener (l) {}
int32_t queryIEditController (const Steinberg::TUID tuid, void** obj) override
{
if (embeddedUi.queryInterface (tuid, obj) == Steinberg::kResultOk)
return Steinberg::kResultOk;
*obj = nullptr;
return Steinberg::kNoInterface;
}
void setIHostApplication (Steinberg::FUnknown* ptr) override
{
if (ptr == nullptr)
return;
void* objPtr = nullptr;
if (ptr->queryInterface (reaper::IReaperHostApplication::iid, &objPtr) == Steinberg::kResultOk)
{
if (void* fnPtr = static_cast<reaper::IReaperHostApplication*> (objPtr)->getReaperApi ("BypassFxAllTracks"))
listener.setGlobalBypassFunction (reinterpret_cast<void (*) (int)> (fnPtr));
}
}
private:
EmbeddedViewListener& listener;
EmbeddedUI embeddedUi { listener };
};
//==============================================================================
class Editor final : public AudioProcessorEditor
{
public:
explicit Editor (AudioProcessor& proc,
AudioParameterFloat& param,
void (*globalBypass) (int))
: AudioProcessorEditor (proc), attachment (param, slider)
{
addAndMakeVisible (slider);
addAndMakeVisible (bypassButton);
// Clicking will bypass *everything*
bypassButton.onClick = [globalBypass] { NullCheckedInvocation::invoke (globalBypass, -1); };
setSize (300, 80);
}
void resized() override
{
auto b = getLocalBounds();
slider.setBounds (b.removeFromTop (40));
bypassButton.setBounds (b);
}
void paint (Graphics& g) override
{
g.fillAll (Colours::darkgrey);
}
private:
Slider slider;
TextButton bypassButton { "global bypass" };
SliderParameterAttachment attachment;
};
//==============================================================================
class ReaperEmbeddedViewDemo final : public AudioProcessor,
private EmbeddedViewListener,
private Timer
{
public:
ReaperEmbeddedViewDemo()
{
addParameter (gain = new AudioParameterFloat ({ "gain", 1 }, "Gain", 0.0f, 1.0f, 0.5f));
startTimerHz (60);
}
void prepareToPlay (double, int) override {}
void reset() override {}
void releaseResources() override {}
void processBlock (AudioBuffer<float>& audio, MidiBuffer&) override { processBlockImpl (audio); }
void processBlock (AudioBuffer<double>& audio, MidiBuffer&) override { processBlockImpl (audio); }
//==============================================================================
AudioProcessorEditor* createEditor() override { return new Editor (*this, *gain, globalBypassFn); }
bool hasEditor() const override { return true; }
//==============================================================================
const String getName() const override { return "ReaperEmbeddedViewDemo"; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0.0; }
//==============================================================================
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return "None"; }
void changeProgramName (int, const String&) override {}
//==============================================================================
void getStateInformation (MemoryBlock& destData) override
{
MemoryOutputStream (destData, true).writeFloat (*gain);
}
void setStateInformation (const void* data, int sizeInBytes) override
{
gain->setValueNotifyingHost (MemoryInputStream (data,
static_cast<size_t> (sizeInBytes),
false).readFloat());
}
VST2ClientExtensions* getVST2ClientExtensions() override { return &vst2Extensions; }
VST3ClientExtensions* getVST3ClientExtensions() override { return &vst3Extensions; }
private:
template <typename Float>
void processBlockImpl (AudioBuffer<Float>& audio)
{
audio.applyGain (*gain);
const auto minMax = audio.findMinMax (0, 0, audio.getNumSamples());
const auto newMax = (float) std::max (std::abs (minMax.getStart()), std::abs (minMax.getEnd()));
auto loaded = storedLevel.load();
while (loaded < newMax && ! storedLevel.compare_exchange_weak (loaded, newMax)) {}
}
void timerCallback() override
{
levelToDraw = std::max (levelToDraw * 0.95f, storedLevel.exchange (0.0f));
}
Steinberg::TPtrInt getSizeInfo (reaper::REAPER_FXEMBED_SizeHints* sizeHints)
{
if (sizeHints == nullptr)
return 0;
sizeHints->preferred_aspect = 1 << 16;
sizeHints->minimum_aspect = 1 << 16;
sizeHints->min_height = sizeHints->min_width = 50;
sizeHints->max_height = sizeHints->max_width = 1000;
return 1;
}
Steinberg::TPtrInt doPaint (reaper::REAPER_FXEMBED_IBitmap* bitmap,
reaper::REAPER_FXEMBED_DrawInfo* drawInfo)
{
if (bitmap == nullptr || drawInfo == nullptr || bitmap->getWidth() <= 0 || bitmap->getHeight() <= 0)
return 0;
Image img (juce::Image::PixelFormat::ARGB, bitmap->getWidth(), bitmap->getHeight(), true);
Graphics g (img);
g.fillAll (Colours::black);
const auto bounds = g.getClipBounds();
const auto corner = 3.0f;
g.setColour (Colours::darkgrey);
g.fillRoundedRectangle (bounds.withSizeKeepingCentre (20, bounds.getHeight() - 6).toFloat(),
corner);
const auto minDb = -50.0f;
const auto maxDb = 6.0f;
const auto levelInDb = Decibels::gainToDecibels (levelToDraw, minDb);
const auto fractionOfHeight = jmap (levelInDb, minDb, maxDb, 0.0f, 1.0f);
const auto trackBounds = bounds.withSizeKeepingCentre (16, bounds.getHeight() - 10).toFloat();
g.setColour (Colours::black);
const auto zeroDbIndicatorY = trackBounds.proportionOfHeight (jmap (0.0f,
minDb,
maxDb,
0.0f,
1.0f));
g.drawHorizontalLine ((int) (trackBounds.getBottom() - zeroDbIndicatorY),
trackBounds.getX(),
trackBounds.getRight());
g.setGradientFill (ColourGradient (Colours::darkgreen,
{ 0.0f, (float) bounds.getHeight() },
Colours::darkred,
{ 0.0f, 0.0f },
false));
g.fillRoundedRectangle (trackBounds.withHeight (trackBounds.proportionOfHeight (fractionOfHeight))
.withBottomY (trackBounds.getBottom()),
corner);
Image::BitmapData imgData { img, Image::BitmapData::readOnly };
const auto pixelsWidth = imgData.pixelStride * imgData.width;
auto* px = bitmap->getBits();
const auto rowSpan = bitmap->getRowSpan();
const auto numRows = bitmap->getHeight();
for (int y = 0; y < numRows; ++y)
std::memcpy (px + (y * rowSpan), imgData.getLinePointer (y), (size_t) pixelsWidth);
return 1;
}
Steinberg::TPtrInt handledEmbeddedUIMessage (int msg,
Steinberg::TPtrInt parm2,
Steinberg::TPtrInt parm3) override
{
switch (msg)
{
case REAPER_FXEMBED_WM_IS_SUPPORTED:
return 1;
case REAPER_FXEMBED_WM_PAINT:
return doPaint (reinterpret_cast<reaper::REAPER_FXEMBED_IBitmap*> (parm2),
reinterpret_cast<reaper::REAPER_FXEMBED_DrawInfo*> (parm3));
case REAPER_FXEMBED_WM_GETMINMAXINFO:
return getSizeInfo (reinterpret_cast<reaper::REAPER_FXEMBED_SizeHints*> (parm3));
// Implementing mouse behaviour is left as an exercise for the reaper, I mean reader
case REAPER_FXEMBED_WM_CREATE: break;
case REAPER_FXEMBED_WM_DESTROY: break;
case REAPER_FXEMBED_WM_SETCURSOR: break;
case REAPER_FXEMBED_WM_MOUSEMOVE: break;
case REAPER_FXEMBED_WM_LBUTTONDOWN: break;
case REAPER_FXEMBED_WM_LBUTTONUP: break;
case REAPER_FXEMBED_WM_LBUTTONDBLCLK: break;
case REAPER_FXEMBED_WM_RBUTTONDOWN: break;
case REAPER_FXEMBED_WM_RBUTTONUP: break;
case REAPER_FXEMBED_WM_RBUTTONDBLCLK: break;
case REAPER_FXEMBED_WM_MOUSEWHEEL: break;
}
return 0;
}
void setGlobalBypassFunction (void (*fn) (int)) override { globalBypassFn = fn; }
AudioParameterFloat* gain = nullptr;
void (*globalBypassFn) (int) = nullptr;
std::atomic<float> storedLevel { 0.0f };
float levelToDraw = 0.0f;
VST2Extensions vst2Extensions { *this };
VST3Extensions vst3Extensions { *this };
};
File diff suppressed because it is too large Load Diff
+461
View File
@@ -0,0 +1,461 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2022 - Raw Material Software Limited
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
PURPOSE, ARE DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this PIP. A PIP is a short snippet
of code that can be read by the Projucer and used to generate a JUCE project.
BEGIN_JUCE_PIP_METADATA
name: SurroundPlugin
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Surround audio plugin.
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2022, linux_make
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: SurroundProcessor
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class ProcessorWithLevels : public AudioProcessor,
private AsyncUpdater,
private Timer
{
public:
ProcessorWithLevels()
: AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo())
.withInput ("Aux", AudioChannelSet::stereo(), false)
.withOutput ("Output", AudioChannelSet::stereo())
.withOutput ("Aux", AudioChannelSet::stereo(), false))
{
startTimerHz (60);
applyBusLayouts (getBusesLayout());
}
~ProcessorWithLevels() override
{
stopTimer();
cancelPendingUpdate();
}
void prepareToPlay (double, int) override
{
samplesToPlay = (int) getSampleRate();
reset();
}
void processBlock (AudioBuffer<float>& audio, MidiBuffer&) override { processAudio (audio); }
void processBlock (AudioBuffer<double>& audio, MidiBuffer&) override { processAudio (audio); }
void releaseResources() override { reset(); }
float getLevel (int bus, int channel) const
{
return readableLevels[(size_t) getChannelIndexInProcessBlockBuffer (true, bus, channel)];
}
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
const auto isSetValid = [] (const AudioChannelSet& set)
{
return ! set.isDisabled()
&& ! (set.isDiscreteLayout() && set.getChannelIndexForType (AudioChannelSet::discreteChannel0) == -1);
};
return isSetValid (layouts.getMainOutputChannelSet())
&& isSetValid (layouts.getMainInputChannelSet());
}
void reset() override
{
channelClicked = 0;
samplesPlayed = samplesToPlay;
}
bool applyBusLayouts (const BusesLayout& layouts) final
{
// Some very badly-behaved hosts will call this during processing!
const SpinLock::ScopedLockType lock (levelMutex);
const auto result = AudioProcessor::applyBusLayouts (layouts);
size_t numInputChannels = 0;
for (auto i = 0; i < getBusCount (true); ++i)
numInputChannels += (size_t) getBus (true, i)->getLastEnabledLayout().size();
incomingLevels = readableLevels = std::vector<float> (numInputChannels, 0.0f);
triggerAsyncUpdate();
return result;
}
//==============================================================================
const String getName() const override { return "Surround PlugIn"; }
bool acceptsMidi() const override { return false; }
bool producesMidi() const override { return false; }
double getTailLengthSeconds() const override { return 0; }
//==============================================================================
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const String getProgramName (int) override { return "None"; }
void changeProgramName (int, const String&) override {}
//==============================================================================
void getStateInformation (MemoryBlock&) override {}
void setStateInformation (const void*, int) override {}
void channelButtonClicked (int bus, int channelIndex)
{
channelClicked = getChannelIndexInProcessBlockBuffer (false, bus, channelIndex);
samplesPlayed = 0;
}
std::function<void()> updateEditor;
private:
void handleAsyncUpdate() override
{
NullCheckedInvocation::invoke (updateEditor);
}
template <typename Float>
void processAudio (AudioBuffer<Float>& audio)
{
{
SpinLock::ScopedTryLockType lock (levelMutex);
if (lock.isLocked())
{
const auto numInputChannels = (size_t) getTotalNumInputChannels();
for (size_t i = 0; i < numInputChannels; ++i)
{
const auto minMax = audio.findMinMax ((int) i, 0, audio.getNumSamples());
const auto newMax = (float) std::max (std::abs (minMax.getStart()), std::abs (minMax.getEnd()));
auto& toUpdate = incomingLevels[i];
toUpdate = jmax (toUpdate, newMax);
}
}
}
audio.clear (0, audio.getNumSamples());
auto fillSamples = jmin (samplesToPlay - samplesPlayed, audio.getNumSamples());
if (isPositiveAndBelow (channelClicked, audio.getNumChannels()))
{
auto* channelBuffer = audio.getWritePointer (channelClicked);
auto freq = (float) (440.0 / getSampleRate());
for (auto i = 0; i < fillSamples; ++i)
channelBuffer[i] += std::sin (MathConstants<float>::twoPi * freq * (float) samplesPlayed++);
}
}
void timerCallback() override
{
const SpinLock::ScopedLockType lock (levelMutex);
for (size_t i = 0; i < readableLevels.size(); ++i)
readableLevels[i] = std::max (readableLevels[i] * 0.95f, std::exchange (incomingLevels[i], 0.0f));
}
SpinLock levelMutex;
std::vector<float> incomingLevels;
std::vector<float> readableLevels;
int channelClicked;
int samplesPlayed;
int samplesToPlay;
};
//==============================================================================
const Colour textColour = Colours::white.withAlpha (0.8f);
inline void drawBackground (Component& comp, Graphics& g)
{
g.setColour (comp.getLookAndFeel().findColour (ResizableWindow::backgroundColourId).darker (0.8f));
g.fillRoundedRectangle (comp.getLocalBounds().toFloat(), 4.0f);
}
inline void configureLabel (Label& label, const AudioProcessor::Bus* layout)
{
const auto text = layout != nullptr
? (layout->getName() + ": " + layout->getCurrentLayout().getDescription())
: "";
label.setText (text, dontSendNotification);
label.setJustificationType (Justification::centred);
label.setColour (Label::textColourId, textColour);
}
class InputBusViewer final : public Component,
private Timer
{
public:
InputBusViewer (ProcessorWithLevels& proc, int busNumber)
: processor (proc),
bus (busNumber)
{
configureLabel (layoutName, processor.getBus (true, bus));
addAndMakeVisible (layoutName);
startTimerHz (60);
}
~InputBusViewer() override
{
stopTimer();
}
void paint (Graphics& g) override
{
drawBackground (*this, g);
auto* layout = processor.getBus (true, bus);
if (layout == nullptr)
return;
const auto channelSet = layout->getCurrentLayout();
const auto numChannels = channelSet.size();
Grid grid;
grid.autoFlow = Grid::AutoFlow::column;
grid.autoColumns = grid.autoRows = Grid::TrackInfo (Grid::Fr (1));
grid.items.insertMultiple (0, GridItem(), numChannels);
grid.performLayout (getLocalBounds());
const auto minDb = -50.0f;
const auto maxDb = 6.0f;
for (auto i = 0; i < numChannels; ++i)
{
g.setColour (Colours::orange.darker());
const auto levelInDb = Decibels::gainToDecibels (processor.getLevel (bus, i), minDb);
const auto fractionOfHeight = jmap (levelInDb, minDb, maxDb, 0.0f, 1.0f);
const auto bounds = grid.items[i].currentBounds;
const auto trackBounds = bounds.withSizeKeepingCentre (16, bounds.getHeight() - 10).toFloat();
g.fillRect (trackBounds.withHeight (trackBounds.proportionOfHeight (fractionOfHeight)).withBottomY (trackBounds.getBottom()));
g.setColour (textColour);
g.drawText (channelSet.getAbbreviatedChannelTypeName (channelSet.getTypeOfChannel (i)),
bounds,
Justification::centredBottom);
}
}
void resized() override
{
layoutName.setBounds (getLocalBounds().removeFromTop (20));
}
int getNumChannels() const
{
if (auto* b = processor.getBus (true, bus))
return b->getCurrentLayout().size();
return 0;
}
private:
void timerCallback() override { repaint(); }
ProcessorWithLevels& processor;
int bus = 0;
Label layoutName;
};
//==============================================================================
class OutputBusViewer final : public Component
{
public:
OutputBusViewer (ProcessorWithLevels& proc, int busNumber)
: processor (proc),
bus (busNumber)
{
auto* layout = processor.getBus (false, bus);
configureLabel (layoutName, layout);
addAndMakeVisible (layoutName);
if (layout == nullptr)
return;
const auto& channelSet = layout->getCurrentLayout();
const auto numChannels = channelSet.size();
for (auto i = 0; i < numChannels; ++i)
{
const auto channelName = channelSet.getAbbreviatedChannelTypeName (channelSet.getTypeOfChannel (i));
channelButtons.emplace_back (channelName, channelName);
auto& newButton = channelButtons.back();
newButton.onClick = [&p = processor, b = bus, i] { p.channelButtonClicked (b, i); };
addAndMakeVisible (newButton);
}
resized();
}
void paint (Graphics& g) override
{
drawBackground (*this, g);
}
void resized() override
{
auto b = getLocalBounds();
layoutName.setBounds (b.removeFromBottom (20));
Grid grid;
grid.autoFlow = Grid::AutoFlow::column;
grid.autoColumns = grid.autoRows = Grid::TrackInfo (Grid::Fr (1));
for (auto& channelButton : channelButtons)
grid.items.add (GridItem (channelButton));
grid.performLayout (b.reduced (2));
}
int getNumChannels() const
{
if (auto* b = processor.getBus (false, bus))
return b->getCurrentLayout().size();
return 0;
}
private:
ProcessorWithLevels& processor;
int bus = 0;
Label layoutName;
std::list<TextButton> channelButtons;
};
//==============================================================================
class SurroundEditor final : public AudioProcessorEditor
{
public:
explicit SurroundEditor (ProcessorWithLevels& parent)
: AudioProcessorEditor (parent),
customProcessor (parent),
scopedUpdateEditor (customProcessor.updateEditor, [this] { updateGUI(); })
{
updateGUI();
setResizable (true, true);
}
void resized() override
{
auto r = getLocalBounds();
doLayout (inputViewers, r.removeFromTop (proportionOfHeight (0.5f)));
doLayout (outputViewers, r);
}
void paint (Graphics& g) override
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
private:
template <typename Range>
void doLayout (Range& range, Rectangle<int> bounds) const
{
FlexBox fb;
for (auto& viewer : range)
{
if (viewer.getNumChannels() != 0)
{
fb.items.add (FlexItem (viewer)
.withFlex ((float) viewer.getNumChannels())
.withMargin (4.0f));
}
}
fb.performLayout (bounds);
}
void updateGUI()
{
inputViewers.clear();
outputViewers.clear();
const auto inputBuses = getAudioProcessor()->getBusCount (true);
for (auto i = 0; i < inputBuses; ++i)
{
inputViewers.emplace_back (customProcessor, i);
addAndMakeVisible (inputViewers.back());
}
const auto outputBuses = getAudioProcessor()->getBusCount (false);
for (auto i = 0; i < outputBuses; ++i)
{
outputViewers.emplace_back (customProcessor, i);
addAndMakeVisible (outputViewers.back());
}
const auto channels = jmax (processor.getTotalNumInputChannels(),
processor.getTotalNumOutputChannels());
setSize (jmax (150, channels * 40), 200);
resized();
}
ProcessorWithLevels& customProcessor;
ScopedValueSetter<std::function<void()>> scopedUpdateEditor;
std::list<InputBusViewer> inputViewers;
std::list<OutputBusViewer> outputViewers;
};
//==============================================================================
struct SurroundProcessor final : public ProcessorWithLevels
{
AudioProcessorEditor* createEditor() override { return new SurroundEditor (*this); }
bool hasEditor() const override { return true; }
};
+19
View File
@@ -0,0 +1,19 @@
The files in this folder were copied from the [reaper-sdk
repository](https://github.com/justinfrankel/reaper-sdk) on Github. At the time
of writing, these files were distributed under the following license:
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
@@ -0,0 +1,140 @@
#ifndef _REAPER_PLUGIN_FX_EMBED_H_
#define _REAPER_PLUGIN_FX_EMBED_H_
/*
* to support via VST2: canDo("hasCockosEmbeddedUI") should return 0xbeef0000
* dispatcher will be called with opcode=effVendorSpecific, index=effEditDraw, value=parm2, ptr=(void*)(INT_PTR)parm3, opt=message (REAPER_FXEMBED_WM_*)
*
* to support via VST3: IController should support IReaperUIEmbedInterface, see reaper_vst3_interfaces.h
*
* to support via LV2: todo
*/
// these alias to win32's WM_*
#define REAPER_FXEMBED_WM_IS_SUPPORTED 0x0000
/* return 1 if embedding is supported and available
* return -1 if embedding is supported and unavailable
* return 0 if embedding is not supported
*/
#define REAPER_FXEMBED_WM_CREATE 0x0001 // called when embedding begins (return value ignored)
#define REAPER_FXEMBED_WM_DESTROY 0x0002 // called when embedding ends (return value ignored)
typedef struct REAPER_FXEMBED_DrawInfo // alias of REAPER_inline_positioninfo
{
int context; // 0=unknown (v6.23 and earlier), 1=TCP, 2=MCP
int dpi; // 0=unknown (v6.23 and earlier), otherwise 24.8 fixed point (256=100%)
int mousewheel_amt; // for REAPER_FXEMBED_WM_MOUSEWHEEL, 120 = step, typically
double _res2;
int width, height;
int mouse_x, mouse_y;
int flags; // REAPER_FXEMBED_DRAWINFO_FLAG_PAINT_OPTIONAL etc
int _res3;
void *spare[6];
} REAPER_FXEMBED_DrawInfo;
#define REAPER_FXEMBED_DRAWINFO_FLAG_PAINT_OPTIONAL 1
#define REAPER_FXEMBED_DRAWINFO_FLAG_LBUTTON_CAPTURED 0x10000
#define REAPER_FXEMBED_DRAWINFO_FLAG_RBUTTON_CAPTURED 0x20000
#define REAPER_FXEMBED_WM_PAINT 0x000F
/*
* draw embedded UI.
* parm2: REAPER_FXEMBED_IBitmap * to draw into. note
* parm3: REAPER_FXEMBED_DrawInfo *
*
* if flags has REAPER_FXEMBED_DRAWINFO_FLAG_PAINT_OPTIONAL set, update is optional. if no change since last draw, return 0.
* if flags has REAPER_FXEMBED_DRAWINFO_FLAG_LBUTTON_CAPTURED set, left mouse button is down and captured
* if flags has REAPER_FXEMBED_DRAWINFO_FLAG_RBUTTON_CAPTURED set, right mouse button is down and captured
*
* HiDPI:
* if REAPER_FXEMBED_IBitmap::Extended(REAPER_FXEMBED_EXT_GET_ADVISORY_SCALING,NULL) returns nonzero, then it is a 24.8 scalefactor for UI drawing
*
* return 1 if drawing occurred, 0 otherwise.
*
*/
#define REAPER_FXEMBED_WM_SETCURSOR 0x0020 // parm3: REAPER_FXEMBED_DrawInfo*. set mouse cursor and return REAPER_FXEMBED_RETNOTIFY_HANDLED, or return 0.
#define REAPER_FXEMBED_WM_GETMINMAXINFO 0x0024
/*
* get size hints. parm3 = (REAPER_FXEMBED_SizeHints*). return 1 if supported
* note that these are just hints, the actual size may vary
*/
typedef struct REAPER_FXEMBED_SizeHints { // alias to MINMAXINFO
int preferred_aspect; // 16.16 fixed point (65536 = 1:1, 32768 = 1:2, etc)
int minimum_aspect; // 16.16 fixed point
int _res1, _res2, _res3, _res4;
int min_width, min_height;
int max_width, max_height;
} REAPER_FXEMBED_SizeHints;
/*
* mouse messages
* parm3 = (REAPER_FXEMBED_DrawInfo*)
* capture is automatically set on mouse down, released on mouse up
* when not captured, will always receive a mousemove when exiting the window
*/
#define REAPER_FXEMBED_WM_MOUSEMOVE 0x0200
#define REAPER_FXEMBED_WM_LBUTTONDOWN 0x0201
#define REAPER_FXEMBED_WM_LBUTTONUP 0x0202
#define REAPER_FXEMBED_WM_LBUTTONDBLCLK 0x0203
#define REAPER_FXEMBED_WM_RBUTTONDOWN 0x0204
#define REAPER_FXEMBED_WM_RBUTTONUP 0x0205
#define REAPER_FXEMBED_WM_RBUTTONDBLCLK 0x0206
#define REAPER_FXEMBED_WM_MOUSEWHEEL 0x020A
/* REAPER_FXEMBED_WM_SETCURSOR should return REAPER_FXEMBED_RETNOTIFY_HANDLED if a cursor was set
*/
#define REAPER_FXEMBED_RETNOTIFY_HANDLED 0x0000001
/* if the mouse messages return with REAPER_FXEMBED_RETNOTIFY_INVALIDATE set, a non-optional
* redraw is initiated (generally sooner than the next timer-based redraw)
*/
#define REAPER_FXEMBED_RETNOTIFY_INVALIDATE 0x1000000
/*
* bitmap interface
* this is an alias of LICE_IBitmap etc from WDL/lice/lice.h
*
*/
#define REAPER_FXEMBED_RGBA(r,g,b,a) (((b)&0xff)|(((g)&0xff)<<8)|(((r)&0xff)<<16)|(((a)&0xff)<<24))
#define REAPER_FXEMBED_GETB(v) ((v)&0xff)
#define REAPER_FXEMBED_GETG(v) (((v)>>8)&0xff)
#define REAPER_FXEMBED_GETR(v) (((v)>>16)&0xff)
#define REAPER_FXEMBED_GETA(v) (((v)>>24)&0xff)
#ifdef __cplusplus
class REAPER_FXEMBED_IBitmap // alias of LICE_IBitmap
{
public:
virtual ~REAPER_FXEMBED_IBitmap() { }
virtual unsigned int *getBits()=0;
virtual int getWidth()=0;
virtual int getHeight()=0;
virtual int getRowSpan()=0; // includes any off-bitmap data. this is in sizeof(unsigned int) units, not bytes.
virtual bool isFlipped() { return false; }
virtual bool resize(int w, int h)=0;
virtual void *getDC() { return 0; } // do not use
virtual INT_PTR Extended(int id, void* data) { return 0; }
};
#endif
#define REAPER_FXEMBED_EXT_GET_ADVISORY_SCALING 0x2003 // data ignored, returns .8 fixed point. returns 0 if unscaled
#endif
@@ -0,0 +1,31 @@
#ifndef _REAPER_VST3_INTERFACES_H_
#define _REAPER_VST3_INTERFACES_H_
class IReaperHostApplication : public FUnknown // available from IHostApplication in REAPER v5.02+
{
public:
// Gets a REAPER Extension API function by name, returns NULL is failed
virtual void* PLUGIN_API getReaperApi(CStringA funcname) = 0;
virtual void* PLUGIN_API getReaperParent(uint32 w) = 0; // get parent track(=1), take(=2), project(=3), fxdsp(=4), trackchan(=5)
// Multi-purpose function, returns NULL if unsupported
virtual void* PLUGIN_API reaperExtended(uint32 call, void *parm1, void *parm2, void *parm3) = 0;
static const FUID iid;
};
DECLARE_CLASS_IID (IReaperHostApplication, 0x79655E36, 0x77EE4267, 0xA573FEF7, 0x4912C27C)
class IReaperUIEmbedInterface : public FUnknown // supported by REAPER v6.24+, queried from plug-in IController
{
public:
// note: VST2 uses CanDo "hasCockosEmbeddedUI"==0xbeef0000, then opcode=effVendorSpecific, index=effEditDraw, opt=(float)msg, value=parm2, ptr=parm3
// see reaper_plugin_fx_embed.h
virtual Steinberg::TPtrInt embed_message(int msg, Steinberg::TPtrInt parm2, Steinberg::TPtrInt parm3) = 0;
static const FUID iid;
};
DECLARE_CLASS_IID (IReaperUIEmbedInterface, 0x049bf9e7, 0xbc74ead0, 0xc4101e86, 0x7f725981)
#endif