paulxstretch/deps/juce/examples/Plugins/AUv3SynthPluginDemo.h

478 lines
18 KiB
C
Raw Normal View History

/*
==============================================================================
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)
};