/* ============================================================================== This file was auto-generated by the Jucer! It contains the basic startup code for a Juce application. ============================================================================== */ #include "PluginProcessor.h" #include "PluginEditor.h" AudioProcessor* JUCE_CALLTYPE createPluginFilter(); //============================================================================== /** A demo synth sound that's just a basic sine wave.. */ class SineWaveSound : public SynthesiserSound { public: SineWaveSound() {} bool appliesToNote (int /*midiNoteNumber*/) override { return true; } bool appliesToChannel (int /*midiChannel*/) override { return true; } }; //============================================================================== /** A simple demo synth voice that just plays a sine wave.. */ class SineWaveVoice : public SynthesiserVoice { public: SineWaveVoice() : angleDelta (0.0), tailOff (0.0) { } 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 { // can't be bothered implementing this for the demo! } void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override { // not interested in controllers in this case. } void renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) override { processBlock (outputBuffer, startSample, numSamples); } void renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) override { processBlock (outputBuffer, startSample, numSamples); } private: template void processBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) { if (angleDelta != 0.0) { if (tailOff > 0) { while (--numSamples >= 0) { const FloatType currentSample = static_cast (std::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) { clearCurrentNote(); angleDelta = 0.0; break; } } } else { while (--numSamples >= 0) { const FloatType currentSample = static_cast (std::sin (currentAngle) * level); for (int i = outputBuffer.getNumChannels(); --i >= 0;) outputBuffer.addSample (i, startSample, currentSample); currentAngle += angleDelta; ++startSample; } } } } double currentAngle, angleDelta, level, tailOff; }; //============================================================================== JuceDemoPluginAudioProcessor::JuceDemoPluginAudioProcessor() : AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true) .withOutput ("Output", AudioChannelSet::stereo(), true)), lastUIWidth (400), lastUIHeight (200), gainParam (nullptr), delayParam (nullptr), delayPosition (0) { lastPosInfo.resetToDefault(); // This creates our parameters. We'll keep some raw pointers to them in this class, // so that we can easily access them later, but the base class will take care of // deleting them for us. addParameter (gainParam = new AudioParameterFloat ("gain", "Gain", 0.0f, 1.0f, 0.9f)); addParameter (delayParam = new AudioParameterFloat ("delay", "Delay Feedback", 0.0f, 1.0f, 0.5f)); initialiseSynth(); } JuceDemoPluginAudioProcessor::~JuceDemoPluginAudioProcessor() { } void JuceDemoPluginAudioProcessor::initialiseSynth() { 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()); } //============================================================================== bool JuceDemoPluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const { // Only mono/stereo and input/output must have same layout const AudioChannelSet& mainInput = layouts.getMainInputChannelSet(); const AudioChannelSet& mainOutput = layouts.getMainOutputChannelSet(); // input and output layout must be the same if (mainInput != mainOutput) return false; // do not allow disabling the main buses if (mainInput.isDisabled()) return false; // only allow stereo and mono if (mainInput.size() > 2) return false; return true; } void JuceDemoPluginAudioProcessor::prepareToPlay (double newSampleRate, int /*samplesPerBlock*/) { // Use this method as the place to do any pre-playback // initialisation that you need.. synth.setCurrentPlaybackSampleRate (newSampleRate); keyboardState.reset(); if (isUsingDoublePrecision()) { delayBufferDouble.setSize (2, 12000); delayBufferFloat.setSize (1, 1); } else { delayBufferFloat.setSize (2, 12000); delayBufferDouble.setSize (1, 1); } reset(); } void JuceDemoPluginAudioProcessor::releaseResources() { // When playback stops, you can use this as an opportunity to free up any // spare memory, etc. keyboardState.reset(); } void JuceDemoPluginAudioProcessor::reset() { // Use this method as the place to clear any delay lines, buffers, etc, as it // means there's been a break in the audio's continuity. delayBufferFloat.clear(); delayBufferDouble.clear(); } template void JuceDemoPluginAudioProcessor::process (AudioBuffer& buffer, MidiBuffer& midiMessages, AudioBuffer& delayBuffer) { const int numSamples = buffer.getNumSamples(); // apply our gain-change to the incoming data.. applyGain (buffer, delayBuffer); // Now pass any incoming midi messages to our keyboard state object, and let it // add messages to the buffer if the user is clicking on the on-screen keys keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true); // and now get our synth to process these midi events and generate its output. synth.renderNextBlock (buffer, midiMessages, 0, numSamples); // Apply our delay effect to the new output.. applyDelay (buffer, delayBuffer); // In case we have more outputs than inputs, we'll clear any output // channels that didn't contain input data, (because these aren't // guaranteed to be empty - they may contain garbage). for (int i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i) buffer.clear (i, 0, numSamples); // Now ask the host for the current time so we can store it to be displayed later... updateCurrentTimeInfoFromHost(); } template void JuceDemoPluginAudioProcessor::applyGain (AudioBuffer& buffer, AudioBuffer& delayBuffer) { ignoreUnused (delayBuffer); const float gainLevel = *gainParam; for (int channel = 0; channel < getTotalNumInputChannels(); ++channel) buffer.applyGain (channel, 0, buffer.getNumSamples(), gainLevel); } template void JuceDemoPluginAudioProcessor::applyDelay (AudioBuffer& buffer, AudioBuffer& delayBuffer) { const int numSamples = buffer.getNumSamples(); const float delayLevel = *delayParam; int delayPos = 0; for (int channel = 0; channel < getTotalNumInputChannels(); ++channel) { FloatType* const channelData = buffer.getWritePointer (channel); FloatType* const delayData = delayBuffer.getWritePointer (jmin (channel, delayBuffer.getNumChannels() - 1)); delayPos = delayPosition; for (int i = 0; i < numSamples; ++i) { const FloatType in = channelData[i]; channelData[i] += delayData[delayPos]; delayData[delayPos] = (delayData[delayPos] + in) * delayLevel; if (++delayPos >= delayBuffer.getNumSamples()) delayPos = 0; } } delayPosition = delayPos; } void JuceDemoPluginAudioProcessor::updateCurrentTimeInfoFromHost() { if (AudioPlayHead* ph = getPlayHead()) { AudioPlayHead::CurrentPositionInfo newTime; if (ph->getCurrentPosition (newTime)) { lastPosInfo = newTime; // Successfully got the current time from the host.. return; } } // If the host fails to provide the current time, we'll just reset our copy to a default.. lastPosInfo.resetToDefault(); } //============================================================================== AudioProcessorEditor* JuceDemoPluginAudioProcessor::createEditor() { return new JuceDemoPluginAudioProcessorEditor (*this); } //============================================================================== void JuceDemoPluginAudioProcessor::getStateInformation (MemoryBlock& destData) { // You should use this method to store your parameters in the memory block. // Here's an example of how you can use XML to make it easy and more robust: // Create an outer XML element.. XmlElement xml ("MYPLUGINSETTINGS"); // add some attributes to it.. xml.setAttribute ("uiWidth", lastUIWidth); xml.setAttribute ("uiHeight", lastUIHeight); // Store the values of all our parameters, using their param ID as the XML attribute for (int i = 0; i < getNumParameters(); ++i) if (AudioProcessorParameterWithID* p = dynamic_cast (getParameters().getUnchecked(i))) xml.setAttribute (p->paramID, p->getValue()); // then use this helper function to stuff it into the binary blob and return it.. copyXmlToBinary (xml, destData); } void JuceDemoPluginAudioProcessor::setStateInformation (const void* data, int sizeInBytes) { // You should use this method to restore your parameters from this memory block, // whose contents will have been created by the getStateInformation() call. // This getXmlFromBinary() helper function retrieves our XML from the binary blob.. ScopedPointer xmlState (getXmlFromBinary (data, sizeInBytes)); if (xmlState != nullptr) { // make sure that it's actually our type of XML object.. if (xmlState->hasTagName ("MYPLUGINSETTINGS")) { // ok, now pull out our last window size.. lastUIWidth = jmax (xmlState->getIntAttribute ("uiWidth", lastUIWidth), 400); lastUIHeight = jmax (xmlState->getIntAttribute ("uiHeight", lastUIHeight), 200); // Now reload our parameters.. for (int i = 0; i < getNumParameters(); ++i) if (AudioProcessorParameterWithID* p = dynamic_cast (getParameters().getUnchecked(i))) p->setValue ((float) xmlState->getDoubleAttribute (p->paramID, p->getValue())); } } } //============================================================================== // This creates new instances of the plugin.. AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new JuceDemoPluginAudioProcessor(); }