migrating to the latest JUCE version
This commit is contained in:
1449
deps/juce/examples/Plugins/ARAPluginDemo.h
vendored
Normal file
1449
deps/juce/examples/Plugins/ARAPluginDemo.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
954
deps/juce/examples/Plugins/AUv3SynthPluginDemo.h
vendored
954
deps/juce/examples/Plugins/AUv3SynthPluginDemo.h
vendored
@ -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)
|
||||
};
|
||||
|
332
deps/juce/examples/Plugins/ArpeggiatorPluginDemo.h
vendored
332
deps/juce/examples/Plugins/ArpeggiatorPluginDemo.h
vendored
@ -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)
|
||||
};
|
||||
|
1338
deps/juce/examples/Plugins/AudioPluginDemo.h
vendored
1338
deps/juce/examples/Plugins/AudioPluginDemo.h
vendored
File diff suppressed because it is too large
Load Diff
8
deps/juce/examples/Plugins/CMakeLists.txt
vendored
8
deps/juce/examples/Plugins/CMakeLists.txt
vendored
@ -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
|
||||
|
4284
deps/juce/examples/Plugins/DSPModulePluginDemo.h
vendored
4284
deps/juce/examples/Plugins/DSPModulePluginDemo.h
vendored
File diff suppressed because it is too large
Load Diff
244
deps/juce/examples/Plugins/GainPluginDemo.h
vendored
244
deps/juce/examples/Plugins/GainPluginDemo.h
vendored
@ -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)
|
||||
};
|
||||
|
588
deps/juce/examples/Plugins/HostPluginDemo.h
vendored
Normal file
588
deps/juce/examples/Plugins/HostPluginDemo.h
vendored
Normal 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); }
|
||||
};
|
@ -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)
|
||||
};
|
696
deps/juce/examples/Plugins/MidiLoggerPluginDemo.h
vendored
696
deps/juce/examples/Plugins/MidiLoggerPluginDemo.h
vendored
@ -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)
|
||||
};
|
||||
|
409
deps/juce/examples/Plugins/MultiOutSynthPluginDemo.h
vendored
409
deps/juce/examples/Plugins/MultiOutSynthPluginDemo.h
vendored
@ -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)
|
||||
};
|
||||
|
302
deps/juce/examples/Plugins/NoiseGatePluginDemo.h
vendored
302
deps/juce/examples/Plugins/NoiseGatePluginDemo.h
vendored
@ -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)
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
5213
deps/juce/examples/Plugins/SamplerPluginDemo.h
vendored
5213
deps/juce/examples/Plugins/SamplerPluginDemo.h
vendored
File diff suppressed because it is too large
Load Diff
798
deps/juce/examples/Plugins/SurroundPluginDemo.h
vendored
798
deps/juce/examples/Plugins/SurroundPluginDemo.h
vendored
@ -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; }
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user