/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2017 - ROLI Ltd. 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 5 End-User License Agreement and JUCE 5 Privacy Policy (both updated and effective as of the 27th April 2017). End User License Agreement: www.juce.com/juce-5-licence Privacy Policy: www.juce.com/juce-5-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ #include "../JuceLibraryCode/JuceHeader.h" #include "InternalPlugins.h" #include "PluginGraph.h" //============================================================================== class InternalPlugin : public AudioPluginInstance { protected: InternalPlugin (const PluginDescription& descr, const AudioChannelSet& channelSetToUse = AudioChannelSet::stereo()) : AudioPluginInstance (getBusProperties (descr.numInputChannels == 0, channelSetToUse)), name (descr.fileOrIdentifier.upToFirstOccurrenceOf (":", false, false)), state (descr.fileOrIdentifier.fromFirstOccurrenceOf (":", false, false)), isGenerator (descr.numInputChannels == 0), hasMidi (descr.isInstrument), channelSet (channelSetToUse) { jassert (channelSetToUse.size() == descr.numOutputChannels); } public: //============================================================================== const String getName() const override { return name; } double getTailLengthSeconds() const override { return 0.0; } bool acceptsMidi() const override { return hasMidi; } bool producesMidi() const override { return hasMidi; } AudioProcessorEditor* createEditor() override { return nullptr; } bool hasEditor() const override { return false; } int getNumPrograms() override { return 0; } int getCurrentProgram() override { return 0; } void setCurrentProgram (int) override {} const String getProgramName (int) override { return {}; } void changeProgramName (int, const String&) override {} void getStateInformation (juce::MemoryBlock&) override {} void setStateInformation (const void*, int) override {} //============================================================================== bool isBusesLayoutSupported (const BusesLayout& layout) const override { if (! isGenerator) if (layout.getMainOutputChannelSet() != channelSet) return false; if (layout.getMainInputChannelSet() != channelSet) return false; return true; } //============================================================================== void fillInPluginDescription (PluginDescription& description) const override { description = getPluginDescription (name + ":" + state, isGenerator, hasMidi, channelSet); } static PluginDescription getPluginDescription (const String& identifier, bool registerAsGenerator, bool acceptsMidi, const AudioChannelSet& channelSetToUse = AudioChannelSet::stereo()) { PluginDescription descr; auto pluginName = identifier.upToFirstOccurrenceOf (":", false, false); auto pluginState = identifier.fromFirstOccurrenceOf (":", false, false); descr.name = pluginName; descr.descriptiveName = pluginName; descr.pluginFormatName = "Internal"; descr.category = (registerAsGenerator ? (acceptsMidi ? "Synth" : "Generator") : "Effect"); descr.manufacturerName = "JUCE"; descr.version = ProjectInfo::versionString; descr.fileOrIdentifier = pluginName + ":" + pluginState; descr.uid = pluginName.hashCode(); descr.isInstrument = (acceptsMidi && registerAsGenerator); descr.numInputChannels = (registerAsGenerator ? 0 : channelSetToUse.size()); descr.numOutputChannels = channelSetToUse.size(); return descr; } private: static BusesProperties getBusProperties (bool registerAsGenerator, const AudioChannelSet& channelSetToUse) { return registerAsGenerator ? BusesProperties().withOutput ("Output", channelSetToUse) : BusesProperties().withInput ("Input", channelSetToUse) .withOutput ("Output", channelSetToUse); } //============================================================================== String name, state; bool isGenerator, hasMidi; AudioChannelSet channelSet; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InternalPlugin) }; //============================================================================== class SineWaveSynth : public InternalPlugin { public: SineWaveSynth (const PluginDescription& descr) : InternalPlugin (descr) { const int numVoices = 8; // Add some voices... for (int i = numVoices; --i >= 0;) synth.addVoice (new SineWaveVoice()); // ..and give the synth a sound to play synth.addSound (new SineWaveSound()); } static String getIdentifier() { return "Sine Wave Synth"; } static PluginDescription getPluginDescription() { return InternalPlugin::getPluginDescription (getIdentifier(), true, true); } //============================================================================== void prepareToPlay (double newSampleRate, int) override { synth.setCurrentPlaybackSampleRate (newSampleRate); } void releaseResources() override {} //============================================================================== void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override { const int numSamples = buffer.getNumSamples(); buffer.clear(); synth.renderNextBlock (buffer, midiMessages, 0, numSamples); buffer.applyGain (0.8f); } using InternalPlugin::processBlock; private: //============================================================================== struct SineWaveSound : public SynthesiserSound { SineWaveSound() = default; bool appliesToNote (int /*midiNoteNumber*/) override { return true; } bool appliesToChannel (int /*midiChannel*/) override { return true; } }; struct SineWaveVoice : public SynthesiserVoice { SineWaveVoice() = default; bool canPlaySound (SynthesiserSound* sound) override { return dynamic_cast (sound) != nullptr; } void startNote (int midiNoteNumber, float velocity, SynthesiserSound* /*sound*/, int /*currentPitchWheelPosition*/) override { currentAngle = 0.0; level = velocity * 0.15; tailOff = 0.0; double cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber); double cyclesPerSample = cyclesPerSecond / getSampleRate(); angleDelta = cyclesPerSample * 2.0 * double_Pi; } void stopNote (float /*velocity*/, bool allowTailOff) override { if (allowTailOff) { // start a tail-off by setting this flag. The render callback will pick up on // this and do a fade out, calling clearCurrentNote() when it's finished. if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the // stopNote method could be called more than once. tailOff = 1.0; } else { // we're being told to stop playing immediately, so reset everything.. clearCurrentNote(); angleDelta = 0.0; } } void pitchWheelMoved (int /*newValue*/) override { // not implemented for the purposes of this demo! } void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override { // not implemented for the purposes of this demo! } void renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) override { if (angleDelta != 0.0) { if (tailOff > 0) { while (--numSamples >= 0) { const float currentSample = (float) (sin (currentAngle) * level * tailOff); for (int i = outputBuffer.getNumChannels(); --i >= 0;) outputBuffer.addSample (i, startSample, currentSample); currentAngle += angleDelta; ++startSample; tailOff *= 0.99; if (tailOff <= 0.005) { // tells the synth that this voice has stopped clearCurrentNote(); angleDelta = 0.0; break; } } } else { while (--numSamples >= 0) { const float currentSample = (float) (sin (currentAngle) * level); for (int i = outputBuffer.getNumChannels(); --i >= 0;) outputBuffer.addSample (i, startSample, currentSample); currentAngle += angleDelta; ++startSample; } } } } using SynthesiserVoice::renderNextBlock; private: double currentAngle = 0, angleDelta = 0, level = 0, tailOff = 0; }; //============================================================================== Synthesiser synth; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SineWaveSynth) }; //============================================================================== class ReverbPlugin : public InternalPlugin { public: ReverbPlugin (const PluginDescription& descr) : InternalPlugin (descr) {} static String getIdentifier() { return "Reverb"; } static PluginDescription getPluginDescription() { return InternalPlugin::getPluginDescription (getIdentifier(), false, false); } void prepareToPlay (double newSampleRate, int) override { reverb.setSampleRate (newSampleRate); } void reset() override { reverb.reset(); } void releaseResources() override {} void processBlock (AudioBuffer& buffer, MidiBuffer&) override { auto numChannels = buffer.getNumChannels(); if (numChannels == 1) reverb.processMono (buffer.getWritePointer (0), buffer.getNumSamples()); else reverb.processStereo (buffer.getWritePointer (0), buffer.getWritePointer (1), buffer.getNumSamples()); for (int ch = 2; ch < numChannels; ++ch) buffer.clear (ch, 0, buffer.getNumSamples()); } using InternalPlugin::processBlock; private: Reverb reverb; }; //============================================================================== InternalPluginFormat::InternalPluginFormat() { { AudioProcessorGraph::AudioGraphIOProcessor p (AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode); p.fillInPluginDescription (audioOutDesc); } { AudioProcessorGraph::AudioGraphIOProcessor p (AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode); p.fillInPluginDescription (audioInDesc); } { AudioProcessorGraph::AudioGraphIOProcessor p (AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode); p.fillInPluginDescription (midiInDesc); } } std::unique_ptr InternalPluginFormat::createInstance (const String& name) { if (name == audioOutDesc.name) return std::make_unique (AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode); if (name == audioInDesc.name) return std::make_unique (AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode); if (name == midiInDesc.name) return std::make_unique (AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode); if (name == SineWaveSynth::getIdentifier()) return std::make_unique (SineWaveSynth::getPluginDescription()); if (name == ReverbPlugin::getIdentifier()) return std::make_unique (ReverbPlugin::getPluginDescription()); return {}; } void InternalPluginFormat::createPluginInstance (const PluginDescription& desc, double /*initialSampleRate*/, int /*initialBufferSize*/, PluginCreationCallback callback) { if (auto p = createInstance (desc.name)) callback (std::move (p), {}); else callback (nullptr, NEEDS_TRANS ("Invalid internal plugin name")); } bool InternalPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const { return false; } void InternalPluginFormat::getAllTypes (Array& results) { results.add (audioInDesc, audioOutDesc, midiInDesc, SineWaveSynth::getPluginDescription(), ReverbPlugin::getPluginDescription()); }