/* ============================================================================== This file is part of the JUCE examples. Copyright (c) 2017 - ROLI Ltd. 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: DSPModulePluginDemo version: 1.0.0 vendor: JUCE website: http://juce.com description: Audio plugin using the DSP module. 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_dsp, juce_events, juce_graphics, juce_gui_basics, juce_gui_extra exporters: xcode_mac, vs2017 moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 type: AudioProcessor mainClass: DspModulePluginDemoAudioProcessor useLocalCopy: 1 END_JUCE_PIP_METADATA *******************************************************************************/ #pragma once #include "../Assets/DemoUtilities.h" //============================================================================== struct ParameterSlider : public Slider, public Timer { ParameterSlider (AudioProcessorParameter& p) : Slider (p.getName (256)), param (p) { setRange (0.0, 1.0, 0.0); startTimerHz (30); updateSliderPos(); } void valueChanged() override { if (isMouseButtonDown()) param.setValueNotifyingHost ((float) Slider::getValue()); else param.setValue ((float) Slider::getValue()); } void timerCallback() override { updateSliderPos(); } void startedDragging() override { param.beginChangeGesture(); } void stoppedDragging() override { param.endChangeGesture(); } double getValueFromText (const String& text) override { return param.getValueForText (text); } String getTextFromValue (double value) override { return param.getText ((float) value, 1024) + " " + param.getLabel(); } void updateSliderPos() { auto newValue = param.getValue(); if (newValue != (float) Slider::getValue() && ! isMouseButtonDown()) Slider::setValue (newValue); } AudioProcessorParameter& param; }; //============================================================================== /** This class handles the audio processing for the DSP module plugin demo. */ class DspModulePluginDemoAudioProcessor : public AudioProcessor { public: //============================================================================== DspModulePluginDemoAudioProcessor() : AudioProcessor (BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true) .withOutput ("Output", AudioChannelSet::stereo(), true)), lowPassFilter (dsp::IIR::Coefficients::makeFirstOrderLowPass (48000.0, 20000.0f)), highPassFilter (dsp::IIR::Coefficients::makeFirstOrderHighPass (48000.0, 20.0f)), waveShapers { { std::tanh }, { dsp::FastMathApproximations::tanh } }, clipping { clip } { // Oversampling 2 times with IIR filtering oversampling.reset (new dsp::Oversampling (2, 1, dsp::Oversampling::filterHalfBandPolyphaseIIR, false)); addParameter (inputVolumeParam = new AudioParameterFloat ("INPUT", "Input Volume", { 0.0f, 60.0f, 0.0f, 1.0f }, 0.0f, "dB")); addParameter (highPassFilterFreqParam = new AudioParameterFloat ("HPFREQ", "Pre Highpass Freq.", { 20.0f, 20000.0f, 0.0f, 0.5f }, 20.0f, "Hz")); addParameter (lowPassFilterFreqParam = new AudioParameterFloat ("LPFREQ", "Post Lowpass Freq.", { 20.0f, 20000.0f, 0.0f, 0.5f }, 20000.0f, "Hz")); addParameter (stereoParam = new AudioParameterChoice ("STEREO", "Stereo Processing", { "Always mono", "Yes" }, 1)); addParameter (slopeParam = new AudioParameterChoice ("SLOPE", "Slope", { "-6 dB / octave", "-12 dB / octave" }, 0)); addParameter (waveshaperParam = new AudioParameterChoice ("WVSHP", "Waveshaper", { "std::tanh", "Fast tanh approx." }, 0)); addParameter (cabinetTypeParam = new AudioParameterChoice ("CABTYPE", "Cabinet Type", { "Guitar amplifier 8'' cabinet ", "Cassette recorder cabinet" }, 0)); addParameter (cabinetSimParam = new AudioParameterBool ("CABSIM", "Cabinet Sim", false)); addParameter (oversamplingParam = new AudioParameterBool ("OVERS", "Oversampling", false)); addParameter (outputVolumeParam = new AudioParameterFloat ("OUTPUT", "Output Volume", { -40.0f, 40.0f, 0.0f, 1.0f }, 0.0f, "dB")); cabinetType.set (0); } ~DspModulePluginDemoAudioProcessor() {} //============================================================================== bool isBusesLayoutSupported (const BusesLayout& layouts) const override { // This is the place where you check if the layout is supported. // In this template code we only support mono or stereo. if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono() && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo()) return false; // This checks if the input layout matches the output layout if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) return false; return true; } void prepareToPlay (double sampleRate, int samplesPerBlock) override { auto channels = static_cast (jmin (getMainBusNumInputChannels(), getMainBusNumOutputChannels())); dsp::ProcessSpec spec { sampleRate, static_cast (samplesPerBlock), channels }; lowPassFilter .prepare (spec); highPassFilter.prepare (spec); inputVolume .prepare (spec); outputVolume.prepare (spec); convolution.prepare (spec); cabinetType.set (-1); oversampling->initProcessing (static_cast (samplesPerBlock)); updateParameters(); reset(); } void releaseResources() override {} void processBlock (AudioBuffer& inoutBuffer, MidiBuffer&) override { auto totalNumInputChannels = getTotalNumInputChannels(); auto totalNumOutputChannels = getTotalNumOutputChannels(); auto numSamples = inoutBuffer.getNumSamples(); for (auto i = jmin (2, totalNumInputChannels); i < totalNumOutputChannels; ++i) inoutBuffer.clear (i, 0, numSamples); updateParameters(); dsp::AudioBlock block (inoutBuffer); if (stereoParam->getIndex() == 1) { // Stereo processing mode: if (block.getNumChannels() > 2) block = block.getSubsetChannelBlock (0, 2); process (dsp::ProcessContextReplacing (block)); } else { // Mono processing mode: auto firstChan = block.getSingleChannelBlock (0); process (dsp::ProcessContextReplacing (firstChan)); for (size_t chan = 1; chan < block.getNumChannels(); ++chan) block.getSingleChannelBlock (chan).copy (firstChan); } } void reset() override { lowPassFilter .reset(); highPassFilter.reset(); convolution .reset(); oversampling->reset(); } //============================================================================== bool hasEditor() const override { return true; } AudioProcessorEditor* createEditor() override { return new DspModulePluginDemoAudioProcessorEditor (*this); } //============================================================================== bool acceptsMidi() const override { return false; } bool producesMidi() const override { return false; } const String getName() const override { return JucePlugin_Name; } 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 {}; } void changeProgramName (int, const String&) override {} //============================================================================== void getStateInformation (MemoryBlock&) override {} void setStateInformation (const void*, int) override {} //============================================================================== void updateParameters() { auto newOversampling = oversamplingParam->get(); if (newOversampling != audioCurrentlyOversampled) { audioCurrentlyOversampled = newOversampling; oversampling->reset(); } //============================================================================== auto inputdB = Decibels::decibelsToGain (inputVolumeParam->get()); auto outputdB = Decibels::decibelsToGain (outputVolumeParam->get()); if (inputVolume .getGainLinear() != inputdB) inputVolume.setGainLinear (inputdB); if (outputVolume.getGainLinear() != outputdB) outputVolume.setGainLinear (outputdB); auto newSlopeType = slopeParam->getIndex(); if (newSlopeType == 0) { *lowPassFilter .state = *dsp::IIR::Coefficients::makeFirstOrderLowPass (getSampleRate(), lowPassFilterFreqParam->get()); *highPassFilter.state = *dsp::IIR::Coefficients::makeFirstOrderHighPass (getSampleRate(), highPassFilterFreqParam->get()); } else { *lowPassFilter .state = *dsp::IIR::Coefficients::makeLowPass (getSampleRate(), lowPassFilterFreqParam->get()); *highPassFilter.state = *dsp::IIR::Coefficients::makeHighPass (getSampleRate(), highPassFilterFreqParam->get()); } //============================================================================== auto type = cabinetTypeParam->getIndex(); auto currentType = cabinetType.get(); if (type != currentType) { cabinetType.set (type); auto maxSize = static_cast (roundToInt (getSampleRate() * (8192.0 / 44100.0))); auto assetName = (type == 0 ? "Impulse1.wav" : "Impulse2.wav"); std::unique_ptr assetInputStream (createAssetInputStream (assetName)); if (assetInputStream != nullptr) { currentCabinetData.reset(); assetInputStream->readIntoMemoryBlock (currentCabinetData); convolution.loadImpulseResponse (currentCabinetData.getData(), currentCabinetData.getSize(), false, true, maxSize); } } cabinetIsBypassed = ! cabinetSimParam->get(); } static inline float clip (float x) { return jmax (-1.0f, jmin (1.0f, x)); } //============================================================================== AudioParameterFloat* inputVolumeParam; AudioParameterFloat* outputVolumeParam; AudioParameterFloat* lowPassFilterFreqParam; AudioParameterFloat* highPassFilterFreqParam; AudioParameterChoice* stereoParam; AudioParameterChoice* slopeParam; AudioParameterChoice* waveshaperParam; AudioParameterChoice* cabinetTypeParam; AudioParameterBool* cabinetSimParam; AudioParameterBool* oversamplingParam; private: //============================================================================== /** This is the editor component that will be displayed. */ class DspModulePluginDemoAudioProcessorEditor : public AudioProcessorEditor { public: //============================================================================== DspModulePluginDemoAudioProcessorEditor (DspModulePluginDemoAudioProcessor& p) : AudioProcessorEditor (&p), processor (p), inputVolumeLabel ({}, processor.inputVolumeParam->name), outputVolumeLabel ({}, processor.outputVolumeParam->name), lowPassFilterFreqLabel ({}, processor.lowPassFilterFreqParam->name), highPassFilterFreqLabel ({}, processor.highPassFilterFreqParam->name), stereoLabel ({}, processor.stereoParam->name), slopeLabel ({}, processor.slopeParam->name), waveshaperLabel ({}, processor.waveshaperParam->name), cabinetTypeLabel ({}, processor.cabinetTypeParam->name) { //============================================================================== inputVolumeSlider .reset (new ParameterSlider (*processor.inputVolumeParam)); outputVolumeSlider .reset (new ParameterSlider (*processor.outputVolumeParam)); lowPassFilterFreqSlider .reset (new ParameterSlider (*processor.lowPassFilterFreqParam)); highPassFilterFreqSlider.reset (new ParameterSlider (*processor.highPassFilterFreqParam)); addAndMakeVisible (inputVolumeSlider .get()); addAndMakeVisible (outputVolumeSlider .get()); addAndMakeVisible (lowPassFilterFreqSlider .get()); addAndMakeVisible (highPassFilterFreqSlider.get()); addAndMakeVisible (inputVolumeLabel); inputVolumeLabel.setJustificationType (Justification::centredLeft); inputVolumeLabel.attachToComponent (inputVolumeSlider.get(), true); addAndMakeVisible (outputVolumeLabel); outputVolumeLabel.setJustificationType (Justification::centredLeft); outputVolumeLabel.attachToComponent (outputVolumeSlider.get(), true); addAndMakeVisible (lowPassFilterFreqLabel); lowPassFilterFreqLabel.setJustificationType (Justification::centredLeft); lowPassFilterFreqLabel.attachToComponent (lowPassFilterFreqSlider.get(), true); addAndMakeVisible (highPassFilterFreqLabel); highPassFilterFreqLabel.setJustificationType (Justification::centredLeft); highPassFilterFreqLabel.attachToComponent (highPassFilterFreqSlider.get(), true); //============================================================================== addAndMakeVisible (stereoBox); auto i = 1; for (auto choice : processor.stereoParam->choices) stereoBox.addItem (choice, i++); stereoBox.onChange = [this] { processor.stereoParam->operator= (stereoBox.getSelectedItemIndex()); }; stereoBox.setSelectedId (processor.stereoParam->getIndex() + 1); addAndMakeVisible (stereoLabel); stereoLabel.setJustificationType (Justification::centredLeft); stereoLabel.attachToComponent (&stereoBox, true); //============================================================================== addAndMakeVisible(slopeBox); i = 1; for (auto choice : processor.slopeParam->choices) slopeBox.addItem(choice, i++); slopeBox.onChange = [this] { processor.slopeParam->operator= (slopeBox.getSelectedItemIndex()); }; slopeBox.setSelectedId(processor.slopeParam->getIndex() + 1); addAndMakeVisible(slopeLabel); slopeLabel.setJustificationType(Justification::centredLeft); slopeLabel.attachToComponent(&slopeBox, true); //============================================================================== addAndMakeVisible (waveshaperBox); i = 1; for (auto choice : processor.waveshaperParam->choices) waveshaperBox.addItem (choice, i++); waveshaperBox.onChange = [this] { processor.waveshaperParam->operator= (waveshaperBox.getSelectedItemIndex()); }; waveshaperBox.setSelectedId (processor.waveshaperParam->getIndex() + 1); addAndMakeVisible (waveshaperLabel); waveshaperLabel.setJustificationType (Justification::centredLeft); waveshaperLabel.attachToComponent (&waveshaperBox, true); //============================================================================== addAndMakeVisible (cabinetTypeBox); i = 1; for (auto choice : processor.cabinetTypeParam->choices) cabinetTypeBox.addItem (choice, i++); cabinetTypeBox.onChange = [this] { processor.cabinetTypeParam->operator= (cabinetTypeBox.getSelectedItemIndex()); }; cabinetTypeBox.setSelectedId (processor.cabinetTypeParam->getIndex() + 1); addAndMakeVisible (cabinetTypeLabel); cabinetTypeLabel.setJustificationType (Justification::centredLeft); cabinetTypeLabel.attachToComponent (&cabinetTypeBox, true); //============================================================================== addAndMakeVisible (cabinetSimButton); cabinetSimButton.onClick = [this] { processor.cabinetSimParam->operator= (cabinetSimButton.getToggleState()); }; cabinetSimButton.setButtonText (processor.cabinetSimParam->name); cabinetSimButton.setToggleState (processor.cabinetSimParam->get(), NotificationType::dontSendNotification); addAndMakeVisible (oversamplingButton); oversamplingButton.onClick = [this] { processor.oversamplingParam->operator= (oversamplingButton.getToggleState()); }; oversamplingButton.setButtonText (processor.oversamplingParam->name); oversamplingButton.setToggleState (processor.oversamplingParam->get(), NotificationType::dontSendNotification); //============================================================================== setSize (600, 400); } ~DspModulePluginDemoAudioProcessorEditor() {} //============================================================================== void paint (Graphics& g) override { g.setColour (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); g.fillAll(); } void resized() override { auto bounds = getLocalBounds().reduced (10); bounds.removeFromTop (10); bounds.removeFromLeft (125); //============================================================================== inputVolumeSlider->setBounds (bounds.removeFromTop (30)); bounds.removeFromTop (5); outputVolumeSlider->setBounds (bounds.removeFromTop (30)); bounds.removeFromTop (15); highPassFilterFreqSlider->setBounds (bounds.removeFromTop (30)); bounds.removeFromTop (5); lowPassFilterFreqSlider->setBounds (bounds.removeFromTop (30)); bounds.removeFromTop (15); //============================================================================== stereoBox.setBounds (bounds.removeFromTop(30)); bounds.removeFromTop (5); slopeBox.setBounds (bounds.removeFromTop (30)); bounds.removeFromTop (5); waveshaperBox.setBounds (bounds.removeFromTop (30)); bounds.removeFromTop (5); cabinetTypeBox.setBounds (bounds.removeFromTop (30)); bounds.removeFromTop (15); //============================================================================== auto buttonSlice = bounds.removeFromTop (30); cabinetSimButton.setSize (200, buttonSlice.getHeight()); cabinetSimButton.setCentrePosition (buttonSlice.getCentre()); bounds.removeFromTop(5); buttonSlice = bounds.removeFromTop (30); oversamplingButton.setSize(200, buttonSlice.getHeight()); oversamplingButton.setCentrePosition(buttonSlice.getCentre()); } private: //============================================================================== DspModulePluginDemoAudioProcessor& processor; std::unique_ptr inputVolumeSlider, outputVolumeSlider, lowPassFilterFreqSlider, highPassFilterFreqSlider; ComboBox stereoBox, slopeBox, waveshaperBox, cabinetTypeBox; ToggleButton cabinetSimButton, oversamplingButton; Label inputVolumeLabel, outputVolumeLabel, lowPassFilterFreqLabel, highPassFilterFreqLabel, stereoLabel, slopeLabel, waveshaperLabel, cabinetTypeLabel; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DspModulePluginDemoAudioProcessorEditor) }; //============================================================================== void process (dsp::ProcessContextReplacing context) noexcept { ScopedNoDenormals noDenormals; // Input volume applied with a SmoothedValue inputVolume.process (context); // Pre-highpass filtering, very useful for distortion audio effects // Note : try frequencies around 700 Hz highPassFilter.process (context); // Upsampling dsp::AudioBlock oversampledBlock; setLatencySamples (audioCurrentlyOversampled ? roundToInt (oversampling->getLatencyInSamples()) : 0); if (audioCurrentlyOversampled) oversampledBlock = oversampling->processSamplesUp (context.getInputBlock()); auto waveshaperContext = audioCurrentlyOversampled ? dsp::ProcessContextReplacing (oversampledBlock) : context; // Waveshaper processing, for distortion generation, thanks to the input gain // The fast tanh can be used instead of std::tanh to reduce the CPU load auto waveshaperIndex = waveshaperParam->getIndex(); if (isPositiveAndBelow (waveshaperIndex, numWaveShapers) ) { waveShapers[waveshaperIndex].process (waveshaperContext); if (waveshaperIndex == 1) clipping.process (waveshaperContext); waveshaperContext.getOutputBlock() *= 0.7f; } // Downsampling if (audioCurrentlyOversampled) oversampling->processSamplesDown (context.getOutputBlock()); // Post-lowpass filtering lowPassFilter.process (context); // Convolution with the impulse response of a guitar cabinet auto wasBypassed = context.isBypassed; context.isBypassed = context.isBypassed || cabinetIsBypassed; convolution.process (context); context.isBypassed = wasBypassed; // Output volume applied with a SmoothedValue outputVolume.process (context); } //============================================================================== dsp::ProcessorDuplicator, dsp::IIR::Coefficients> lowPassFilter, highPassFilter; dsp::Convolution convolution; MemoryBlock currentCabinetData; static constexpr size_t numWaveShapers = 2; dsp::WaveShaper waveShapers[numWaveShapers]; dsp::WaveShaper clipping; dsp::Gain inputVolume, outputVolume; std::unique_ptr> oversampling; bool audioCurrentlyOversampled = false; Atomic cabinetType; bool cabinetIsBypassed = false; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DspModulePluginDemoAudioProcessor) };