migrating to the latest JUCE version

This commit is contained in:
2022-11-04 23:11:33 +01:00
committed by Nikolai Rodionov
parent 4257a0f8ba
commit faf8f18333
2796 changed files with 888518 additions and 784244 deletions

1449
deps/juce/examples/Plugins/ARAPluginDemo.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,477 +1,477 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - 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 : 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,
const 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 : 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 : public AudioProcessor
{
public:
AUv3SynthProcessor ()
: AudioProcessor (BusesProperties().withOutput ("Output", AudioChannelSet::stereo(), true)),
currentRecording (1, 1), currentProgram (0)
{
// initialize parameters
addParameter (isRecordingParam = new AudioParameterBool ("isRecording", "Is Recording", false));
addParameter (roomSizeParam = new AudioParameterFloat ("roomSize", "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)
};
/*
==============================================================================
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 : 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,
const 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 : 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 : 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)
};

View File

@ -1,166 +1,166 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - 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, vs2019
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: Arpeggiator
useLocalCopy: 1
pluginCharacteristics: pluginWantsMidiIn, pluginProducesMidiOut, pluginIsMidiEffectPlugin
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class Arpeggiator : public AudioProcessor
{
public:
//==============================================================================
Arpeggiator()
: AudioProcessor (BusesProperties()) // add no audio buses at all
{
addParameter (speed = new AudioParameterFloat ("speed", "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
{
// the audio buffer in a midi effect will have zero channels!
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)
};
/*
==============================================================================
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 : 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)
};

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,15 @@
# ==============================================================================
#
# This file is part of the JUCE library.
# Copyright (c) 2020 - Raw Material Software Limited
# 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 6 End-User License
# Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
# 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-6-licence
# 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

File diff suppressed because it is too large Load Diff

View File

@ -1,122 +1,122 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - 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, vs2019
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: GainProcessor
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class GainProcessor : public AudioProcessor
{
public:
//==============================================================================
GainProcessor()
: AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo())
.withOutput ("Output", AudioChannelSet::stereo()))
{
addParameter (gain = new AudioParameterFloat ("gain", "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)
};
/*
==============================================================================
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 : 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)
};

View File

@ -0,0 +1,588 @@
/*
==============================================================================
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 override
{
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) override
{
const ScopedLock sl (innerMutex);
active = true;
if (inner != nullptr)
{
inner->setRateAndBufferSizeDetails (sr, bs);
inner->prepareToPlay (sr, bs);
}
}
void releaseResources() override
{
const ScopedLock sl (innerMutex);
active = false;
if (inner != nullptr)
inner->releaseResources();
}
void reset() override
{
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&) override
{
jassert (! isUsingDoublePrecision());
}
void processBlock (AudioBuffer<double>&, MidiBuffer&) override
{
jassert (isUsingDoublePrecision());
}
bool hasEditor() const override { return false; }
AudioProcessorEditor* createEditor() override { return nullptr; }
const String getName() const override { return "HostPluginDemo"; }
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
{
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) override
{
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())
{
NativeMessageBox::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
"Plugin Load Failed",
error,
nullptr,
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;
static constexpr const char* innerStateTag = "inner_state";
static constexpr const char* editorStyleTag = "editor_style";
void changeListenerCallback (ChangeBroadcaster* source) override
{
if (source != &pluginList)
return;
if (auto savedPluginList = pluginList.createXml())
{
appProperties.getUserSettings()->setValue ("pluginList", savedPluginList.get());
appProperties.saveIfNeeded();
}
}
};
constexpr const char* HostAudioProcessorImpl::innerStateTag;
constexpr const char* HostAudioProcessorImpl::editorStyleTag;
//==============================================================================
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 : 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, callback = std::forward<Callback> (callback)] (EditorStyle style)
{
return [this, &list, callback, style]
{
const auto index = pluginListComponent.getTableListBox().getSelectedRow();
const auto& types = list.getTypes();
if (isPositiveAndBelow (index, types.size()))
NullCheckedInvocation::invoke (callback, 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 : 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 : 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 : 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 : 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);
const auto posted = MessageManager::callAsync ([ref = SafePointer<HostAudioProcessorEditor> (this), scale]
{
if (auto* r = ref.getComponent())
if (auto* e = r->currentEditorComponent)
e->setScaleFactor (scale);
});
jassertquiet (posted);
}
private:
void pluginChanged()
{
loader.setVisible (! hostProcessor.isPluginLoaded());
closeButton.setVisible (hostProcessor.isPluginLoaded());
if (hostProcessor.isPluginLoaded())
{
auto editorComponent = std::make_unique<PluginEditorComponent> (hostProcessor.createInnerEditor(), [this]
{
const auto posted = MessageManager::callAsync ([this] { clearPlugin(); });
jassertquiet (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 : public HostAudioProcessorImpl
{
public:
bool hasEditor() const override { return true; }
AudioProcessorEditor* createEditor() override { return new HostAudioProcessorEditor (*this); }
};

View File

@ -1,529 +0,0 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - 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: InterAppAudioEffectPlugin
version: 1.0.0
vendor: JUCE
website: http://juce.com
description: Inter-app audio effect 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_iphone
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: IAAEffectProcessor
useLocalCopy: 1
extraPluginFormats: IAA
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include <array>
//==============================================================================
// A very simple decaying meter.
class SimpleMeter : public Component,
private Timer
{
public:
SimpleMeter()
{
startTimerHz (30);
}
//==============================================================================
void paint (Graphics& g) override
{
g.fillAll (Colours::transparentBlack);
auto area = g.getClipBounds();
g.setColour (getLookAndFeel().findColour (Slider::thumbColourId));
g.fillRoundedRectangle (area.toFloat(), 6.0);
auto unfilledHeight = area.getHeight() * (1.0 - level);
g.reduceClipRegion (area.getX(), area.getY(),
area.getWidth(), (int) unfilledHeight);
g.setColour (getLookAndFeel().findColour (Slider::trackColourId));
g.fillRoundedRectangle (area.toFloat(), 6.0);
}
//==============================================================================
// Called from the audio thread.
void update (float newLevel)
{
// We don't care if maxLevel gets set to zero (in timerCallback) between the
// load and the assignment.
maxLevel = jmax (maxLevel.load(), newLevel);
}
private:
//==============================================================================
void timerCallback() override
{
auto callbackLevel = maxLevel.exchange (0.0);
float decayFactor = 0.95f;
if (callbackLevel > level)
level = callbackLevel;
else if (level > 0.001)
level *= decayFactor;
else
level = 0;
repaint();
}
std::atomic<float> maxLevel {0.0};
float level = 0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleMeter)
};
//==============================================================================
// A simple Inter-App Audio plug-in with a gain control and some meters.
class IAAEffectProcessor : public AudioProcessor
{
public:
IAAEffectProcessor()
: AudioProcessor (BusesProperties()
.withInput ("Input", AudioChannelSet::stereo(), true)
.withOutput ("Output", AudioChannelSet::stereo(), true)),
parameters (*this, nullptr, "InterAppAudioEffect",
{ std::make_unique<AudioParameterFloat> ("gain", "Gain", NormalisableRange<float> (0.0f, 1.0f), 1.0f / 3.14f) })
{
}
//==============================================================================
void prepareToPlay (double, int) override
{
previousGain = *parameters.getRawParameterValue ("gain");
}
void releaseResources() override {}
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
if (layouts.getMainInputChannels() > 2)
return false;
if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
return false;
return true;
}
using AudioProcessor::processBlock;
void processBlock (AudioBuffer<float>& buffer, MidiBuffer&) override
{
float gain = *parameters.getRawParameterValue ("gain");
auto totalNumInputChannels = getTotalNumInputChannels();
auto totalNumOutputChannels = getTotalNumOutputChannels();
auto numSamples = buffer.getNumSamples();
for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
buffer.clear (i, 0, buffer.getNumSamples());
// Apply the gain to the samples using a ramp to avoid discontinuities in
// the audio between processed buffers.
for (auto channel = 0; channel < totalNumInputChannels; ++channel)
{
buffer.applyGainRamp (channel, 0, numSamples, previousGain, gain);
auto newLevel = buffer.getMagnitude (channel, 0, numSamples);
meterListeners.call ([=] (MeterListener& l) { l.handleNewMeterValue (channel, newLevel); });
}
previousGain = gain;
// Now ask the host for the current time so we can store it to be displayed later.
updateCurrentTimeInfoFromHost (lastPosInfo);
}
//==============================================================================
AudioProcessorEditor* createEditor() override
{
return new IAAEffectEditor (*this, parameters);
}
bool hasEditor() const override { return true; }
//==============================================================================
const String getName() const override { return "InterAppAudioEffectPlugin"; }
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 {}
//==============================================================================
void getStateInformation (MemoryBlock& destData) override
{
if (auto xml = parameters.state.createXml())
copyXmlToBinary (*xml, destData);
}
void setStateInformation (const void* data, int sizeInBytes) override
{
if (auto xmlState = getXmlFromBinary (data, sizeInBytes))
if (xmlState->hasTagName (parameters.state.getType()))
parameters.state = ValueTree::fromXml (*xmlState);
}
//==============================================================================
bool updateCurrentTimeInfoFromHost (AudioPlayHead::CurrentPositionInfo& posInfo)
{
if (auto* ph = getPlayHead())
{
AudioPlayHead::CurrentPositionInfo newTime;
if (ph->getCurrentPosition (newTime))
{
posInfo = newTime; // Successfully got the current time from the host.
return true;
}
}
// If the host fails to provide the current time, we'll just reset our copy to a default.
lastPosInfo.resetToDefault();
return false;
}
// Allow an IAAAudioProcessorEditor to register as a listener to receive new
// meter values directly from the audio thread.
struct MeterListener
{
virtual ~MeterListener() {}
virtual void handleNewMeterValue (int, float) = 0;
};
void addMeterListener (MeterListener& listener) { meterListeners.add (&listener); }
void removeMeterListener (MeterListener& listener) { meterListeners.remove (&listener); }
private:
//==============================================================================
class IAAEffectEditor : public AudioProcessorEditor,
private IAAEffectProcessor::MeterListener,
private Timer
{
public:
IAAEffectEditor (IAAEffectProcessor& p,
AudioProcessorValueTreeState& vts)
: AudioProcessorEditor (p),
iaaEffectProcessor (p),
parameters (vts)
{
// Register for meter value updates.
iaaEffectProcessor.addMeterListener (*this);
gainSlider.setSliderStyle (Slider::SliderStyle::LinearVertical);
gainSlider.setTextBoxStyle (Slider::TextEntryBoxPosition::TextBoxAbove, false, 60, 20);
addAndMakeVisible (gainSlider);
for (auto& meter : meters)
addAndMakeVisible (meter);
// Configure all the graphics for the transport control.
transportText.setFont (Font (Font::getDefaultMonospacedFontName(), 18.0f, Font::plain));
transportText.setJustificationType (Justification::topLeft);
addChildComponent (transportText);
Path rewindShape;
rewindShape.addRectangle (0.0, 0.0, 5.0, buttonSize);
rewindShape.addTriangle (0.0, buttonSize * 0.5f, buttonSize, 0.0, buttonSize, buttonSize);
rewindButton.setShape (rewindShape, true, true, false);
rewindButton.onClick = [this]
{
if (transportControllable())
iaaEffectProcessor.getPlayHead()->transportRewind();
};
addChildComponent (rewindButton);
Path playShape;
playShape.addTriangle (0.0, 0.0, 0.0, buttonSize, buttonSize, buttonSize / 2);
playButton.setShape (playShape, true, true, false);
playButton.onClick = [this]
{
if (transportControllable())
iaaEffectProcessor.getPlayHead()->transportPlay (! lastPosInfo.isPlaying);
};
addChildComponent (playButton);
Path recordShape;
recordShape.addEllipse (0.0, 0.0, buttonSize, buttonSize);
recordButton.setShape (recordShape, true, true, false);
recordButton.onClick = [this]
{
if (transportControllable())
iaaEffectProcessor.getPlayHead()->transportRecord (! lastPosInfo.isRecording);
};
addChildComponent (recordButton);
// Configure the switch to host button.
switchToHostButtonLabel.setFont (Font (Font::getDefaultMonospacedFontName(), 18.0f, Font::plain));
switchToHostButtonLabel.setJustificationType (Justification::centredRight);
switchToHostButtonLabel.setText ("Switch to\nhost app:", dontSendNotification);
addChildComponent (switchToHostButtonLabel);
switchToHostButton.onClick = [this]
{
if (transportControllable())
{
PluginHostType hostType;
hostType.switchToHostApplication();
}
};
addChildComponent (switchToHostButton);
auto screenSize = Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
setSize (screenSize.getWidth(), screenSize.getHeight());
resized();
startTimerHz (60);
}
~IAAEffectEditor() override
{
iaaEffectProcessor.removeMeterListener (*this);
}
//==============================================================================
void paint (Graphics& g) override
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
void resized() override
{
auto area = getBounds().reduced (20);
gainSlider.setBounds (area.removeFromLeft (60));
for (auto& meter : meters)
{
area.removeFromLeft (10);
meter.setBounds (area.removeFromLeft (20));
}
area.removeFromLeft (20);
transportText.setBounds (area.removeFromTop (120));
auto navigationArea = area.removeFromTop ((int) buttonSize);
rewindButton.setTopLeftPosition (navigationArea.getPosition());
navigationArea.removeFromLeft ((int) buttonSize + 10);
playButton.setTopLeftPosition (navigationArea.getPosition());
navigationArea.removeFromLeft ((int) buttonSize + 10);
recordButton.setTopLeftPosition (navigationArea.getPosition());
area.removeFromTop (30);
auto appSwitchArea = area.removeFromTop ((int) buttonSize);
switchToHostButtonLabel.setBounds (appSwitchArea.removeFromLeft (100));
appSwitchArea.removeFromLeft (5);
switchToHostButton.setBounds (appSwitchArea.removeFromLeft ((int) buttonSize));
}
private:
//==============================================================================
// Called from the audio thread.
void handleNewMeterValue (int channel, float value) override
{
meters[(size_t) channel].update (value);
}
//==============================================================================
void timerCallback () override
{
auto timeInfoSuccess = iaaEffectProcessor.updateCurrentTimeInfoFromHost (lastPosInfo);
transportText.setVisible (timeInfoSuccess);
if (timeInfoSuccess)
updateTransportTextDisplay();
updateTransportButtonsDisplay();
updateSwitchToHostDisplay();
}
//==============================================================================
bool transportControllable()
{
auto processorPlayHead = iaaEffectProcessor.getPlayHead();
return processorPlayHead != nullptr && processorPlayHead->canControlTransport();
}
//==============================================================================
// quick-and-dirty function to format a timecode string
String timeToTimecodeString (double seconds)
{
auto millisecs = roundToInt (seconds * 1000.0);
auto absMillisecs = std::abs (millisecs);
return String::formatted ("%02d:%02d:%02d.%03d",
millisecs / 360000,
(absMillisecs / 60000) % 60,
(absMillisecs / 1000) % 60,
absMillisecs % 1000);
}
// A quick-and-dirty function to format a bars/beats string.
String quarterNotePositionToBarsBeatsString (double quarterNotes, int numerator, int denominator)
{
if (numerator == 0 || denominator == 0)
return "1|1|000";
auto quarterNotesPerBar = (numerator * 4 / denominator);
auto beats = (fmod (quarterNotes, quarterNotesPerBar) / quarterNotesPerBar) * numerator;
auto bar = ((int) quarterNotes) / quarterNotesPerBar + 1;
auto beat = ((int) beats) + 1;
auto ticks = ((int) (fmod (beats, 1.0) * 960.0 + 0.5));
return String::formatted ("%d|%d|%03d", bar, beat, ticks);
}
void updateTransportTextDisplay()
{
MemoryOutputStream displayText;
displayText << "[" << SystemStats::getJUCEVersion() << "]\n"
<< String (lastPosInfo.bpm, 2) << " bpm\n"
<< lastPosInfo.timeSigNumerator << '/' << lastPosInfo.timeSigDenominator << "\n"
<< timeToTimecodeString (lastPosInfo.timeInSeconds) << "\n"
<< quarterNotePositionToBarsBeatsString (lastPosInfo.ppqPosition,
lastPosInfo.timeSigNumerator,
lastPosInfo.timeSigDenominator) << "\n";
if (lastPosInfo.isRecording)
displayText << "(recording)";
else if (lastPosInfo.isPlaying)
displayText << "(playing)";
transportText.setText (displayText.toString(), dontSendNotification);
}
void updateTransportButtonsDisplay()
{
auto visible = iaaEffectProcessor.getPlayHead() != nullptr
&& iaaEffectProcessor.getPlayHead()->canControlTransport();
if (rewindButton.isVisible() != visible)
{
rewindButton.setVisible (visible);
playButton.setVisible (visible);
recordButton.setVisible (visible);
}
if (visible)
{
auto playColour = lastPosInfo.isPlaying ? Colours::green : defaultButtonColour;
playButton.setColours (playColour, playColour, playColour);
playButton.repaint();
auto recordColour = lastPosInfo.isRecording ? Colours::red : defaultButtonColour;
recordButton.setColours (recordColour, recordColour, recordColour);
recordButton.repaint();
}
}
void updateSwitchToHostDisplay()
{
PluginHostType hostType;
auto visible = hostType.isInterAppAudioConnected();
if (switchToHostButtonLabel.isVisible() != visible)
{
switchToHostButtonLabel.setVisible (visible);
switchToHostButton.setVisible (visible);
if (visible)
{
auto icon = hostType.getHostIcon ((int) buttonSize);
switchToHostButton.setImages(false, true, true,
icon, 1.0, Colours::transparentBlack,
icon, 1.0, Colours::transparentBlack,
icon, 1.0, Colours::transparentBlack);
}
}
}
IAAEffectProcessor& iaaEffectProcessor;
AudioProcessorValueTreeState& parameters;
const float buttonSize = 30.0f;
const Colour defaultButtonColour = Colours::darkgrey;
ShapeButton rewindButton {"Rewind", defaultButtonColour, defaultButtonColour, defaultButtonColour};
ShapeButton playButton {"Play", defaultButtonColour, defaultButtonColour, defaultButtonColour};
ShapeButton recordButton {"Record", defaultButtonColour, defaultButtonColour, defaultButtonColour};
Slider gainSlider;
AudioProcessorValueTreeState::SliderAttachment gainAttachment = { parameters, "gain", gainSlider };
std::array<SimpleMeter, 2> meters;
ImageButton switchToHostButton;
Label transportText, switchToHostButtonLabel;
AudioPlayHead::CurrentPositionInfo lastPosInfo;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IAAEffectEditor)
};
//==============================================================================
AudioProcessorValueTreeState parameters;
float previousGain = 0.0f;
// This keeps a copy of the last set of timing info that was acquired during an
// audio callback - the UI component will display this.
AudioPlayHead::CurrentPositionInfo lastPosInfo;
ListenerList<MeterListener> meterListeners;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IAAEffectProcessor)
};

View File

@ -1,345 +1,351 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - 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, vs2019, 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)
{
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);
if (onChange != nullptr)
onChange();
}
void clear()
{
messages.clear();
if (onChange != nullptr)
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 : 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 ("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,
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 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 : public AudioProcessor,
private Timer
{
public:
MidiLoggerPluginDemoProcessor()
: AudioProcessor (getBusesLayout())
{
state.addChild ({ "uiState", { { "width", 500 }, { "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 : 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 doesn't like to load midi-only plugins, so we add an audio output there.
return PluginHostType().isAbletonLive() ? 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)
};
/*
==============================================================================
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);
if (onChange != nullptr)
onChange();
}
void clear()
{
messages.clear();
if (onChange != nullptr)
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 : 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 : 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 : 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 doesn't like to load midi-only plugins, so we add an audio output there.
return PluginHostType().isAbletonLive() ? 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)
};

View File

@ -1,201 +1,208 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - 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, vs2019
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 : 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);
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
{
for (const auto& bus : layout.outputBuses)
if (bus != AudioChannelSet::stereo())
return false;
return layout.inputBuses.isEmpty() && 1 <= layout.outputBuses.size();
}
//==============================================================================
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)
};
/*
==============================================================================
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 : 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 == 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)
};

View File

@ -1,151 +1,151 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - 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, vs2019
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: NoiseGate
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class NoiseGate : public AudioProcessor
{
public:
//==============================================================================
NoiseGate()
: AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo())
.withOutput ("Output", AudioChannelSet::stereo())
.withInput ("Sidechain", AudioChannelSet::stereo()))
{
addParameter (threshold = new AudioParameterFloat ("threshold", "Threshold", 0.0f, 1.0f, 0.5f));
addParameter (alpha = new AudioParameterFloat ("alpha", "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)
};
/*
==============================================================================
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 : 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)
};

View File

@ -1,423 +1,423 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - 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, vs2019, linux_make
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: ReaperEmbeddedViewDemo
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
/* This demo shows how to use the VSTCallbackHandler 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;
};
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnon-virtual-dtor")
//==============================================================================
class EmbeddedUI : 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 Editor : 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] { if (globalBypass != nullptr) 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 : public AudioProcessor,
public VSTCallbackHandler,
public VST3ClientExtensions,
private EmbeddedViewListener,
private Timer
{
public:
ReaperEmbeddedViewDemo()
{
addParameter (gain = new AudioParameterFloat ("gain", "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());
}
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"))
globalBypassFn = reinterpret_cast<void (*) (int)> (fnPtr);
}
}
pointer_sized_int handleVstPluginCanDo (int32, pointer_sized_int, void* ptr, float) override
{
if (auto* str = static_cast<const char*> (ptr))
{
if (strcmp (str, "hasCockosEmbeddedUI") == 0)
return 0xbeef0000;
if (strcmp (str, "hasCockosExtensions") == 0)
return 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) handledEmbeddedUIMessage ((int) opt,
(Steinberg::TPtrInt) value,
(Steinberg::TPtrInt) ptr);
}
void handleVstHostCallbackAvailable (std::function<VstHostCallbackType>&& hostcb) override
{
char functionName[] = "BypassFxAllTracks";
globalBypassFn = reinterpret_cast<void (*) (int)> (hostcb ((int32_t) 0xdeadbeef, (int32_t) 0xdeadf00d, 0, functionName, 0.0));
}
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;
}
AudioParameterFloat* gain = nullptr;
void (*globalBypassFn) (int) = nullptr;
EmbeddedUI embeddedUi { *this };
std::atomic<float> storedLevel { 0.0f };
float levelToDraw = 0.0f;
};
/*
==============================================================================
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 VSTCallbackHandler 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;
};
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnon-virtual-dtor")
//==============================================================================
class EmbeddedUI : 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 Editor : 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] { if (globalBypass != nullptr) 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 : public AudioProcessor,
public VSTCallbackHandler,
public VST3ClientExtensions,
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());
}
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"))
globalBypassFn = reinterpret_cast<void (*) (int)> (fnPtr);
}
}
pointer_sized_int handleVstPluginCanDo (int32, pointer_sized_int, void* ptr, float) override
{
if (auto* str = static_cast<const char*> (ptr))
{
if (strcmp (str, "hasCockosEmbeddedUI") == 0)
return 0xbeef0000;
if (strcmp (str, "hasCockosExtensions") == 0)
return 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) handledEmbeddedUIMessage ((int) opt,
(Steinberg::TPtrInt) value,
(Steinberg::TPtrInt) ptr);
}
void handleVstHostCallbackAvailable (std::function<VstHostCallbackType>&& hostcb) override
{
char functionName[] = "BypassFxAllTracks";
globalBypassFn = reinterpret_cast<void (*) (int)> (hostcb ((int32_t) 0xdeadbeef, (int32_t) 0xdeadf00d, 0, functionName, 0.0));
}
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;
}
AudioParameterFloat* gain = nullptr;
void (*globalBypassFn) (int) = nullptr;
EmbeddedUI embeddedUi { *this };
std::atomic<float> storedLevel { 0.0f };
float levelToDraw = 0.0f;
};

File diff suppressed because it is too large Load Diff

View File

@ -1,337 +1,461 @@
/*
==============================================================================
This file is part of the JUCE examples.
Copyright (c) 2020 - 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, vs2019, linux_make
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: SurroundProcessor
useLocalCopy: 1
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
//==============================================================================
class ChannelClickListener
{
public:
virtual ~ChannelClickListener() {}
virtual void channelButtonClicked (int channelIndex) = 0;
virtual bool isChannelActive (int channelIndex) = 0;
};
class SurroundEditor : public AudioProcessorEditor,
private Timer
{
public:
SurroundEditor (AudioProcessor& parent)
: AudioProcessorEditor (parent),
currentChannelLayout (AudioChannelSet::disabled()),
layoutTitle ("LayoutTitleLabel", getLayoutName())
{
layoutTitle.setJustificationType (Justification::centred);
addAndMakeVisible (layoutTitle);
addAndMakeVisible (noChannelsLabel);
setSize (600, 100);
lastSuspended = ! getAudioProcessor()->isSuspended();
timerCallback();
startTimer (500);
}
void resized() override
{
auto r = getLocalBounds();
layoutTitle.setBounds (r.removeFromBottom (16));
noChannelsLabel.setBounds (r);
if (channelButtons.size() > 0)
{
auto buttonWidth = r.getWidth() / channelButtons.size();
for (auto channelButton : channelButtons)
channelButton->setBounds (r.removeFromLeft (buttonWidth));
}
}
void paint (Graphics& g) override
{
g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
}
void updateButton (Button* btn)
{
if (auto* textButton = dynamic_cast<TextButton*> (btn))
{
auto channelIndex = channelButtons.indexOf (textButton);
if (auto* listener = dynamic_cast<ChannelClickListener*> (getAudioProcessor()))
listener->channelButtonClicked (channelIndex);
}
}
void updateGUI()
{
const auto& channelSet = getAudioProcessor()->getChannelLayoutOfBus (false, 0);
if (channelSet != currentChannelLayout)
{
currentChannelLayout = channelSet;
layoutTitle.setText (currentChannelLayout.getDescription(), NotificationType::dontSendNotification);
channelButtons.clear();
activeChannels.resize (currentChannelLayout.size());
if (currentChannelLayout == AudioChannelSet::disabled())
{
noChannelsLabel.setVisible (true);
}
else
{
auto numChannels = currentChannelLayout.size();
for (auto i = 0; i < numChannels; ++i)
{
auto channelName =
AudioChannelSet::getAbbreviatedChannelTypeName (currentChannelLayout.getTypeOfChannel (i));
TextButton* newButton;
channelButtons.add (newButton = new TextButton (channelName, channelName));
newButton->onClick = [this, newButton] { updateButton (newButton); };
addAndMakeVisible (newButton);
}
noChannelsLabel.setVisible (false);
resized();
}
if (auto* listener = dynamic_cast<ChannelClickListener*> (getAudioProcessor()))
{
auto activeColour = getLookAndFeel().findColour (Slider::thumbColourId);
auto inactiveColour = getLookAndFeel().findColour (Slider::trackColourId);
for (auto i = 0; i < activeChannels.size(); ++i)
{
auto isActive = listener->isChannelActive (i);
activeChannels.getReference (i) = isActive;
channelButtons[i]->setColour (TextButton::buttonColourId, isActive ? activeColour : inactiveColour);
channelButtons[i]->repaint();
}
}
}
}
private:
String getLayoutName() const
{
if (auto* p = getAudioProcessor())
return p->getChannelLayoutOfBus (false, 0).getDescription();
return "Unknown";
}
void timerCallback() override
{
if (getAudioProcessor()->isSuspended() != lastSuspended)
{
lastSuspended = getAudioProcessor()->isSuspended();
updateGUI();
}
if (! lastSuspended)
{
if (auto* listener = dynamic_cast<ChannelClickListener*> (getAudioProcessor()))
{
auto activeColour = getLookAndFeel().findColour (Slider::thumbColourId);
auto inactiveColour = getLookAndFeel().findColour (Slider::trackColourId);
for (auto i = 0; i < activeChannels.size(); ++i)
{
auto isActive = listener->isChannelActive (i);
if (activeChannels.getReference (i) != isActive)
{
activeChannels.getReference (i) = isActive;
channelButtons[i]->setColour (TextButton::buttonColourId, isActive ? activeColour : inactiveColour);
channelButtons[i]->repaint();
}
}
}
}
}
AudioChannelSet currentChannelLayout;
Label noChannelsLabel { "noChannelsLabel", "Input disabled" },
layoutTitle;
OwnedArray<TextButton> channelButtons;
Array<bool> activeChannels;
bool lastSuspended;
};
//==============================================================================
class SurroundProcessor : public AudioProcessor,
public ChannelClickListener,
private AsyncUpdater
{
public:
SurroundProcessor()
: AudioProcessor(BusesProperties().withInput ("Input", AudioChannelSet::stereo())
.withOutput ("Output", AudioChannelSet::stereo()))
{}
//==============================================================================
void prepareToPlay (double sampleRate, int samplesPerBlock) override
{
channelClicked = 0;
sampleOffset = static_cast<int> (std::ceil (sampleRate));
auto numChannels = getChannelCountOfBus (true, 0);
channelActive.resize (numChannels);
alphaCoeffs .resize (numChannels);
reset();
triggerAsyncUpdate();
ignoreUnused (samplesPerBlock);
}
void releaseResources() override { reset(); }
void processBlock (AudioBuffer<float>& buffer, MidiBuffer&) override
{
for (auto ch = 0; ch < buffer.getNumChannels(); ++ch)
{
auto& channelTime = channelActive.getReference (ch);
auto& alpha = alphaCoeffs .getReference (ch);
for (auto j = 0; j < buffer.getNumSamples(); ++j)
{
auto sample = buffer.getReadPointer (ch)[j];
alpha = (0.8f * alpha) + (0.2f * sample);
if (std::abs (alpha) >= 0.1f)
channelTime = static_cast<int> (getSampleRate() / 2.0);
}
channelTime = jmax (0, channelTime - buffer.getNumSamples());
}
auto fillSamples = jmin (static_cast<int> (std::ceil (getSampleRate())) - sampleOffset,
buffer.getNumSamples());
if (isPositiveAndBelow (channelClicked, buffer.getNumChannels()))
{
auto* channelBuffer = buffer.getWritePointer (channelClicked);
auto freq = (float) (440.0 / getSampleRate());
for (auto i = 0; i < fillSamples; ++i)
channelBuffer[i] += std::sin (MathConstants<float>::twoPi * freq * static_cast<float> (sampleOffset++));
}
}
using AudioProcessor::processBlock;
//==============================================================================
AudioProcessorEditor* createEditor() override { return new SurroundEditor (*this); }
bool hasEditor() const override { return true; }
//==============================================================================
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
return ((! layouts.getMainInputChannelSet() .isDiscreteLayout())
&& (! layouts.getMainOutputChannelSet().isDiscreteLayout())
&& (layouts.getMainInputChannelSet() == layouts.getMainOutputChannelSet())
&& (! layouts.getMainInputChannelSet().isDisabled()));
}
void reset() override
{
for (auto& channel : channelActive)
channel = 0;
}
//==============================================================================
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 channelIndex) override
{
channelClicked = channelIndex;
sampleOffset = 0;
}
bool isChannelActive (int channelIndex) override
{
return channelActive[channelIndex] > 0;
}
void handleAsyncUpdate() override
{
if (auto* editor = getActiveEditor())
if (auto* surroundEditor = dynamic_cast<SurroundEditor*> (editor))
surroundEditor->updateGUI();
}
private:
Array<int> channelActive;
Array<float> alphaCoeffs;
int channelClicked;
int sampleOffset;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SurroundProcessor)
};
/*
==============================================================================
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) override
{
// 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 : 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 : 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 = [&proc = processor, bus = bus, i] { proc.channelButtonClicked (bus, 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 : 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 : public ProcessorWithLevels
{
AudioProcessorEditor* createEditor() override { return new SurroundEditor (*this); }
bool hasEditor() const override { return true; }
};

View File

@ -1,140 +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
#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

View File

@ -1,31 +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
#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