├── .gitignore ├── Chapter Code ├── Chapter 10 │ ├── Delay.jucer │ └── Source │ │ ├── Parameters.cpp │ │ ├── Parameters.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── RotaryKnob.cpp │ │ └── RotaryKnob.h ├── Chapter 11 │ ├── Delay.jucer │ └── Source │ │ ├── LookAndFeel.cpp │ │ ├── LookAndFeel.h │ │ ├── Parameters.cpp │ │ ├── Parameters.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── RotaryKnob.cpp │ │ └── RotaryKnob.h ├── Chapter 12 │ ├── Delay.jucer │ └── Source │ │ ├── LookAndFeel.cpp │ │ ├── LookAndFeel.h │ │ ├── Parameters.cpp │ │ ├── Parameters.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── ProtectYourEars.h │ │ ├── RotaryKnob.cpp │ │ └── RotaryKnob.h ├── Chapter 13 │ ├── Delay.jucer │ └── Source │ │ ├── DSP.h │ │ ├── LookAndFeel.cpp │ │ ├── LookAndFeel.h │ │ ├── Parameters.cpp │ │ ├── Parameters.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── ProtectYourEars.h │ │ ├── RotaryKnob.cpp │ │ └── RotaryKnob.h ├── Chapter 14 │ ├── Delay.jucer │ └── Source │ │ ├── DSP.h │ │ ├── LookAndFeel.cpp │ │ ├── LookAndFeel.h │ │ ├── Parameters.cpp │ │ ├── Parameters.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── ProtectYourEars.h │ │ ├── RotaryKnob.cpp │ │ └── RotaryKnob.h ├── Chapter 15 │ ├── Delay.jucer │ └── Source │ │ ├── DSP.h │ │ ├── LookAndFeel.cpp │ │ ├── LookAndFeel.h │ │ ├── Parameters.cpp │ │ ├── Parameters.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── ProtectYourEars.h │ │ ├── RotaryKnob.cpp │ │ ├── RotaryKnob.h │ │ ├── Tempo.cpp │ │ └── Tempo.h ├── Chapter 16 │ ├── Delay.jucer │ └── Source │ │ ├── DSP.h │ │ ├── DelayLine.cpp │ │ ├── DelayLine.h │ │ ├── LookAndFeel.cpp │ │ ├── LookAndFeel.h │ │ ├── Parameters.cpp │ │ ├── Parameters.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── ProtectYourEars.h │ │ ├── RotaryKnob.cpp │ │ ├── RotaryKnob.h │ │ ├── Tempo.cpp │ │ └── Tempo.h ├── Chapter 17 │ ├── Delay.jucer │ └── Source │ │ ├── DSP.h │ │ ├── DelayLine.cpp │ │ ├── DelayLine.h │ │ ├── LevelMeter.cpp │ │ ├── LevelMeter.h │ │ ├── LookAndFeel.cpp │ │ ├── LookAndFeel.h │ │ ├── Measurement.h │ │ ├── Parameters.cpp │ │ ├── Parameters.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── ProtectYourEars.h │ │ ├── RotaryKnob.cpp │ │ ├── RotaryKnob.h │ │ ├── Tempo.cpp │ │ └── Tempo.h ├── Chapter 18 │ ├── Delay.jucer │ └── Source │ │ ├── DSP.h │ │ ├── DelayLine.cpp │ │ ├── DelayLine.h │ │ ├── LevelMeter.cpp │ │ ├── LevelMeter.h │ │ ├── LookAndFeel.cpp │ │ ├── LookAndFeel.h │ │ ├── Measurement.h │ │ ├── Parameters.cpp │ │ ├── Parameters.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── ProtectYourEars.h │ │ ├── RotaryKnob.cpp │ │ ├── RotaryKnob.h │ │ ├── Tempo.cpp │ │ └── Tempo.h ├── Chapter 6 │ ├── Delay.jucer │ └── Source │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ └── PluginProcessor.h ├── Chapter 7 │ ├── Delay.jucer │ └── Source │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ └── PluginProcessor.h ├── Chapter 8 │ ├── Delay.jucer │ └── Source │ │ ├── Parameters.cpp │ │ ├── Parameters.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ └── PluginProcessor.h └── Chapter 9 │ ├── Delay.jucer │ └── Source │ ├── Parameters.cpp │ ├── Parameters.h │ ├── PluginEditor.cpp │ ├── PluginEditor.h │ ├── PluginProcessor.cpp │ └── PluginProcessor.h ├── Errata.markdown ├── Finished Project ├── Delay.jucer └── Source │ ├── DSP.h │ ├── DelayLine.cpp │ ├── DelayLine.h │ ├── LevelMeter.cpp │ ├── LevelMeter.h │ ├── LookAndFeel.cpp │ ├── LookAndFeel.h │ ├── Measurement.h │ ├── Parameters.cpp │ ├── Parameters.h │ ├── PluginEditor.cpp │ ├── PluginEditor.h │ ├── PluginProcessor.cpp │ ├── PluginProcessor.h │ ├── ProtectYourEars.h │ ├── RotaryKnob.cpp │ ├── RotaryKnob.h │ ├── Tempo.cpp │ └── Tempo.h ├── Images └── BuildOnlyDeviceError.png ├── LICENSE.txt ├── OFL.txt ├── README.markdown ├── Resources ├── Beep-stereo.wav ├── Beep.wav ├── Bypass.png ├── Lato-Medium.ttf ├── Logo.png ├── Noise.png ├── ProtectYourEars.h ├── Sine.wav └── WhiteNoise.wav ├── book-cover.jpg └── ping-pong-delay.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | **/Builds 3 | **/JuceLibraryCode 4 | **/*.filtergraph 5 | 6 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | const juce::ParameterID delayTimeParamID { "delayTime", 1 }; 7 | const juce::ParameterID mixParamID { "mix", 1 }; 8 | 9 | class Parameters 10 | { 11 | public: 12 | Parameters(juce::AudioProcessorValueTreeState& apvts); 13 | 14 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 15 | 16 | void prepareToPlay(double sampleRate) noexcept; 17 | void reset() noexcept; 18 | void update() noexcept; 19 | void smoothen() noexcept; 20 | 21 | float gain = 0.0f; 22 | float delayTime = 0.0f; 23 | float mix = 1.0f; 24 | 25 | static constexpr float minDelayTime = 5.0f; 26 | static constexpr float maxDelayTime = 5000.0f; 27 | 28 | private: 29 | juce::AudioParameterFloat* gainParam; 30 | juce::LinearSmoothedValue gainSmoother; 31 | 32 | juce::AudioParameterFloat* delayTimeParam; 33 | 34 | float targetDelayTime = 0.0f; 35 | float coeff = 0.0f; // one-pole smoothing 36 | 37 | juce::AudioParameterFloat* mixParam; 38 | juce::LinearSmoothedValue mixSmoother; 39 | }; 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | //============================================================================== 13 | DelayAudioProcessorEditor::DelayAudioProcessorEditor (DelayAudioProcessor& p) 14 | : AudioProcessorEditor (&p), audioProcessor (p) 15 | { 16 | delayGroup.setText("Delay"); 17 | delayGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 18 | delayGroup.addAndMakeVisible(delayTimeKnob); 19 | addAndMakeVisible(delayGroup); 20 | 21 | feedbackGroup.setText("Feedback"); 22 | feedbackGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 23 | addAndMakeVisible(feedbackGroup); 24 | 25 | outputGroup.setText("Output"); 26 | outputGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 27 | outputGroup.addAndMakeVisible(gainKnob); 28 | outputGroup.addAndMakeVisible(mixKnob); 29 | addAndMakeVisible(outputGroup); 30 | 31 | setSize(500, 330); 32 | } 33 | 34 | DelayAudioProcessorEditor::~DelayAudioProcessorEditor() 35 | { 36 | } 37 | 38 | //============================================================================== 39 | void DelayAudioProcessorEditor::paint (juce::Graphics& g) 40 | { 41 | g.fillAll(juce::Colours::darkgrey); 42 | } 43 | 44 | void DelayAudioProcessorEditor::resized() 45 | { 46 | auto bounds = getLocalBounds(); 47 | 48 | int y = 10; 49 | int height = bounds.getHeight() - 20; 50 | 51 | // Position the groups 52 | delayGroup.setBounds(10, y, 110, height); 53 | 54 | outputGroup.setBounds(bounds.getWidth() - 160, y, 150, height); 55 | 56 | feedbackGroup.setBounds(delayGroup.getRight() + 10, y, 57 | outputGroup.getX() - delayGroup.getRight() - 20, 58 | height); 59 | 60 | // Position the knobs inside the groups 61 | delayTimeKnob.setTopLeftPosition(20, 20); 62 | mixKnob.setTopLeftPosition(20, 20); 63 | gainKnob.setTopLeftPosition(mixKnob.getX(), mixKnob.getBottom() + 10); 64 | } 65 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | #include "Parameters.h" 14 | #include "RotaryKnob.h" 15 | 16 | //============================================================================== 17 | /** 18 | */ 19 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor 20 | { 21 | public: 22 | DelayAudioProcessorEditor (DelayAudioProcessor&); 23 | ~DelayAudioProcessorEditor() override; 24 | 25 | //============================================================================== 26 | void paint (juce::Graphics&) override; 27 | void resized() override; 28 | 29 | private: 30 | // This reference is provided as a quick way for your editor to 31 | // access the processor object that created it. 32 | DelayAudioProcessor& audioProcessor; 33 | 34 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 35 | 36 | RotaryKnob gainKnob { "Gain", audioProcessor.apvts, gainParamID }; 37 | RotaryKnob mixKnob { "Mix", audioProcessor.apvts, mixParamID }; 38 | RotaryKnob delayTimeKnob { "Time", audioProcessor.apvts, delayTimeParamID }; 39 | 40 | juce::GroupComponent delayGroup, feedbackGroup, outputGroup; 41 | }; 42 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "Parameters.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class DelayAudioProcessor : public juce::AudioProcessor 18 | { 19 | public: 20 | //============================================================================== 21 | DelayAudioProcessor(); 22 | ~DelayAudioProcessor() override; 23 | 24 | //============================================================================== 25 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 26 | void releaseResources() override; 27 | 28 | #ifndef JucePlugin_PreferredChannelConfigurations 29 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 30 | #endif 31 | 32 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 33 | 34 | //============================================================================== 35 | juce::AudioProcessorEditor* createEditor() override; 36 | bool hasEditor() const override; 37 | 38 | //============================================================================== 39 | const juce::String getName() const override; 40 | 41 | bool acceptsMidi() const override; 42 | bool producesMidi() const override; 43 | bool isMidiEffect() const override; 44 | double getTailLengthSeconds() const override; 45 | 46 | //============================================================================== 47 | int getNumPrograms() override; 48 | int getCurrentProgram() override; 49 | void setCurrentProgram (int index) override; 50 | const juce::String getProgramName (int index) override; 51 | void changeProgramName (int index, const juce::String& newName) override; 52 | 53 | //============================================================================== 54 | void getStateInformation (juce::MemoryBlock& destData) override; 55 | void setStateInformation (const void* data, int sizeInBytes) override; 56 | 57 | juce::AudioProcessorValueTreeState apvts { 58 | *this, nullptr, "Parameters", Parameters::createParameterLayout() 59 | }; 60 | 61 | private: 62 | Parameters params; 63 | 64 | juce::dsp::DelayLine delayLine; 65 | 66 | //============================================================================== 67 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 68 | }; 69 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/RotaryKnob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RotaryKnob.h" 3 | 4 | RotaryKnob::RotaryKnob(const juce::String& text, 5 | juce::AudioProcessorValueTreeState& apvts, 6 | const juce::ParameterID& parameterID) 7 | : attachment(apvts, parameterID.getParamID(), slider) 8 | { 9 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); 10 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 70, 16); 11 | slider.setBounds(0, 0, 70, 86); 12 | addAndMakeVisible(slider); 13 | 14 | label.setText(text, juce::NotificationType::dontSendNotification); 15 | label.setJustificationType(juce::Justification::horizontallyCentred); 16 | label.setBorderSize(juce::BorderSize{ 0, 0, 2, 0 }); 17 | label.attachToComponent(&slider, false); 18 | addAndMakeVisible(label); 19 | 20 | setSize(70, 110); 21 | } 22 | 23 | RotaryKnob::~RotaryKnob() 24 | { 25 | } 26 | 27 | void RotaryKnob::resized() 28 | { 29 | slider.setTopLeftPosition(0, 24); 30 | } 31 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/RotaryKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RotaryKnob : public juce::Component 6 | { 7 | public: 8 | RotaryKnob(const juce::String& text, 9 | juce::AudioProcessorValueTreeState& apvts, 10 | const juce::ParameterID& parameterID); 11 | ~RotaryKnob() override; 12 | 13 | void resized() override; 14 | 15 | juce::Slider slider; 16 | juce::Label label; 17 | 18 | juce::AudioProcessorValueTreeState::SliderAttachment attachment; 19 | 20 | private: 21 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RotaryKnob) 22 | }; 23 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/LookAndFeel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Colors 6 | { 7 | const juce::Colour background { 245, 240, 235 }; 8 | const juce::Colour header { 40, 40, 40 }; 9 | 10 | namespace Knob 11 | { 12 | const juce::Colour trackBackground { 205, 200, 195 }; 13 | const juce::Colour trackActive { 177, 101, 135 }; 14 | const juce::Colour outline { 255, 250, 245 }; 15 | const juce::Colour gradientTop { 250, 245, 240 }; 16 | const juce::Colour gradientBottom { 240, 235, 230 }; 17 | const juce::Colour dial { 100, 100, 100 }; 18 | const juce::Colour dropShadow { 195, 190, 185 }; 19 | const juce::Colour label { 80, 80, 80 }; 20 | const juce::Colour textBoxBackground { 80, 80, 80 }; 21 | const juce::Colour value { 240, 240, 240 }; 22 | const juce::Colour caret { 255, 255, 255 }; 23 | } 24 | 25 | namespace Group 26 | { 27 | const juce::Colour label { 160, 155, 150 }; 28 | const juce::Colour outline { 235, 230, 225 }; 29 | } 30 | } 31 | 32 | class Fonts 33 | { 34 | public: 35 | Fonts() = delete; 36 | 37 | static juce::Font getFont(float height = 16.0f); 38 | 39 | private: 40 | static const juce::Typeface::Ptr typeface; 41 | }; 42 | 43 | class RotaryKnobLookAndFeel : public juce::LookAndFeel_V4 44 | { 45 | public: 46 | RotaryKnobLookAndFeel(); 47 | 48 | static RotaryKnobLookAndFeel* get() 49 | { 50 | static RotaryKnobLookAndFeel instance; 51 | return &instance; 52 | } 53 | 54 | void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, 55 | float sliderPos, float rotaryStartAngle, 56 | float rotaryEndAngle, juce::Slider& slider) override; 57 | 58 | juce::Font getLabelFont(juce::Label&) override; 59 | juce::Label* createSliderTextBox(juce::Slider&) override; 60 | 61 | void drawTextEditorOutline(juce::Graphics&, int, int, juce::TextEditor&) override { } 62 | void fillTextEditorBackground(juce::Graphics&, int width, int height, juce::TextEditor&) override; 63 | 64 | private: 65 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RotaryKnobLookAndFeel) 66 | 67 | juce::DropShadow dropShadow { Colors::Knob::dropShadow, 6, { 0, 3 } }; 68 | }; 69 | 70 | class MainLookAndFeel : public juce::LookAndFeel_V4 71 | { 72 | public: 73 | MainLookAndFeel(); 74 | 75 | juce::Font getLabelFont(juce::Label&) override; 76 | 77 | private: 78 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainLookAndFeel) 79 | }; 80 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | const juce::ParameterID delayTimeParamID { "delayTime", 1 }; 7 | const juce::ParameterID mixParamID { "mix", 1 }; 8 | 9 | class Parameters 10 | { 11 | public: 12 | Parameters(juce::AudioProcessorValueTreeState& apvts); 13 | 14 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 15 | 16 | void prepareToPlay(double sampleRate) noexcept; 17 | void reset() noexcept; 18 | void update() noexcept; 19 | void smoothen() noexcept; 20 | 21 | float gain = 0.0f; 22 | float delayTime = 0.0f; 23 | float mix = 1.0f; 24 | 25 | static constexpr float minDelayTime = 5.0f; 26 | static constexpr float maxDelayTime = 5000.0f; 27 | 28 | private: 29 | juce::AudioParameterFloat* gainParam; 30 | juce::LinearSmoothedValue gainSmoother; 31 | 32 | juce::AudioParameterFloat* delayTimeParam; 33 | 34 | float targetDelayTime = 0.0f; 35 | float coeff = 0.0f; // one-pole smoothing 36 | 37 | juce::AudioParameterFloat* mixParam; 38 | juce::LinearSmoothedValue mixSmoother; 39 | }; 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | //============================================================================== 13 | DelayAudioProcessorEditor::DelayAudioProcessorEditor (DelayAudioProcessor& p) 14 | : AudioProcessorEditor (&p), audioProcessor (p) 15 | { 16 | delayGroup.setText("Delay"); 17 | delayGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 18 | delayGroup.addAndMakeVisible(delayTimeKnob); 19 | addAndMakeVisible(delayGroup); 20 | 21 | feedbackGroup.setText("Feedback"); 22 | feedbackGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 23 | addAndMakeVisible(feedbackGroup); 24 | 25 | outputGroup.setText("Output"); 26 | outputGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 27 | outputGroup.addAndMakeVisible(gainKnob); 28 | outputGroup.addAndMakeVisible(mixKnob); 29 | addAndMakeVisible(outputGroup); 30 | 31 | setSize(500, 330); 32 | 33 | //gainKnob.slider.setColour(juce::Slider::rotarySliderFillColourId, juce::Colours::green); 34 | 35 | setLookAndFeel(&mainLF); 36 | } 37 | 38 | DelayAudioProcessorEditor::~DelayAudioProcessorEditor() 39 | { 40 | setLookAndFeel(nullptr); 41 | } 42 | 43 | //============================================================================== 44 | void DelayAudioProcessorEditor::paint (juce::Graphics& g) 45 | { 46 | auto noise = juce::ImageCache::getFromMemory( 47 | BinaryData::Noise_png, BinaryData::Noise_pngSize); 48 | auto fillType = juce::FillType(noise, juce::AffineTransform::scale(0.5f)); 49 | g.setFillType(fillType); 50 | g.fillRect(getLocalBounds()); 51 | 52 | auto rect = getLocalBounds().withHeight(40); 53 | g.setColour(Colors::header); 54 | g.fillRect(rect); 55 | 56 | auto image = juce::ImageCache::getFromMemory( 57 | BinaryData::Logo_png, BinaryData::Logo_pngSize); 58 | 59 | int destWidth = image.getWidth() / 2; 60 | int destHeight = image.getHeight() / 2; 61 | g.drawImage(image, 62 | getWidth() / 2 - destWidth / 2, 0, destWidth, destHeight, 63 | 0, 0, image.getWidth(), image.getHeight()); 64 | } 65 | 66 | void DelayAudioProcessorEditor::resized() 67 | { 68 | auto bounds = getLocalBounds(); 69 | 70 | int y = 50; 71 | int height = bounds.getHeight() - 60; 72 | 73 | // Position the groups 74 | delayGroup.setBounds(10, y, 110, height); 75 | 76 | outputGroup.setBounds(bounds.getWidth() - 160, y, 150, height); 77 | 78 | feedbackGroup.setBounds(delayGroup.getRight() + 10, y, 79 | outputGroup.getX() - delayGroup.getRight() - 20, 80 | height); 81 | 82 | // Position the knobs inside the groups 83 | delayTimeKnob.setTopLeftPosition(20, 20); 84 | mixKnob.setTopLeftPosition(20, 20); 85 | gainKnob.setTopLeftPosition(mixKnob.getX(), mixKnob.getBottom() + 10); 86 | } 87 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | #include "Parameters.h" 14 | #include "RotaryKnob.h" 15 | #include "LookAndFeel.h" 16 | 17 | //============================================================================== 18 | /** 19 | */ 20 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor 21 | { 22 | public: 23 | DelayAudioProcessorEditor (DelayAudioProcessor&); 24 | ~DelayAudioProcessorEditor() override; 25 | 26 | //============================================================================== 27 | void paint (juce::Graphics&) override; 28 | void resized() override; 29 | 30 | private: 31 | // This reference is provided as a quick way for your editor to 32 | // access the processor object that created it. 33 | DelayAudioProcessor& audioProcessor; 34 | 35 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 36 | 37 | RotaryKnob gainKnob { "Gain", audioProcessor.apvts, gainParamID, true }; 38 | RotaryKnob mixKnob { "Mix", audioProcessor.apvts, mixParamID }; 39 | RotaryKnob delayTimeKnob { "Time", audioProcessor.apvts, delayTimeParamID }; 40 | 41 | juce::GroupComponent delayGroup, feedbackGroup, outputGroup; 42 | 43 | MainLookAndFeel mainLF; 44 | }; 45 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "Parameters.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class DelayAudioProcessor : public juce::AudioProcessor 18 | { 19 | public: 20 | //============================================================================== 21 | DelayAudioProcessor(); 22 | ~DelayAudioProcessor() override; 23 | 24 | //============================================================================== 25 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 26 | void releaseResources() override; 27 | 28 | #ifndef JucePlugin_PreferredChannelConfigurations 29 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 30 | #endif 31 | 32 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 33 | 34 | //============================================================================== 35 | juce::AudioProcessorEditor* createEditor() override; 36 | bool hasEditor() const override; 37 | 38 | //============================================================================== 39 | const juce::String getName() const override; 40 | 41 | bool acceptsMidi() const override; 42 | bool producesMidi() const override; 43 | bool isMidiEffect() const override; 44 | double getTailLengthSeconds() const override; 45 | 46 | //============================================================================== 47 | int getNumPrograms() override; 48 | int getCurrentProgram() override; 49 | void setCurrentProgram (int index) override; 50 | const juce::String getProgramName (int index) override; 51 | void changeProgramName (int index, const juce::String& newName) override; 52 | 53 | //============================================================================== 54 | void getStateInformation (juce::MemoryBlock& destData) override; 55 | void setStateInformation (const void* data, int sizeInBytes) override; 56 | 57 | juce::AudioProcessorValueTreeState apvts { 58 | *this, nullptr, "Parameters", Parameters::createParameterLayout() 59 | }; 60 | 61 | private: 62 | Parameters params; 63 | 64 | juce::dsp::DelayLine delayLine; 65 | 66 | //============================================================================== 67 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 68 | }; 69 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/RotaryKnob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RotaryKnob.h" 3 | #include "LookAndFeel.h" 4 | 5 | RotaryKnob::RotaryKnob(const juce::String& text, 6 | juce::AudioProcessorValueTreeState& apvts, 7 | const juce::ParameterID& parameterID, 8 | bool drawFromMiddle) 9 | : attachment(apvts, parameterID.getParamID(), slider) 10 | { 11 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); 12 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 70, 16); 13 | slider.setBounds(0, 0, 70, 86); 14 | addAndMakeVisible(slider); 15 | 16 | label.setText(text, juce::NotificationType::dontSendNotification); 17 | label.setJustificationType(juce::Justification::horizontallyCentred); 18 | label.setBorderSize(juce::BorderSize{ 0, 0, 2, 0 }); 19 | label.attachToComponent(&slider, false); 20 | addAndMakeVisible(label); 21 | 22 | setSize(70, 110); 23 | 24 | setLookAndFeel(RotaryKnobLookAndFeel::get()); 25 | 26 | float pi = juce::MathConstants::pi; 27 | slider.setRotaryParameters(1.25f * pi, 2.75f * pi, true); 28 | 29 | slider.getProperties().set("drawFromMiddle", drawFromMiddle); 30 | } 31 | 32 | RotaryKnob::~RotaryKnob() 33 | { 34 | } 35 | 36 | void RotaryKnob::resized() 37 | { 38 | slider.setTopLeftPosition(0, 24); 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/RotaryKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RotaryKnob : public juce::Component 6 | { 7 | public: 8 | RotaryKnob(const juce::String& text, 9 | juce::AudioProcessorValueTreeState& apvts, 10 | const juce::ParameterID& parameterID, 11 | bool drawFromMiddle = false); 12 | 13 | ~RotaryKnob() override; 14 | 15 | void resized() override; 16 | 17 | juce::Slider slider; 18 | juce::Label label; 19 | 20 | juce::AudioProcessorValueTreeState::SliderAttachment attachment; 21 | 22 | private: 23 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RotaryKnob) 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/LookAndFeel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Colors 6 | { 7 | const juce::Colour background { 245, 240, 235 }; 8 | const juce::Colour header { 40, 40, 40 }; 9 | 10 | namespace Knob 11 | { 12 | const juce::Colour trackBackground { 205, 200, 195 }; 13 | const juce::Colour trackActive { 177, 101, 135 }; 14 | const juce::Colour outline { 255, 250, 245 }; 15 | const juce::Colour gradientTop { 250, 245, 240 }; 16 | const juce::Colour gradientBottom { 240, 235, 230 }; 17 | const juce::Colour dial { 100, 100, 100 }; 18 | const juce::Colour dropShadow { 195, 190, 185 }; 19 | const juce::Colour label { 80, 80, 80 }; 20 | const juce::Colour textBoxBackground { 80, 80, 80 }; 21 | const juce::Colour value { 240, 240, 240 }; 22 | const juce::Colour caret { 255, 255, 255 }; 23 | } 24 | 25 | namespace Group 26 | { 27 | const juce::Colour label { 160, 155, 150 }; 28 | const juce::Colour outline { 235, 230, 225 }; 29 | } 30 | } 31 | 32 | class Fonts 33 | { 34 | public: 35 | Fonts() = delete; 36 | 37 | static juce::Font getFont(float height = 16.0f); 38 | 39 | private: 40 | static const juce::Typeface::Ptr typeface; 41 | }; 42 | 43 | class RotaryKnobLookAndFeel : public juce::LookAndFeel_V4 44 | { 45 | public: 46 | RotaryKnobLookAndFeel(); 47 | 48 | static RotaryKnobLookAndFeel* get() 49 | { 50 | static RotaryKnobLookAndFeel instance; 51 | return &instance; 52 | } 53 | 54 | void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, 55 | float sliderPos, float rotaryStartAngle, 56 | float rotaryEndAngle, juce::Slider& slider) override; 57 | 58 | juce::Font getLabelFont(juce::Label&) override; 59 | juce::Label* createSliderTextBox(juce::Slider&) override; 60 | 61 | void drawTextEditorOutline(juce::Graphics&, int, int, juce::TextEditor&) override { } 62 | void fillTextEditorBackground(juce::Graphics&, int width, int height, juce::TextEditor&) override; 63 | 64 | private: 65 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RotaryKnobLookAndFeel) 66 | 67 | juce::DropShadow dropShadow { Colors::Knob::dropShadow, 6, { 0, 3 } }; 68 | }; 69 | 70 | class MainLookAndFeel : public juce::LookAndFeel_V4 71 | { 72 | public: 73 | MainLookAndFeel(); 74 | 75 | juce::Font getLabelFont(juce::Label&) override; 76 | 77 | private: 78 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainLookAndFeel) 79 | }; 80 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | const juce::ParameterID delayTimeParamID { "delayTime", 1 }; 7 | const juce::ParameterID mixParamID { "mix", 1 }; 8 | const juce::ParameterID feedbackParamID { "feedback", 1 }; 9 | 10 | class Parameters 11 | { 12 | public: 13 | Parameters(juce::AudioProcessorValueTreeState& apvts); 14 | 15 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 16 | 17 | void prepareToPlay(double sampleRate) noexcept; 18 | void reset() noexcept; 19 | void update() noexcept; 20 | void smoothen() noexcept; 21 | 22 | float gain = 0.0f; 23 | float delayTime = 0.0f; 24 | float mix = 1.0f; 25 | float feedback = 0.0f; 26 | 27 | static constexpr float minDelayTime = 5.0f; 28 | static constexpr float maxDelayTime = 5000.0f; 29 | 30 | private: 31 | juce::AudioParameterFloat* gainParam; 32 | juce::LinearSmoothedValue gainSmoother; 33 | 34 | juce::AudioParameterFloat* delayTimeParam; 35 | 36 | float targetDelayTime = 0.0f; 37 | float coeff = 0.0f; // one-pole smoothing 38 | 39 | juce::AudioParameterFloat* mixParam; 40 | juce::LinearSmoothedValue mixSmoother; 41 | 42 | juce::AudioParameterFloat* feedbackParam; 43 | juce::LinearSmoothedValue feedbackSmoother; 44 | }; 45 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | //============================================================================== 13 | DelayAudioProcessorEditor::DelayAudioProcessorEditor (DelayAudioProcessor& p) 14 | : AudioProcessorEditor (&p), audioProcessor (p) 15 | { 16 | delayGroup.setText("Delay"); 17 | delayGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 18 | delayGroup.addAndMakeVisible(delayTimeKnob); 19 | addAndMakeVisible(delayGroup); 20 | 21 | feedbackGroup.setText("Feedback"); 22 | feedbackGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 23 | feedbackGroup.addAndMakeVisible(feedbackKnob); 24 | addAndMakeVisible(feedbackGroup); 25 | 26 | outputGroup.setText("Output"); 27 | outputGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 28 | outputGroup.addAndMakeVisible(gainKnob); 29 | outputGroup.addAndMakeVisible(mixKnob); 30 | addAndMakeVisible(outputGroup); 31 | 32 | setSize(500, 330); 33 | 34 | //gainKnob.slider.setColour(juce::Slider::rotarySliderFillColourId, juce::Colours::green); 35 | 36 | setLookAndFeel(&mainLF); 37 | } 38 | 39 | DelayAudioProcessorEditor::~DelayAudioProcessorEditor() 40 | { 41 | setLookAndFeel(nullptr); 42 | } 43 | 44 | //============================================================================== 45 | void DelayAudioProcessorEditor::paint (juce::Graphics& g) 46 | { 47 | auto noise = juce::ImageCache::getFromMemory( 48 | BinaryData::Noise_png, BinaryData::Noise_pngSize); 49 | auto fillType = juce::FillType(noise, juce::AffineTransform::scale(0.5f)); 50 | g.setFillType(fillType); 51 | g.fillRect(getLocalBounds()); 52 | 53 | auto rect = getLocalBounds().withHeight(40); 54 | g.setColour(Colors::header); 55 | g.fillRect(rect); 56 | 57 | auto image = juce::ImageCache::getFromMemory( 58 | BinaryData::Logo_png, BinaryData::Logo_pngSize); 59 | 60 | int destWidth = image.getWidth() / 2; 61 | int destHeight = image.getHeight() / 2; 62 | g.drawImage(image, 63 | getWidth() / 2 - destWidth / 2, 0, destWidth, destHeight, 64 | 0, 0, image.getWidth(), image.getHeight()); 65 | } 66 | 67 | void DelayAudioProcessorEditor::resized() 68 | { 69 | auto bounds = getLocalBounds(); 70 | 71 | int y = 50; 72 | int height = bounds.getHeight() - 60; 73 | 74 | // Position the groups 75 | delayGroup.setBounds(10, y, 110, height); 76 | 77 | outputGroup.setBounds(bounds.getWidth() - 160, y, 150, height); 78 | 79 | feedbackGroup.setBounds(delayGroup.getRight() + 10, y, 80 | outputGroup.getX() - delayGroup.getRight() - 20, 81 | height); 82 | 83 | // Position the knobs inside the groups 84 | delayTimeKnob.setTopLeftPosition(20, 20); 85 | mixKnob.setTopLeftPosition(20, 20); 86 | gainKnob.setTopLeftPosition(mixKnob.getX(), mixKnob.getBottom() + 10); 87 | feedbackKnob.setTopLeftPosition(20, 20); 88 | } 89 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | #include "Parameters.h" 14 | #include "RotaryKnob.h" 15 | #include "LookAndFeel.h" 16 | 17 | //============================================================================== 18 | /** 19 | */ 20 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor 21 | { 22 | public: 23 | DelayAudioProcessorEditor (DelayAudioProcessor&); 24 | ~DelayAudioProcessorEditor() override; 25 | 26 | //============================================================================== 27 | void paint (juce::Graphics&) override; 28 | void resized() override; 29 | 30 | private: 31 | // This reference is provided as a quick way for your editor to 32 | // access the processor object that created it. 33 | DelayAudioProcessor& audioProcessor; 34 | 35 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 36 | 37 | RotaryKnob gainKnob { "Gain", audioProcessor.apvts, gainParamID, true }; 38 | RotaryKnob mixKnob { "Mix", audioProcessor.apvts, mixParamID }; 39 | RotaryKnob delayTimeKnob { "Time", audioProcessor.apvts, delayTimeParamID }; 40 | RotaryKnob feedbackKnob { "Feedback", audioProcessor.apvts, feedbackParamID, true }; 41 | 42 | juce::GroupComponent delayGroup, feedbackGroup, outputGroup; 43 | 44 | MainLookAndFeel mainLF; 45 | }; 46 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "Parameters.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class DelayAudioProcessor : public juce::AudioProcessor 18 | { 19 | public: 20 | //============================================================================== 21 | DelayAudioProcessor(); 22 | ~DelayAudioProcessor() override; 23 | 24 | //============================================================================== 25 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 26 | void releaseResources() override; 27 | 28 | #ifndef JucePlugin_PreferredChannelConfigurations 29 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 30 | #endif 31 | 32 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 33 | 34 | //============================================================================== 35 | juce::AudioProcessorEditor* createEditor() override; 36 | bool hasEditor() const override; 37 | 38 | //============================================================================== 39 | const juce::String getName() const override; 40 | 41 | bool acceptsMidi() const override; 42 | bool producesMidi() const override; 43 | bool isMidiEffect() const override; 44 | double getTailLengthSeconds() const override; 45 | 46 | //============================================================================== 47 | int getNumPrograms() override; 48 | int getCurrentProgram() override; 49 | void setCurrentProgram (int index) override; 50 | const juce::String getProgramName (int index) override; 51 | void changeProgramName (int index, const juce::String& newName) override; 52 | 53 | //============================================================================== 54 | void getStateInformation (juce::MemoryBlock& destData) override; 55 | void setStateInformation (const void* data, int sizeInBytes) override; 56 | 57 | juce::AudioProcessorValueTreeState apvts { 58 | *this, nullptr, "Parameters", Parameters::createParameterLayout() 59 | }; 60 | 61 | private: 62 | Parameters params; 63 | 64 | juce::dsp::DelayLine delayLine; 65 | 66 | float feedbackL = 0.0f; 67 | float feedbackR = 0.0f; 68 | 69 | //============================================================================== 70 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 71 | }; 72 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/ProtectYourEars.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Silences the buffer if bad or loud values are detected in the output buffer. 6 | // Use this during debugging to avoid blowing out your eardrums on headphones. 7 | inline void protectYourEars(juce::AudioBuffer& buffer) 8 | { 9 | bool firstWarning = true; 10 | for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { 11 | float* channelData = buffer.getWritePointer(channel); 12 | for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { 13 | float x = channelData[sample]; 14 | bool silence = false; 15 | if (std::isnan(x)) { 16 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 17 | silence = true; 18 | } else if (std::isinf(x)) { 19 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 20 | silence = true; 21 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 22 | DBG("!!! WARNING: sample out of range, silencing !!!"); 23 | silence = true; 24 | } else if (x < -1.0f || x > 1.0f) { 25 | if (firstWarning) { 26 | DBG("!!! WARNING: sample out of range: " << x << " !!!"); 27 | firstWarning = false; 28 | } 29 | } 30 | if (silence) { 31 | buffer.clear(); 32 | return; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/RotaryKnob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RotaryKnob.h" 3 | #include "LookAndFeel.h" 4 | 5 | RotaryKnob::RotaryKnob(const juce::String& text, 6 | juce::AudioProcessorValueTreeState& apvts, 7 | const juce::ParameterID& parameterID, 8 | bool drawFromMiddle) 9 | : attachment(apvts, parameterID.getParamID(), slider) 10 | { 11 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); 12 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 70, 16); 13 | slider.setBounds(0, 0, 70, 86); 14 | addAndMakeVisible(slider); 15 | 16 | label.setText(text, juce::NotificationType::dontSendNotification); 17 | label.setJustificationType(juce::Justification::horizontallyCentred); 18 | label.setBorderSize(juce::BorderSize{ 0, 0, 2, 0 }); 19 | label.attachToComponent(&slider, false); 20 | addAndMakeVisible(label); 21 | 22 | setSize(70, 110); 23 | 24 | setLookAndFeel(RotaryKnobLookAndFeel::get()); 25 | 26 | float pi = juce::MathConstants::pi; 27 | slider.setRotaryParameters(1.25f * pi, 2.75f * pi, true); 28 | 29 | slider.getProperties().set("drawFromMiddle", drawFromMiddle); 30 | } 31 | 32 | RotaryKnob::~RotaryKnob() 33 | { 34 | } 35 | 36 | void RotaryKnob::resized() 37 | { 38 | slider.setTopLeftPosition(0, 24); 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/RotaryKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RotaryKnob : public juce::Component 6 | { 7 | public: 8 | RotaryKnob(const juce::String& text, 9 | juce::AudioProcessorValueTreeState& apvts, 10 | const juce::ParameterID& parameterID, 11 | bool drawFromMiddle = false); 12 | 13 | ~RotaryKnob() override; 14 | 15 | void resized() override; 16 | 17 | juce::Slider slider; 18 | juce::Label label; 19 | 20 | juce::AudioProcessorValueTreeState::SliderAttachment attachment; 21 | 22 | private: 23 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RotaryKnob) 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/DSP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | Calculates panning using equal power curves formula. Panning must be [-1, 1]. 7 | */ 8 | inline void panningEqualPower(float panning, float& left, float& right) 9 | { 10 | float x = 0.7853981633974483f * (panning + 1.0f); 11 | left = std::cos(x); 12 | right = std::sin(x); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/LookAndFeel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Colors 6 | { 7 | const juce::Colour background { 245, 240, 235 }; 8 | const juce::Colour header { 40, 40, 40 }; 9 | 10 | namespace Knob 11 | { 12 | const juce::Colour trackBackground { 205, 200, 195 }; 13 | const juce::Colour trackActive { 177, 101, 135 }; 14 | const juce::Colour outline { 255, 250, 245 }; 15 | const juce::Colour gradientTop { 250, 245, 240 }; 16 | const juce::Colour gradientBottom { 240, 235, 230 }; 17 | const juce::Colour dial { 100, 100, 100 }; 18 | const juce::Colour dropShadow { 195, 190, 185 }; 19 | const juce::Colour label { 80, 80, 80 }; 20 | const juce::Colour textBoxBackground { 80, 80, 80 }; 21 | const juce::Colour value { 240, 240, 240 }; 22 | const juce::Colour caret { 255, 255, 255 }; 23 | } 24 | 25 | namespace Group 26 | { 27 | const juce::Colour label { 160, 155, 150 }; 28 | const juce::Colour outline { 235, 230, 225 }; 29 | } 30 | } 31 | 32 | class Fonts 33 | { 34 | public: 35 | Fonts() = delete; 36 | 37 | static juce::Font getFont(float height = 16.0f); 38 | 39 | private: 40 | static const juce::Typeface::Ptr typeface; 41 | }; 42 | 43 | class RotaryKnobLookAndFeel : public juce::LookAndFeel_V4 44 | { 45 | public: 46 | RotaryKnobLookAndFeel(); 47 | 48 | static RotaryKnobLookAndFeel* get() 49 | { 50 | static RotaryKnobLookAndFeel instance; 51 | return &instance; 52 | } 53 | 54 | void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, 55 | float sliderPos, float rotaryStartAngle, 56 | float rotaryEndAngle, juce::Slider& slider) override; 57 | 58 | juce::Font getLabelFont(juce::Label&) override; 59 | juce::Label* createSliderTextBox(juce::Slider&) override; 60 | 61 | void drawTextEditorOutline(juce::Graphics&, int, int, juce::TextEditor&) override { } 62 | void fillTextEditorBackground(juce::Graphics&, int width, int height, juce::TextEditor&) override; 63 | 64 | private: 65 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RotaryKnobLookAndFeel) 66 | 67 | juce::DropShadow dropShadow { Colors::Knob::dropShadow, 6, { 0, 3 } }; 68 | }; 69 | 70 | class MainLookAndFeel : public juce::LookAndFeel_V4 71 | { 72 | public: 73 | MainLookAndFeel(); 74 | 75 | juce::Font getLabelFont(juce::Label&) override; 76 | 77 | private: 78 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainLookAndFeel) 79 | }; 80 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | const juce::ParameterID delayTimeParamID { "delayTime", 1 }; 7 | const juce::ParameterID mixParamID { "mix", 1 }; 8 | const juce::ParameterID feedbackParamID { "feedback", 1 }; 9 | const juce::ParameterID stereoParamID { "stereo", 1 }; 10 | 11 | class Parameters 12 | { 13 | public: 14 | Parameters(juce::AudioProcessorValueTreeState& apvts); 15 | 16 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 17 | 18 | void prepareToPlay(double sampleRate) noexcept; 19 | void reset() noexcept; 20 | void update() noexcept; 21 | void smoothen() noexcept; 22 | 23 | float gain = 0.0f; 24 | float delayTime = 0.0f; 25 | float mix = 1.0f; 26 | float feedback = 0.0f; 27 | float panL = 0.0f; 28 | float panR = 1.0f; 29 | 30 | static constexpr float minDelayTime = 5.0f; 31 | static constexpr float maxDelayTime = 5000.0f; 32 | 33 | private: 34 | juce::AudioParameterFloat* gainParam; 35 | juce::LinearSmoothedValue gainSmoother; 36 | 37 | juce::AudioParameterFloat* delayTimeParam; 38 | 39 | float targetDelayTime = 0.0f; 40 | float coeff = 0.0f; // one-pole smoothing 41 | 42 | juce::AudioParameterFloat* mixParam; 43 | juce::LinearSmoothedValue mixSmoother; 44 | 45 | juce::AudioParameterFloat* feedbackParam; 46 | juce::LinearSmoothedValue feedbackSmoother; 47 | 48 | juce::AudioParameterFloat* stereoParam; 49 | juce::LinearSmoothedValue stereoSmoother; 50 | }; 51 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | //============================================================================== 13 | DelayAudioProcessorEditor::DelayAudioProcessorEditor (DelayAudioProcessor& p) 14 | : AudioProcessorEditor (&p), audioProcessor (p) 15 | { 16 | delayGroup.setText("Delay"); 17 | delayGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 18 | delayGroup.addAndMakeVisible(delayTimeKnob); 19 | addAndMakeVisible(delayGroup); 20 | 21 | feedbackGroup.setText("Feedback"); 22 | feedbackGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 23 | feedbackGroup.addAndMakeVisible(feedbackKnob); 24 | feedbackGroup.addAndMakeVisible(stereoKnob); 25 | addAndMakeVisible(feedbackGroup); 26 | 27 | outputGroup.setText("Output"); 28 | outputGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 29 | outputGroup.addAndMakeVisible(gainKnob); 30 | outputGroup.addAndMakeVisible(mixKnob); 31 | addAndMakeVisible(outputGroup); 32 | 33 | setSize(500, 330); 34 | 35 | //gainKnob.slider.setColour(juce::Slider::rotarySliderFillColourId, juce::Colours::green); 36 | 37 | setLookAndFeel(&mainLF); 38 | } 39 | 40 | DelayAudioProcessorEditor::~DelayAudioProcessorEditor() 41 | { 42 | setLookAndFeel(nullptr); 43 | } 44 | 45 | //============================================================================== 46 | void DelayAudioProcessorEditor::paint (juce::Graphics& g) 47 | { 48 | auto noise = juce::ImageCache::getFromMemory( 49 | BinaryData::Noise_png, BinaryData::Noise_pngSize); 50 | auto fillType = juce::FillType(noise, juce::AffineTransform::scale(0.5f)); 51 | g.setFillType(fillType); 52 | g.fillRect(getLocalBounds()); 53 | 54 | auto rect = getLocalBounds().withHeight(40); 55 | g.setColour(Colors::header); 56 | g.fillRect(rect); 57 | 58 | auto image = juce::ImageCache::getFromMemory( 59 | BinaryData::Logo_png, BinaryData::Logo_pngSize); 60 | 61 | int destWidth = image.getWidth() / 2; 62 | int destHeight = image.getHeight() / 2; 63 | g.drawImage(image, 64 | getWidth() / 2 - destWidth / 2, 0, destWidth, destHeight, 65 | 0, 0, image.getWidth(), image.getHeight()); 66 | } 67 | 68 | void DelayAudioProcessorEditor::resized() 69 | { 70 | auto bounds = getLocalBounds(); 71 | 72 | int y = 50; 73 | int height = bounds.getHeight() - 60; 74 | 75 | // Position the groups 76 | delayGroup.setBounds(10, y, 110, height); 77 | 78 | outputGroup.setBounds(bounds.getWidth() - 160, y, 150, height); 79 | 80 | feedbackGroup.setBounds(delayGroup.getRight() + 10, y, 81 | outputGroup.getX() - delayGroup.getRight() - 20, 82 | height); 83 | 84 | // Position the knobs inside the groups 85 | delayTimeKnob.setTopLeftPosition(20, 20); 86 | mixKnob.setTopLeftPosition(20, 20); 87 | gainKnob.setTopLeftPosition(mixKnob.getX(), mixKnob.getBottom() + 10); 88 | feedbackKnob.setTopLeftPosition(20, 20); 89 | stereoKnob.setTopLeftPosition(feedbackKnob.getRight() + 20, 20); 90 | } 91 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | #include "Parameters.h" 14 | #include "RotaryKnob.h" 15 | #include "LookAndFeel.h" 16 | 17 | //============================================================================== 18 | /** 19 | */ 20 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor 21 | { 22 | public: 23 | DelayAudioProcessorEditor (DelayAudioProcessor&); 24 | ~DelayAudioProcessorEditor() override; 25 | 26 | //============================================================================== 27 | void paint (juce::Graphics&) override; 28 | void resized() override; 29 | 30 | private: 31 | // This reference is provided as a quick way for your editor to 32 | // access the processor object that created it. 33 | DelayAudioProcessor& audioProcessor; 34 | 35 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 36 | 37 | RotaryKnob gainKnob { "Gain", audioProcessor.apvts, gainParamID, true }; 38 | RotaryKnob mixKnob { "Mix", audioProcessor.apvts, mixParamID }; 39 | RotaryKnob delayTimeKnob { "Time", audioProcessor.apvts, delayTimeParamID }; 40 | RotaryKnob feedbackKnob { "Feedback", audioProcessor.apvts, feedbackParamID, true }; 41 | RotaryKnob stereoKnob { "Stereo", audioProcessor.apvts, stereoParamID, true }; 42 | 43 | juce::GroupComponent delayGroup, feedbackGroup, outputGroup; 44 | 45 | MainLookAndFeel mainLF; 46 | }; 47 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "Parameters.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class DelayAudioProcessor : public juce::AudioProcessor 18 | { 19 | public: 20 | //============================================================================== 21 | DelayAudioProcessor(); 22 | ~DelayAudioProcessor() override; 23 | 24 | //============================================================================== 25 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 26 | void releaseResources() override; 27 | 28 | #ifndef JucePlugin_PreferredChannelConfigurations 29 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 30 | #endif 31 | 32 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 33 | 34 | //============================================================================== 35 | juce::AudioProcessorEditor* createEditor() override; 36 | bool hasEditor() const override; 37 | 38 | //============================================================================== 39 | const juce::String getName() const override; 40 | 41 | bool acceptsMidi() const override; 42 | bool producesMidi() const override; 43 | bool isMidiEffect() const override; 44 | double getTailLengthSeconds() const override; 45 | 46 | //============================================================================== 47 | int getNumPrograms() override; 48 | int getCurrentProgram() override; 49 | void setCurrentProgram (int index) override; 50 | const juce::String getProgramName (int index) override; 51 | void changeProgramName (int index, const juce::String& newName) override; 52 | 53 | //============================================================================== 54 | void getStateInformation (juce::MemoryBlock& destData) override; 55 | void setStateInformation (const void* data, int sizeInBytes) override; 56 | 57 | juce::AudioProcessorValueTreeState apvts { 58 | *this, nullptr, "Parameters", Parameters::createParameterLayout() 59 | }; 60 | 61 | private: 62 | Parameters params; 63 | 64 | juce::dsp::DelayLine delayLine; 65 | 66 | float feedbackL = 0.0f; 67 | float feedbackR = 0.0f; 68 | 69 | //============================================================================== 70 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 71 | }; 72 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/ProtectYourEars.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Silences the buffer if bad or loud values are detected in the output buffer. 6 | // Use this during debugging to avoid blowing out your eardrums on headphones. 7 | inline void protectYourEars(juce::AudioBuffer& buffer) 8 | { 9 | bool firstWarning = true; 10 | for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { 11 | float* channelData = buffer.getWritePointer(channel); 12 | for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { 13 | float x = channelData[sample]; 14 | bool silence = false; 15 | if (std::isnan(x)) { 16 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 17 | silence = true; 18 | } else if (std::isinf(x)) { 19 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 20 | silence = true; 21 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 22 | DBG("!!! WARNING: sample out of range, silencing !!!"); 23 | silence = true; 24 | } else if (x < -1.0f || x > 1.0f) { 25 | if (firstWarning) { 26 | DBG("!!! WARNING: sample out of range: " << x << " !!!"); 27 | firstWarning = false; 28 | } 29 | } 30 | if (silence) { 31 | buffer.clear(); 32 | return; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/RotaryKnob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RotaryKnob.h" 3 | #include "LookAndFeel.h" 4 | 5 | RotaryKnob::RotaryKnob(const juce::String& text, 6 | juce::AudioProcessorValueTreeState& apvts, 7 | const juce::ParameterID& parameterID, 8 | bool drawFromMiddle) 9 | : attachment(apvts, parameterID.getParamID(), slider) 10 | { 11 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); 12 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 70, 16); 13 | slider.setBounds(0, 0, 70, 86); 14 | addAndMakeVisible(slider); 15 | 16 | label.setText(text, juce::NotificationType::dontSendNotification); 17 | label.setJustificationType(juce::Justification::horizontallyCentred); 18 | label.setBorderSize(juce::BorderSize{ 0, 0, 2, 0 }); 19 | label.attachToComponent(&slider, false); 20 | addAndMakeVisible(label); 21 | 22 | setSize(70, 110); 23 | 24 | setLookAndFeel(RotaryKnobLookAndFeel::get()); 25 | 26 | float pi = juce::MathConstants::pi; 27 | slider.setRotaryParameters(1.25f * pi, 2.75f * pi, true); 28 | 29 | slider.getProperties().set("drawFromMiddle", drawFromMiddle); 30 | } 31 | 32 | RotaryKnob::~RotaryKnob() 33 | { 34 | } 35 | 36 | void RotaryKnob::resized() 37 | { 38 | slider.setTopLeftPosition(0, 24); 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/RotaryKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RotaryKnob : public juce::Component 6 | { 7 | public: 8 | RotaryKnob(const juce::String& text, 9 | juce::AudioProcessorValueTreeState& apvts, 10 | const juce::ParameterID& parameterID, 11 | bool drawFromMiddle = false); 12 | 13 | ~RotaryKnob() override; 14 | 15 | void resized() override; 16 | 17 | juce::Slider slider; 18 | juce::Label label; 19 | 20 | juce::AudioProcessorValueTreeState::SliderAttachment attachment; 21 | 22 | private: 23 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RotaryKnob) 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 14/Source/DSP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | Calculates panning using equal power curves formula. Panning must be [-1, 1]. 7 | */ 8 | inline void panningEqualPower(float panning, float& left, float& right) 9 | { 10 | float x = 0.7853981633974483f * (panning + 1.0f); 11 | left = std::cos(x); 12 | right = std::sin(x); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 14/Source/LookAndFeel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Colors 6 | { 7 | const juce::Colour background { 245, 240, 235 }; 8 | const juce::Colour header { 40, 40, 40 }; 9 | 10 | namespace Knob 11 | { 12 | const juce::Colour trackBackground { 205, 200, 195 }; 13 | const juce::Colour trackActive { 177, 101, 135 }; 14 | const juce::Colour outline { 255, 250, 245 }; 15 | const juce::Colour gradientTop { 250, 245, 240 }; 16 | const juce::Colour gradientBottom { 240, 235, 230 }; 17 | const juce::Colour dial { 100, 100, 100 }; 18 | const juce::Colour dropShadow { 195, 190, 185 }; 19 | const juce::Colour label { 80, 80, 80 }; 20 | const juce::Colour textBoxBackground { 80, 80, 80 }; 21 | const juce::Colour value { 240, 240, 240 }; 22 | const juce::Colour caret { 255, 255, 255 }; 23 | } 24 | 25 | namespace Group 26 | { 27 | const juce::Colour label { 160, 155, 150 }; 28 | const juce::Colour outline { 235, 230, 225 }; 29 | } 30 | } 31 | 32 | class Fonts 33 | { 34 | public: 35 | Fonts() = delete; 36 | 37 | static juce::Font getFont(float height = 16.0f); 38 | 39 | private: 40 | static const juce::Typeface::Ptr typeface; 41 | }; 42 | 43 | class RotaryKnobLookAndFeel : public juce::LookAndFeel_V4 44 | { 45 | public: 46 | RotaryKnobLookAndFeel(); 47 | 48 | static RotaryKnobLookAndFeel* get() 49 | { 50 | static RotaryKnobLookAndFeel instance; 51 | return &instance; 52 | } 53 | 54 | void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, 55 | float sliderPos, float rotaryStartAngle, 56 | float rotaryEndAngle, juce::Slider& slider) override; 57 | 58 | juce::Font getLabelFont(juce::Label&) override; 59 | juce::Label* createSliderTextBox(juce::Slider&) override; 60 | 61 | void drawTextEditorOutline(juce::Graphics&, int, int, juce::TextEditor&) override { } 62 | void fillTextEditorBackground(juce::Graphics&, int width, int height, juce::TextEditor&) override; 63 | 64 | private: 65 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RotaryKnobLookAndFeel) 66 | 67 | juce::DropShadow dropShadow { Colors::Knob::dropShadow, 6, { 0, 3 } }; 68 | }; 69 | 70 | class MainLookAndFeel : public juce::LookAndFeel_V4 71 | { 72 | public: 73 | MainLookAndFeel(); 74 | 75 | juce::Font getLabelFont(juce::Label&) override; 76 | 77 | private: 78 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainLookAndFeel) 79 | }; 80 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 14/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | const juce::ParameterID delayTimeParamID { "delayTime", 1 }; 7 | const juce::ParameterID mixParamID { "mix", 1 }; 8 | const juce::ParameterID feedbackParamID { "feedback", 1 }; 9 | const juce::ParameterID stereoParamID { "stereo", 1 }; 10 | const juce::ParameterID lowCutParamID { "lowCut", 1 }; 11 | const juce::ParameterID highCutParamID { "highCut", 1 }; 12 | 13 | class Parameters 14 | { 15 | public: 16 | Parameters(juce::AudioProcessorValueTreeState& apvts); 17 | 18 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 19 | 20 | void prepareToPlay(double sampleRate) noexcept; 21 | void reset() noexcept; 22 | void update() noexcept; 23 | void smoothen() noexcept; 24 | 25 | float gain = 0.0f; 26 | float delayTime = 0.0f; 27 | float mix = 1.0f; 28 | float feedback = 0.0f; 29 | float panL = 0.0f; 30 | float panR = 1.0f; 31 | float lowCut = 20.0f; 32 | float highCut = 20000.0f; 33 | 34 | static constexpr float minDelayTime = 5.0f; 35 | static constexpr float maxDelayTime = 5000.0f; 36 | 37 | private: 38 | juce::AudioParameterFloat* gainParam; 39 | juce::LinearSmoothedValue gainSmoother; 40 | 41 | juce::AudioParameterFloat* delayTimeParam; 42 | 43 | float targetDelayTime = 0.0f; 44 | float coeff = 0.0f; // one-pole smoothing 45 | 46 | juce::AudioParameterFloat* mixParam; 47 | juce::LinearSmoothedValue mixSmoother; 48 | 49 | juce::AudioParameterFloat* feedbackParam; 50 | juce::LinearSmoothedValue feedbackSmoother; 51 | 52 | juce::AudioParameterFloat* stereoParam; 53 | juce::LinearSmoothedValue stereoSmoother; 54 | 55 | juce::AudioParameterFloat* lowCutParam; 56 | juce::LinearSmoothedValue lowCutSmoother; 57 | 58 | juce::AudioParameterFloat* highCutParam; 59 | juce::LinearSmoothedValue highCutSmoother; 60 | }; 61 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 14/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | //============================================================================== 13 | DelayAudioProcessorEditor::DelayAudioProcessorEditor (DelayAudioProcessor& p) 14 | : AudioProcessorEditor (&p), audioProcessor (p) 15 | { 16 | delayGroup.setText("Delay"); 17 | delayGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 18 | delayGroup.addAndMakeVisible(delayTimeKnob); 19 | addAndMakeVisible(delayGroup); 20 | 21 | feedbackGroup.setText("Feedback"); 22 | feedbackGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 23 | feedbackGroup.addAndMakeVisible(feedbackKnob); 24 | feedbackGroup.addAndMakeVisible(stereoKnob); 25 | feedbackGroup.addAndMakeVisible(lowCutKnob); 26 | feedbackGroup.addAndMakeVisible(highCutKnob); 27 | addAndMakeVisible(feedbackGroup); 28 | 29 | outputGroup.setText("Output"); 30 | outputGroup.setTextLabelPosition(juce::Justification::horizontallyCentred); 31 | outputGroup.addAndMakeVisible(gainKnob); 32 | outputGroup.addAndMakeVisible(mixKnob); 33 | addAndMakeVisible(outputGroup); 34 | 35 | setSize(500, 330); 36 | 37 | //gainKnob.slider.setColour(juce::Slider::rotarySliderFillColourId, juce::Colours::green); 38 | 39 | setLookAndFeel(&mainLF); 40 | } 41 | 42 | DelayAudioProcessorEditor::~DelayAudioProcessorEditor() 43 | { 44 | setLookAndFeel(nullptr); 45 | } 46 | 47 | //============================================================================== 48 | void DelayAudioProcessorEditor::paint (juce::Graphics& g) 49 | { 50 | auto noise = juce::ImageCache::getFromMemory( 51 | BinaryData::Noise_png, BinaryData::Noise_pngSize); 52 | auto fillType = juce::FillType(noise, juce::AffineTransform::scale(0.5f)); 53 | g.setFillType(fillType); 54 | g.fillRect(getLocalBounds()); 55 | 56 | auto rect = getLocalBounds().withHeight(40); 57 | g.setColour(Colors::header); 58 | g.fillRect(rect); 59 | 60 | auto image = juce::ImageCache::getFromMemory( 61 | BinaryData::Logo_png, BinaryData::Logo_pngSize); 62 | 63 | int destWidth = image.getWidth() / 2; 64 | int destHeight = image.getHeight() / 2; 65 | g.drawImage(image, 66 | getWidth() / 2 - destWidth / 2, 0, destWidth, destHeight, 67 | 0, 0, image.getWidth(), image.getHeight()); 68 | } 69 | 70 | void DelayAudioProcessorEditor::resized() 71 | { 72 | auto bounds = getLocalBounds(); 73 | 74 | int y = 50; 75 | int height = bounds.getHeight() - 60; 76 | 77 | // Position the groups 78 | delayGroup.setBounds(10, y, 110, height); 79 | 80 | outputGroup.setBounds(bounds.getWidth() - 160, y, 150, height); 81 | 82 | feedbackGroup.setBounds(delayGroup.getRight() + 10, y, 83 | outputGroup.getX() - delayGroup.getRight() - 20, 84 | height); 85 | 86 | // Position the knobs inside the groups 87 | delayTimeKnob.setTopLeftPosition(20, 20); 88 | mixKnob.setTopLeftPosition(20, 20); 89 | gainKnob.setTopLeftPosition(mixKnob.getX(), mixKnob.getBottom() + 10); 90 | feedbackKnob.setTopLeftPosition(20, 20); 91 | stereoKnob.setTopLeftPosition(feedbackKnob.getRight() + 20, 20); 92 | lowCutKnob.setTopLeftPosition(feedbackKnob.getX(), feedbackKnob.getBottom() + 10); 93 | highCutKnob.setTopLeftPosition(lowCutKnob.getRight() + 20, lowCutKnob.getY()); 94 | } 95 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 14/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | #include "Parameters.h" 14 | #include "RotaryKnob.h" 15 | #include "LookAndFeel.h" 16 | 17 | //============================================================================== 18 | /** 19 | */ 20 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor 21 | { 22 | public: 23 | DelayAudioProcessorEditor (DelayAudioProcessor&); 24 | ~DelayAudioProcessorEditor() override; 25 | 26 | //============================================================================== 27 | void paint (juce::Graphics&) override; 28 | void resized() override; 29 | 30 | private: 31 | // This reference is provided as a quick way for your editor to 32 | // access the processor object that created it. 33 | DelayAudioProcessor& audioProcessor; 34 | 35 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 36 | 37 | RotaryKnob gainKnob { "Gain", audioProcessor.apvts, gainParamID, true }; 38 | RotaryKnob mixKnob { "Mix", audioProcessor.apvts, mixParamID }; 39 | RotaryKnob delayTimeKnob { "Time", audioProcessor.apvts, delayTimeParamID }; 40 | RotaryKnob feedbackKnob { "Feedback", audioProcessor.apvts, feedbackParamID, true }; 41 | RotaryKnob stereoKnob { "Stereo", audioProcessor.apvts, stereoParamID, true }; 42 | RotaryKnob lowCutKnob { "Low Cut", audioProcessor.apvts, lowCutParamID }; 43 | RotaryKnob highCutKnob { "High Cut", audioProcessor.apvts, highCutParamID }; 44 | 45 | juce::GroupComponent delayGroup, feedbackGroup, outputGroup; 46 | 47 | MainLookAndFeel mainLF; 48 | }; 49 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 14/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "Parameters.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class DelayAudioProcessor : public juce::AudioProcessor 18 | { 19 | public: 20 | //============================================================================== 21 | DelayAudioProcessor(); 22 | ~DelayAudioProcessor() override; 23 | 24 | //============================================================================== 25 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 26 | void releaseResources() override; 27 | 28 | #ifndef JucePlugin_PreferredChannelConfigurations 29 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 30 | #endif 31 | 32 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 33 | 34 | //============================================================================== 35 | juce::AudioProcessorEditor* createEditor() override; 36 | bool hasEditor() const override; 37 | 38 | //============================================================================== 39 | const juce::String getName() const override; 40 | 41 | bool acceptsMidi() const override; 42 | bool producesMidi() const override; 43 | bool isMidiEffect() const override; 44 | double getTailLengthSeconds() const override; 45 | 46 | //============================================================================== 47 | int getNumPrograms() override; 48 | int getCurrentProgram() override; 49 | void setCurrentProgram (int index) override; 50 | const juce::String getProgramName (int index) override; 51 | void changeProgramName (int index, const juce::String& newName) override; 52 | 53 | //============================================================================== 54 | void getStateInformation (juce::MemoryBlock& destData) override; 55 | void setStateInformation (const void* data, int sizeInBytes) override; 56 | 57 | juce::AudioProcessorValueTreeState apvts { 58 | *this, nullptr, "Parameters", Parameters::createParameterLayout() 59 | }; 60 | 61 | private: 62 | Parameters params; 63 | 64 | juce::dsp::DelayLine delayLine; 65 | 66 | float feedbackL = 0.0f; 67 | float feedbackR = 0.0f; 68 | 69 | juce::dsp::StateVariableTPTFilter lowCutFilter; 70 | juce::dsp::StateVariableTPTFilter highCutFilter; 71 | 72 | float lastLowCut = -1.0f; 73 | float lastHighCut = -1.0f; 74 | 75 | //============================================================================== 76 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 77 | }; 78 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 14/Source/ProtectYourEars.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Silences the buffer if bad or loud values are detected in the output buffer. 6 | // Use this during debugging to avoid blowing out your eardrums on headphones. 7 | inline void protectYourEars(juce::AudioBuffer& buffer) 8 | { 9 | bool firstWarning = true; 10 | for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { 11 | float* channelData = buffer.getWritePointer(channel); 12 | for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { 13 | float x = channelData[sample]; 14 | bool silence = false; 15 | if (std::isnan(x)) { 16 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 17 | silence = true; 18 | } else if (std::isinf(x)) { 19 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 20 | silence = true; 21 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 22 | DBG("!!! WARNING: sample out of range, silencing !!!"); 23 | silence = true; 24 | } else if (x < -1.0f || x > 1.0f) { 25 | if (firstWarning) { 26 | DBG("!!! WARNING: sample out of range: " << x << " !!!"); 27 | firstWarning = false; 28 | } 29 | } 30 | if (silence) { 31 | buffer.clear(); 32 | return; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 14/Source/RotaryKnob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RotaryKnob.h" 3 | #include "LookAndFeel.h" 4 | 5 | RotaryKnob::RotaryKnob(const juce::String& text, 6 | juce::AudioProcessorValueTreeState& apvts, 7 | const juce::ParameterID& parameterID, 8 | bool drawFromMiddle) 9 | : attachment(apvts, parameterID.getParamID(), slider) 10 | { 11 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); 12 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 70, 16); 13 | slider.setBounds(0, 0, 70, 86); 14 | addAndMakeVisible(slider); 15 | 16 | label.setText(text, juce::NotificationType::dontSendNotification); 17 | label.setJustificationType(juce::Justification::horizontallyCentred); 18 | label.setBorderSize(juce::BorderSize{ 0, 0, 2, 0 }); 19 | label.attachToComponent(&slider, false); 20 | addAndMakeVisible(label); 21 | 22 | setSize(70, 110); 23 | 24 | setLookAndFeel(RotaryKnobLookAndFeel::get()); 25 | 26 | float pi = juce::MathConstants::pi; 27 | slider.setRotaryParameters(1.25f * pi, 2.75f * pi, true); 28 | 29 | slider.getProperties().set("drawFromMiddle", drawFromMiddle); 30 | } 31 | 32 | RotaryKnob::~RotaryKnob() 33 | { 34 | } 35 | 36 | void RotaryKnob::resized() 37 | { 38 | slider.setTopLeftPosition(0, 24); 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 14/Source/RotaryKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RotaryKnob : public juce::Component 6 | { 7 | public: 8 | RotaryKnob(const juce::String& text, 9 | juce::AudioProcessorValueTreeState& apvts, 10 | const juce::ParameterID& parameterID, 11 | bool drawFromMiddle = false); 12 | 13 | ~RotaryKnob() override; 14 | 15 | void resized() override; 16 | 17 | juce::Slider slider; 18 | juce::Label label; 19 | 20 | juce::AudioProcessorValueTreeState::SliderAttachment attachment; 21 | 22 | private: 23 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RotaryKnob) 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 15/Source/DSP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | Calculates panning using equal power curves formula. Panning must be [-1, 1]. 7 | */ 8 | inline void panningEqualPower(float panning, float& left, float& right) 9 | { 10 | float x = 0.7853981633974483f * (panning + 1.0f); 11 | left = std::cos(x); 12 | right = std::sin(x); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 15/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | const juce::ParameterID delayTimeParamID { "delayTime", 1 }; 7 | const juce::ParameterID mixParamID { "mix", 1 }; 8 | const juce::ParameterID feedbackParamID { "feedback", 1 }; 9 | const juce::ParameterID stereoParamID { "stereo", 1 }; 10 | const juce::ParameterID lowCutParamID { "lowCut", 1 }; 11 | const juce::ParameterID highCutParamID { "highCut", 1 }; 12 | const juce::ParameterID tempoSyncParamID { "tempoSync", 1 }; 13 | const juce::ParameterID delayNoteParamID { "delayNote", 1 }; 14 | 15 | class Parameters 16 | { 17 | public: 18 | Parameters(juce::AudioProcessorValueTreeState& apvts); 19 | 20 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 21 | 22 | void prepareToPlay(double sampleRate) noexcept; 23 | void reset() noexcept; 24 | void update() noexcept; 25 | void smoothen() noexcept; 26 | 27 | float gain = 0.0f; 28 | float delayTime = 0.0f; 29 | float mix = 1.0f; 30 | float feedback = 0.0f; 31 | float panL = 0.0f; 32 | float panR = 1.0f; 33 | float lowCut = 20.0f; 34 | float highCut = 20000.0f; 35 | int delayNote = 0; 36 | bool tempoSync = false; 37 | 38 | static constexpr float minDelayTime = 5.0f; 39 | static constexpr float maxDelayTime = 5000.0f; 40 | 41 | juce::AudioParameterBool* tempoSyncParam; 42 | 43 | private: 44 | juce::AudioParameterFloat* gainParam; 45 | juce::LinearSmoothedValue gainSmoother; 46 | 47 | juce::AudioParameterFloat* delayTimeParam; 48 | 49 | float targetDelayTime = 0.0f; 50 | float coeff = 0.0f; // one-pole smoothing 51 | 52 | juce::AudioParameterFloat* mixParam; 53 | juce::LinearSmoothedValue mixSmoother; 54 | 55 | juce::AudioParameterFloat* feedbackParam; 56 | juce::LinearSmoothedValue feedbackSmoother; 57 | 58 | juce::AudioParameterFloat* stereoParam; 59 | juce::LinearSmoothedValue stereoSmoother; 60 | 61 | juce::AudioParameterFloat* lowCutParam; 62 | juce::LinearSmoothedValue lowCutSmoother; 63 | 64 | juce::AudioParameterFloat* highCutParam; 65 | juce::LinearSmoothedValue highCutSmoother; 66 | 67 | juce::AudioParameterChoice* delayNoteParam; 68 | }; 69 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 15/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | #include "Parameters.h" 14 | #include "RotaryKnob.h" 15 | #include "LookAndFeel.h" 16 | 17 | //============================================================================== 18 | /** 19 | */ 20 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor, 21 | private juce::AudioProcessorParameter::Listener 22 | { 23 | public: 24 | DelayAudioProcessorEditor (DelayAudioProcessor&); 25 | ~DelayAudioProcessorEditor() override; 26 | 27 | //============================================================================== 28 | void paint (juce::Graphics&) override; 29 | void resized() override; 30 | 31 | private: 32 | void parameterValueChanged(int, float) override; 33 | void parameterGestureChanged(int, bool) override { } 34 | 35 | void updateDelayKnobs(bool tempoSyncActive); 36 | 37 | // This reference is provided as a quick way for your editor to 38 | // access the processor object that created it. 39 | DelayAudioProcessor& audioProcessor; 40 | 41 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 42 | 43 | RotaryKnob gainKnob { "Gain", audioProcessor.apvts, gainParamID, true }; 44 | RotaryKnob mixKnob { "Mix", audioProcessor.apvts, mixParamID }; 45 | RotaryKnob delayTimeKnob { "Time", audioProcessor.apvts, delayTimeParamID }; 46 | RotaryKnob feedbackKnob { "Feedback", audioProcessor.apvts, feedbackParamID, true }; 47 | RotaryKnob stereoKnob { "Stereo", audioProcessor.apvts, stereoParamID, true }; 48 | RotaryKnob lowCutKnob { "Low Cut", audioProcessor.apvts, lowCutParamID }; 49 | RotaryKnob highCutKnob { "High Cut", audioProcessor.apvts, highCutParamID }; 50 | RotaryKnob delayNoteKnob { "Note", audioProcessor.apvts, delayNoteParamID }; 51 | 52 | juce::TextButton tempoSyncButton; 53 | 54 | juce::AudioProcessorValueTreeState::ButtonAttachment tempoSyncAttachment { 55 | audioProcessor.apvts, tempoSyncParamID.getParamID(), tempoSyncButton 56 | }; 57 | 58 | juce::GroupComponent delayGroup, feedbackGroup, outputGroup; 59 | 60 | MainLookAndFeel mainLF; 61 | }; 62 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 15/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "Parameters.h" 13 | #include "Tempo.h" 14 | 15 | //============================================================================== 16 | /** 17 | */ 18 | class DelayAudioProcessor : public juce::AudioProcessor 19 | { 20 | public: 21 | //============================================================================== 22 | DelayAudioProcessor(); 23 | ~DelayAudioProcessor() override; 24 | 25 | //============================================================================== 26 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 27 | void releaseResources() override; 28 | 29 | #ifndef JucePlugin_PreferredChannelConfigurations 30 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 31 | #endif 32 | 33 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 34 | 35 | //============================================================================== 36 | juce::AudioProcessorEditor* createEditor() override; 37 | bool hasEditor() const override; 38 | 39 | //============================================================================== 40 | const juce::String getName() const override; 41 | 42 | bool acceptsMidi() const override; 43 | bool producesMidi() const override; 44 | bool isMidiEffect() const override; 45 | double getTailLengthSeconds() const override; 46 | 47 | //============================================================================== 48 | int getNumPrograms() override; 49 | int getCurrentProgram() override; 50 | void setCurrentProgram (int index) override; 51 | const juce::String getProgramName (int index) override; 52 | void changeProgramName (int index, const juce::String& newName) override; 53 | 54 | //============================================================================== 55 | void getStateInformation (juce::MemoryBlock& destData) override; 56 | void setStateInformation (const void* data, int sizeInBytes) override; 57 | 58 | juce::AudioProcessorValueTreeState apvts { 59 | *this, nullptr, "Parameters", Parameters::createParameterLayout() 60 | }; 61 | 62 | Parameters params; 63 | 64 | private: 65 | juce::dsp::DelayLine delayLine; 66 | 67 | float feedbackL = 0.0f; 68 | float feedbackR = 0.0f; 69 | 70 | juce::dsp::StateVariableTPTFilter lowCutFilter; 71 | juce::dsp::StateVariableTPTFilter highCutFilter; 72 | 73 | float lastLowCut = -1.0f; 74 | float lastHighCut = -1.0f; 75 | 76 | Tempo tempo; 77 | 78 | //============================================================================== 79 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 80 | }; 81 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 15/Source/ProtectYourEars.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Silences the buffer if bad or loud values are detected in the output buffer. 6 | // Use this during debugging to avoid blowing out your eardrums on headphones. 7 | inline void protectYourEars(juce::AudioBuffer& buffer) 8 | { 9 | bool firstWarning = true; 10 | for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { 11 | float* channelData = buffer.getWritePointer(channel); 12 | for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { 13 | float x = channelData[sample]; 14 | bool silence = false; 15 | if (std::isnan(x)) { 16 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 17 | silence = true; 18 | } else if (std::isinf(x)) { 19 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 20 | silence = true; 21 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 22 | DBG("!!! WARNING: sample out of range, silencing !!!"); 23 | silence = true; 24 | } else if (x < -1.0f || x > 1.0f) { 25 | if (firstWarning) { 26 | DBG("!!! WARNING: sample out of range: " << x << " !!!"); 27 | firstWarning = false; 28 | } 29 | } 30 | if (silence) { 31 | buffer.clear(); 32 | return; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 15/Source/RotaryKnob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RotaryKnob.h" 3 | #include "LookAndFeel.h" 4 | 5 | RotaryKnob::RotaryKnob(const juce::String& text, 6 | juce::AudioProcessorValueTreeState& apvts, 7 | const juce::ParameterID& parameterID, 8 | bool drawFromMiddle) 9 | : attachment(apvts, parameterID.getParamID(), slider) 10 | { 11 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); 12 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 70, 16); 13 | slider.setBounds(0, 0, 70, 86); 14 | addAndMakeVisible(slider); 15 | 16 | label.setText(text, juce::NotificationType::dontSendNotification); 17 | label.setJustificationType(juce::Justification::horizontallyCentred); 18 | label.setBorderSize(juce::BorderSize{ 0, 0, 2, 0 }); 19 | label.attachToComponent(&slider, false); 20 | addAndMakeVisible(label); 21 | 22 | setSize(70, 110); 23 | 24 | setLookAndFeel(RotaryKnobLookAndFeel::get()); 25 | 26 | float pi = juce::MathConstants::pi; 27 | slider.setRotaryParameters(1.25f * pi, 2.75f * pi, true); 28 | 29 | slider.getProperties().set("drawFromMiddle", drawFromMiddle); 30 | } 31 | 32 | RotaryKnob::~RotaryKnob() 33 | { 34 | } 35 | 36 | void RotaryKnob::resized() 37 | { 38 | slider.setTopLeftPosition(0, 24); 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 15/Source/RotaryKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RotaryKnob : public juce::Component 6 | { 7 | public: 8 | RotaryKnob(const juce::String& text, 9 | juce::AudioProcessorValueTreeState& apvts, 10 | const juce::ParameterID& parameterID, 11 | bool drawFromMiddle = false); 12 | 13 | ~RotaryKnob() override; 14 | 15 | void resized() override; 16 | 17 | juce::Slider slider; 18 | juce::Label label; 19 | 20 | juce::AudioProcessorValueTreeState::SliderAttachment attachment; 21 | 22 | private: 23 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RotaryKnob) 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 15/Source/Tempo.cpp: -------------------------------------------------------------------------------- 1 | #include "Tempo.h" 2 | 3 | static std::array noteLengthMultipliers = 4 | { 5 | 0.125, // 0 = 1/32 6 | 0.5 / 3.0, // 1 = 1/16 triplet 7 | 0.1875, // 2 = 1/32 dotted 8 | 0.25, // 3 = 1/16 9 | 1.0 / 3.0, // 4 = 1/8 triplet 10 | 0.375, // 5 = 1/16 dotted 11 | 0.5, // 6 = 1/8 12 | 2.0 / 3.0, // 7 = 1/4 triplet 13 | 0.75, // 8 = 1/8 dotted 14 | 1.0, // 9 = 1/4 15 | 4.0 / 3.0, // 10 = 1/2 triplet 16 | 1.5, // 11 = 1/4 dotted 17 | 2.0, // 12 = 1/2 18 | 8.0 / 3.0, // 13 = 1/1 triplet 19 | 3.0, // 14 = 1/2 dotted 20 | 4.0, // 15 = 1/1 21 | }; 22 | 23 | void Tempo::reset() noexcept 24 | { 25 | bpm = 120.0; 26 | } 27 | 28 | void Tempo::update(const juce::AudioPlayHead* playhead) noexcept 29 | { 30 | reset(); 31 | 32 | if (playhead == nullptr) { return; } 33 | 34 | const auto opt = playhead->getPosition(); 35 | if (!opt.hasValue()) { return; } 36 | 37 | const auto& pos = *opt; 38 | 39 | if (pos.getBpm().hasValue()) { 40 | bpm = *pos.getBpm(); 41 | } 42 | } 43 | 44 | double Tempo::getMillisecondsForNoteLength(int index) const noexcept 45 | { 46 | return 60000.0 * noteLengthMultipliers[size_t(index)] / bpm; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 15/Source/Tempo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Tempo 6 | { 7 | public: 8 | void reset() noexcept; 9 | 10 | void update(const juce::AudioPlayHead* playhead) noexcept; 11 | 12 | double getMillisecondsForNoteLength(int index) const noexcept; 13 | 14 | double getTempo() const noexcept 15 | { 16 | return bpm; 17 | } 18 | 19 | private: 20 | double bpm = 120.0; 21 | }; 22 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 16/Source/DSP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | Calculates panning using equal power curves formula. Panning must be [-1, 1]. 7 | */ 8 | inline void panningEqualPower(float panning, float& left, float& right) 9 | { 10 | float x = 0.7853981633974483f * (panning + 1.0f); 11 | left = std::cos(x); 12 | right = std::sin(x); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 16/Source/DelayLine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "DelayLine.h" 3 | 4 | void DelayLine::setMaximumDelayInSamples(int maxLengthInSamples) 5 | { 6 | jassert(maxLengthInSamples > 0); 7 | 8 | int paddedLength = maxLengthInSamples + 2; 9 | if (bufferLength < paddedLength) { 10 | bufferLength = paddedLength; 11 | buffer.reset(new float[size_t(bufferLength)]); 12 | } 13 | } 14 | 15 | void DelayLine::reset() noexcept 16 | { 17 | writeIndex = bufferLength - 1; 18 | 19 | for (size_t i = 0; i < size_t(bufferLength); ++i) { 20 | buffer[i] = 0.0f; 21 | } 22 | } 23 | 24 | void DelayLine::write(float input) noexcept 25 | { 26 | jassert(bufferLength > 0); 27 | 28 | writeIndex += 1; 29 | if (writeIndex >= bufferLength) { 30 | writeIndex = 0; 31 | } 32 | 33 | buffer[size_t(writeIndex)] = input; 34 | } 35 | 36 | /* 37 | // Nearest neighbors 38 | float DelayLine::read(float delayInSamples) const noexcept 39 | { 40 | jassert(delayInSamples >= 0.0f); 41 | jassert(delayInSamples <= bufferLength - 1.0f); 42 | 43 | int readIndex = int(std::round(writeIndex - delayInSamples)); 44 | if (readIndex < 0) { 45 | readIndex += bufferLength; 46 | } 47 | 48 | return buffer[size_t(readIndex)]; 49 | } 50 | */ 51 | 52 | /* 53 | // Linear interpolation 54 | float DelayLine::read(float delayInSamples) const noexcept 55 | { 56 | jassert(delayInSamples >= 0.0f); 57 | jassert(delayInSamples <= bufferLength - 1.0f); 58 | 59 | int integerDelay = int(delayInSamples); 60 | 61 | int readIndexA = writeIndex - integerDelay; 62 | if (readIndexA < 0) { 63 | readIndexA += bufferLength; 64 | } 65 | 66 | int readIndexB = readIndexA - 1; 67 | if (readIndexB < 0) { 68 | readIndexB += bufferLength; 69 | } 70 | 71 | float sampleA = buffer[size_t(readIndexA)]; 72 | float sampleB = buffer[size_t(readIndexB)]; 73 | 74 | float fraction = delayInSamples - float(integerDelay); 75 | return sampleA + fraction * (sampleB - sampleA); 76 | } 77 | */ 78 | 79 | float DelayLine::read(float delayInSamples) const noexcept 80 | { 81 | jassert(delayInSamples >= 1.0f); 82 | jassert(delayInSamples <= bufferLength - 2.0f); 83 | 84 | int integerDelay = int(delayInSamples); 85 | 86 | int readIndexA = writeIndex - integerDelay + 1; 87 | int readIndexB = readIndexA - 1; 88 | int readIndexC = readIndexA - 2; 89 | int readIndexD = readIndexA - 3; 90 | 91 | if (readIndexD < 0) { 92 | readIndexD += bufferLength; 93 | if (readIndexC < 0) { 94 | readIndexC += bufferLength; 95 | if (readIndexB < 0) { 96 | readIndexB += bufferLength; 97 | if (readIndexA < 0) { 98 | readIndexA += bufferLength; 99 | } 100 | } 101 | } 102 | } 103 | 104 | float sampleA = buffer[size_t(readIndexA)]; 105 | float sampleB = buffer[size_t(readIndexB)]; 106 | float sampleC = buffer[size_t(readIndexC)]; 107 | float sampleD = buffer[size_t(readIndexD)]; 108 | 109 | float fraction = delayInSamples - float(integerDelay); 110 | float slope0 = (sampleC - sampleA) * 0.5f; 111 | float slope1 = (sampleD - sampleB) * 0.5f; 112 | float v = sampleB - sampleC; 113 | float w = slope0 + v; 114 | float a = w + v + slope1; 115 | float b = w + a; 116 | float stage1 = a * fraction - b; 117 | float stage2 = stage1 * fraction + slope0; 118 | return stage2 * fraction + sampleB; 119 | } 120 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 16/Source/DelayLine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class DelayLine 6 | { 7 | public: 8 | void setMaximumDelayInSamples(int maxLengthInSamples); 9 | void reset() noexcept; 10 | 11 | void write(float input) noexcept; 12 | float read(float delayInSamples) const noexcept; 13 | 14 | int getBufferLength() const noexcept 15 | { 16 | return bufferLength; 17 | } 18 | 19 | private: 20 | std::unique_ptr buffer; 21 | int bufferLength = 0; 22 | int writeIndex = 0; // where the most recent value was written 23 | }; 24 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 16/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | const juce::ParameterID delayTimeParamID { "delayTime", 1 }; 7 | const juce::ParameterID mixParamID { "mix", 1 }; 8 | const juce::ParameterID feedbackParamID { "feedback", 1 }; 9 | const juce::ParameterID stereoParamID { "stereo", 1 }; 10 | const juce::ParameterID lowCutParamID { "lowCut", 1 }; 11 | const juce::ParameterID highCutParamID { "highCut", 1 }; 12 | const juce::ParameterID tempoSyncParamID { "tempoSync", 1 }; 13 | const juce::ParameterID delayNoteParamID { "delayNote", 1 }; 14 | 15 | class Parameters 16 | { 17 | public: 18 | Parameters(juce::AudioProcessorValueTreeState& apvts); 19 | 20 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 21 | 22 | void prepareToPlay(double sampleRate) noexcept; 23 | void reset() noexcept; 24 | void update() noexcept; 25 | void smoothen() noexcept; 26 | 27 | float gain = 0.0f; 28 | float delayTime = 0.0f; 29 | float mix = 1.0f; 30 | float feedback = 0.0f; 31 | float panL = 0.0f; 32 | float panR = 1.0f; 33 | float lowCut = 20.0f; 34 | float highCut = 20000.0f; 35 | int delayNote = 0; 36 | bool tempoSync = false; 37 | 38 | static constexpr float minDelayTime = 5.0f; 39 | static constexpr float maxDelayTime = 5000.0f; 40 | 41 | juce::AudioParameterBool* tempoSyncParam; 42 | 43 | private: 44 | juce::AudioParameterFloat* gainParam; 45 | juce::LinearSmoothedValue gainSmoother; 46 | 47 | juce::AudioParameterFloat* delayTimeParam; 48 | 49 | float targetDelayTime = 0.0f; 50 | float coeff = 0.0f; // one-pole smoothing 51 | 52 | juce::AudioParameterFloat* mixParam; 53 | juce::LinearSmoothedValue mixSmoother; 54 | 55 | juce::AudioParameterFloat* feedbackParam; 56 | juce::LinearSmoothedValue feedbackSmoother; 57 | 58 | juce::AudioParameterFloat* stereoParam; 59 | juce::LinearSmoothedValue stereoSmoother; 60 | 61 | juce::AudioParameterFloat* lowCutParam; 62 | juce::LinearSmoothedValue lowCutSmoother; 63 | 64 | juce::AudioParameterFloat* highCutParam; 65 | juce::LinearSmoothedValue highCutSmoother; 66 | 67 | juce::AudioParameterChoice* delayNoteParam; 68 | }; 69 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 16/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | #include "Parameters.h" 14 | #include "RotaryKnob.h" 15 | #include "LookAndFeel.h" 16 | 17 | //============================================================================== 18 | /** 19 | */ 20 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor, 21 | private juce::AudioProcessorParameter::Listener 22 | { 23 | public: 24 | DelayAudioProcessorEditor (DelayAudioProcessor&); 25 | ~DelayAudioProcessorEditor() override; 26 | 27 | //============================================================================== 28 | void paint (juce::Graphics&) override; 29 | void resized() override; 30 | 31 | private: 32 | void parameterValueChanged(int, float) override; 33 | void parameterGestureChanged(int, bool) override { } 34 | 35 | void updateDelayKnobs(bool tempoSyncActive); 36 | 37 | // This reference is provided as a quick way for your editor to 38 | // access the processor object that created it. 39 | DelayAudioProcessor& audioProcessor; 40 | 41 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 42 | 43 | RotaryKnob gainKnob { "Gain", audioProcessor.apvts, gainParamID, true }; 44 | RotaryKnob mixKnob { "Mix", audioProcessor.apvts, mixParamID }; 45 | RotaryKnob delayTimeKnob { "Time", audioProcessor.apvts, delayTimeParamID }; 46 | RotaryKnob feedbackKnob { "Feedback", audioProcessor.apvts, feedbackParamID, true }; 47 | RotaryKnob stereoKnob { "Stereo", audioProcessor.apvts, stereoParamID, true }; 48 | RotaryKnob lowCutKnob { "Low Cut", audioProcessor.apvts, lowCutParamID }; 49 | RotaryKnob highCutKnob { "High Cut", audioProcessor.apvts, highCutParamID }; 50 | RotaryKnob delayNoteKnob { "Note", audioProcessor.apvts, delayNoteParamID }; 51 | 52 | juce::TextButton tempoSyncButton; 53 | 54 | juce::AudioProcessorValueTreeState::ButtonAttachment tempoSyncAttachment { 55 | audioProcessor.apvts, tempoSyncParamID.getParamID(), tempoSyncButton 56 | }; 57 | 58 | juce::GroupComponent delayGroup, feedbackGroup, outputGroup; 59 | 60 | MainLookAndFeel mainLF; 61 | }; 62 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 16/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "Parameters.h" 13 | #include "Tempo.h" 14 | #include "DelayLine.h" 15 | 16 | //============================================================================== 17 | /** 18 | */ 19 | class DelayAudioProcessor : public juce::AudioProcessor 20 | { 21 | public: 22 | //============================================================================== 23 | DelayAudioProcessor(); 24 | ~DelayAudioProcessor() override; 25 | 26 | //============================================================================== 27 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 28 | void releaseResources() override; 29 | 30 | #ifndef JucePlugin_PreferredChannelConfigurations 31 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 32 | #endif 33 | 34 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 35 | 36 | //============================================================================== 37 | juce::AudioProcessorEditor* createEditor() override; 38 | bool hasEditor() const override; 39 | 40 | //============================================================================== 41 | const juce::String getName() const override; 42 | 43 | bool acceptsMidi() const override; 44 | bool producesMidi() const override; 45 | bool isMidiEffect() const override; 46 | double getTailLengthSeconds() const override; 47 | 48 | //============================================================================== 49 | int getNumPrograms() override; 50 | int getCurrentProgram() override; 51 | void setCurrentProgram (int index) override; 52 | const juce::String getProgramName (int index) override; 53 | void changeProgramName (int index, const juce::String& newName) override; 54 | 55 | //============================================================================== 56 | void getStateInformation (juce::MemoryBlock& destData) override; 57 | void setStateInformation (const void* data, int sizeInBytes) override; 58 | 59 | juce::AudioProcessorValueTreeState apvts { 60 | *this, nullptr, "Parameters", Parameters::createParameterLayout() 61 | }; 62 | 63 | Parameters params; 64 | 65 | private: 66 | DelayLine delayLineL, delayLineR; 67 | 68 | float feedbackL = 0.0f; 69 | float feedbackR = 0.0f; 70 | 71 | juce::dsp::StateVariableTPTFilter lowCutFilter; 72 | juce::dsp::StateVariableTPTFilter highCutFilter; 73 | 74 | float lastLowCut = -1.0f; 75 | float lastHighCut = -1.0f; 76 | 77 | Tempo tempo; 78 | 79 | //============================================================================== 80 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 81 | }; 82 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 16/Source/ProtectYourEars.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Silences the buffer if bad or loud values are detected in the output buffer. 6 | // Use this during debugging to avoid blowing out your eardrums on headphones. 7 | inline void protectYourEars(juce::AudioBuffer& buffer) 8 | { 9 | bool firstWarning = true; 10 | for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { 11 | float* channelData = buffer.getWritePointer(channel); 12 | for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { 13 | float x = channelData[sample]; 14 | bool silence = false; 15 | if (std::isnan(x)) { 16 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 17 | silence = true; 18 | } else if (std::isinf(x)) { 19 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 20 | silence = true; 21 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 22 | DBG("!!! WARNING: sample out of range, silencing !!!"); 23 | silence = true; 24 | } else if (x < -1.0f || x > 1.0f) { 25 | if (firstWarning) { 26 | DBG("!!! WARNING: sample out of range: " << x << " !!!"); 27 | firstWarning = false; 28 | } 29 | } 30 | if (silence) { 31 | buffer.clear(); 32 | return; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 16/Source/RotaryKnob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RotaryKnob.h" 3 | #include "LookAndFeel.h" 4 | 5 | RotaryKnob::RotaryKnob(const juce::String& text, 6 | juce::AudioProcessorValueTreeState& apvts, 7 | const juce::ParameterID& parameterID, 8 | bool drawFromMiddle) 9 | : attachment(apvts, parameterID.getParamID(), slider) 10 | { 11 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); 12 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 70, 16); 13 | slider.setBounds(0, 0, 70, 86); 14 | addAndMakeVisible(slider); 15 | 16 | label.setText(text, juce::NotificationType::dontSendNotification); 17 | label.setJustificationType(juce::Justification::horizontallyCentred); 18 | label.setBorderSize(juce::BorderSize{ 0, 0, 2, 0 }); 19 | label.attachToComponent(&slider, false); 20 | addAndMakeVisible(label); 21 | 22 | setSize(70, 110); 23 | 24 | setLookAndFeel(RotaryKnobLookAndFeel::get()); 25 | 26 | float pi = juce::MathConstants::pi; 27 | slider.setRotaryParameters(1.25f * pi, 2.75f * pi, true); 28 | 29 | slider.getProperties().set("drawFromMiddle", drawFromMiddle); 30 | } 31 | 32 | RotaryKnob::~RotaryKnob() 33 | { 34 | } 35 | 36 | void RotaryKnob::resized() 37 | { 38 | slider.setTopLeftPosition(0, 24); 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 16/Source/RotaryKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RotaryKnob : public juce::Component 6 | { 7 | public: 8 | RotaryKnob(const juce::String& text, 9 | juce::AudioProcessorValueTreeState& apvts, 10 | const juce::ParameterID& parameterID, 11 | bool drawFromMiddle = false); 12 | 13 | ~RotaryKnob() override; 14 | 15 | void resized() override; 16 | 17 | juce::Slider slider; 18 | juce::Label label; 19 | 20 | juce::AudioProcessorValueTreeState::SliderAttachment attachment; 21 | 22 | private: 23 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RotaryKnob) 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 16/Source/Tempo.cpp: -------------------------------------------------------------------------------- 1 | #include "Tempo.h" 2 | 3 | static std::array noteLengthMultipliers = 4 | { 5 | 0.125, // 0 = 1/32 6 | 0.5 / 3.0, // 1 = 1/16 triplet 7 | 0.1875, // 2 = 1/32 dotted 8 | 0.25, // 3 = 1/16 9 | 1.0 / 3.0, // 4 = 1/8 triplet 10 | 0.375, // 5 = 1/16 dotted 11 | 0.5, // 6 = 1/8 12 | 2.0 / 3.0, // 7 = 1/4 triplet 13 | 0.75, // 8 = 1/8 dotted 14 | 1.0, // 9 = 1/4 15 | 4.0 / 3.0, // 10 = 1/2 triplet 16 | 1.5, // 11 = 1/4 dotted 17 | 2.0, // 12 = 1/2 18 | 8.0 / 3.0, // 13 = 1/1 triplet 19 | 3.0, // 14 = 1/2 dotted 20 | 4.0, // 15 = 1/1 21 | }; 22 | 23 | void Tempo::reset() noexcept 24 | { 25 | bpm = 120.0; 26 | } 27 | 28 | void Tempo::update(const juce::AudioPlayHead* playhead) noexcept 29 | { 30 | reset(); 31 | 32 | if (playhead == nullptr) { return; } 33 | 34 | const auto opt = playhead->getPosition(); 35 | if (!opt.hasValue()) { return; } 36 | 37 | const auto& pos = *opt; 38 | 39 | if (pos.getBpm().hasValue()) { 40 | bpm = *pos.getBpm(); 41 | } 42 | } 43 | 44 | double Tempo::getMillisecondsForNoteLength(int index) const noexcept 45 | { 46 | return 60000.0 * noteLengthMultipliers[size_t(index)] / bpm; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 16/Source/Tempo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Tempo 6 | { 7 | public: 8 | void reset() noexcept; 9 | 10 | void update(const juce::AudioPlayHead* playhead) noexcept; 11 | 12 | double getMillisecondsForNoteLength(int index) const noexcept; 13 | 14 | double getTempo() const noexcept 15 | { 16 | return bpm; 17 | } 18 | 19 | private: 20 | double bpm = 120.0; 21 | }; 22 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/DSP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | Calculates panning using equal power curves formula. Panning must be [-1, 1]. 7 | */ 8 | inline void panningEqualPower(float panning, float& left, float& right) 9 | { 10 | float x = 0.7853981633974483f * (panning + 1.0f); 11 | left = std::cos(x); 12 | right = std::sin(x); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/DelayLine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "DelayLine.h" 3 | 4 | void DelayLine::setMaximumDelayInSamples(int maxLengthInSamples) 5 | { 6 | jassert(maxLengthInSamples > 0); 7 | 8 | int paddedLength = maxLengthInSamples + 2; 9 | if (bufferLength < paddedLength) { 10 | bufferLength = paddedLength; 11 | buffer.reset(new float[size_t(bufferLength)]); 12 | } 13 | } 14 | 15 | void DelayLine::reset() noexcept 16 | { 17 | writeIndex = bufferLength - 1; 18 | 19 | for (size_t i = 0; i < size_t(bufferLength); ++i) { 20 | buffer[i] = 0.0f; 21 | } 22 | } 23 | 24 | void DelayLine::write(float input) noexcept 25 | { 26 | jassert(bufferLength > 0); 27 | 28 | writeIndex += 1; 29 | if (writeIndex >= bufferLength) { 30 | writeIndex = 0; 31 | } 32 | 33 | buffer[size_t(writeIndex)] = input; 34 | } 35 | 36 | /* 37 | // Nearest neighbors 38 | float DelayLine::read(float delayInSamples) const noexcept 39 | { 40 | jassert(delayInSamples >= 0.0f); 41 | jassert(delayInSamples <= bufferLength - 1.0f); 42 | 43 | int readIndex = int(std::round(writeIndex - delayInSamples)); 44 | if (readIndex < 0) { 45 | readIndex += bufferLength; 46 | } 47 | 48 | return buffer[size_t(readIndex)]; 49 | } 50 | */ 51 | 52 | /* 53 | // Linear interpolation 54 | float DelayLine::read(float delayInSamples) const noexcept 55 | { 56 | jassert(delayInSamples >= 0.0f); 57 | jassert(delayInSamples <= bufferLength - 1.0f); 58 | 59 | int integerDelay = int(delayInSamples); 60 | 61 | int readIndexA = writeIndex - integerDelay; 62 | if (readIndexA < 0) { 63 | readIndexA += bufferLength; 64 | } 65 | 66 | int readIndexB = readIndexA - 1; 67 | if (readIndexB < 0) { 68 | readIndexB += bufferLength; 69 | } 70 | 71 | float sampleA = buffer[size_t(readIndexA)]; 72 | float sampleB = buffer[size_t(readIndexB)]; 73 | 74 | float fraction = delayInSamples - float(integerDelay); 75 | return sampleA + fraction * (sampleB - sampleA); 76 | } 77 | */ 78 | 79 | float DelayLine::read(float delayInSamples) const noexcept 80 | { 81 | jassert(delayInSamples >= 1.0f); 82 | jassert(delayInSamples <= bufferLength - 2.0f); 83 | 84 | int integerDelay = int(delayInSamples); 85 | 86 | int readIndexA = writeIndex - integerDelay + 1; 87 | int readIndexB = readIndexA - 1; 88 | int readIndexC = readIndexA - 2; 89 | int readIndexD = readIndexA - 3; 90 | 91 | if (readIndexD < 0) { 92 | readIndexD += bufferLength; 93 | if (readIndexC < 0) { 94 | readIndexC += bufferLength; 95 | if (readIndexB < 0) { 96 | readIndexB += bufferLength; 97 | if (readIndexA < 0) { 98 | readIndexA += bufferLength; 99 | } 100 | } 101 | } 102 | } 103 | 104 | float sampleA = buffer[size_t(readIndexA)]; 105 | float sampleB = buffer[size_t(readIndexB)]; 106 | float sampleC = buffer[size_t(readIndexC)]; 107 | float sampleD = buffer[size_t(readIndexD)]; 108 | 109 | float fraction = delayInSamples - float(integerDelay); 110 | float slope0 = (sampleC - sampleA) * 0.5f; 111 | float slope1 = (sampleD - sampleB) * 0.5f; 112 | float v = sampleB - sampleC; 113 | float w = slope0 + v; 114 | float a = w + v + slope1; 115 | float b = w + a; 116 | float stage1 = a * fraction - b; 117 | float stage2 = stage1 * fraction + slope0; 118 | return stage2 * fraction + sampleB; 119 | } 120 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/DelayLine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class DelayLine 6 | { 7 | public: 8 | void setMaximumDelayInSamples(int maxLengthInSamples); 9 | void reset() noexcept; 10 | 11 | void write(float input) noexcept; 12 | float read(float delayInSamples) const noexcept; 13 | 14 | int getBufferLength() const noexcept 15 | { 16 | return bufferLength; 17 | } 18 | 19 | private: 20 | std::unique_ptr buffer; 21 | int bufferLength = 0; 22 | int writeIndex = 0; // where the most recent value was written 23 | }; 24 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/LevelMeter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "LevelMeter.h" 3 | #include "LookAndFeel.h" 4 | 5 | LevelMeter::LevelMeter(Measurement& measurementL_, Measurement& measurementR_) 6 | : measurementL(measurementL_), measurementR(measurementR_), 7 | dbLevelL(clampdB), dbLevelR(clampdB) 8 | { 9 | setOpaque(true); 10 | startTimerHz(refreshRate); 11 | decay = 1.0f - std::exp(-1.0f / (float(refreshRate) * 0.2f)); 12 | } 13 | 14 | LevelMeter::~LevelMeter() 15 | { 16 | } 17 | 18 | void LevelMeter::paint (juce::Graphics& g) 19 | { 20 | const auto bounds = getLocalBounds(); 21 | 22 | g.fillAll(Colors::LevelMeter::background); 23 | 24 | drawLevel(g, dbLevelL, 0, 7); 25 | drawLevel(g, dbLevelR, 9, 7); 26 | 27 | g.setFont(Fonts::getFont(10.0f)); 28 | for (float db = maxdB; db >= mindB; db -= stepdB) { 29 | int y = positionForLevel(db); 30 | 31 | g.setColour(Colors::LevelMeter::tickLine); 32 | g.fillRect(0, y, 16, 1); 33 | 34 | g.setColour(Colors::LevelMeter::tickLabel); 35 | g.drawSingleLineText(juce::String(int(db)), bounds.getWidth(), y + 3, 36 | juce::Justification::right); 37 | } 38 | } 39 | 40 | void LevelMeter::resized() 41 | { 42 | maxPos = 4.0f; 43 | minPos = float(getHeight()) - 4.0f; 44 | } 45 | 46 | void LevelMeter::timerCallback() 47 | { 48 | updateLevel(measurementL.readAndReset(), levelL, dbLevelL); 49 | updateLevel(measurementR.readAndReset(), levelR, dbLevelR); 50 | 51 | repaint(); 52 | } 53 | 54 | void LevelMeter::drawLevel(juce::Graphics& g, float level, int x, int width) 55 | { 56 | int y = positionForLevel(level); 57 | if (level > 0.0f) { 58 | int y0 = positionForLevel(0.0f); 59 | g.setColour(Colors::LevelMeter::tooLoud); 60 | g.fillRect(x, y, width, y0 - y); 61 | g.setColour(Colors::LevelMeter::levelOK); 62 | g.fillRect(x, y0, width, getHeight() - y0); 63 | } else if (y < getHeight()) { 64 | g.setColour(Colors::LevelMeter::levelOK); 65 | g.fillRect(x, y, width, getHeight() - y); 66 | } 67 | } 68 | 69 | void LevelMeter::updateLevel(float newLevel, float& smoothedLevel, float& leveldB) const 70 | { 71 | if (newLevel > smoothedLevel) { 72 | smoothedLevel = newLevel; // instantaneous attack 73 | } else { 74 | smoothedLevel += (newLevel - smoothedLevel) * decay; 75 | } 76 | if (smoothedLevel > clampLevel) { 77 | leveldB = juce::Decibels::gainToDecibels(smoothedLevel); 78 | } else { 79 | leveldB = clampdB; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/LevelMeter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Measurement.h" 5 | 6 | class LevelMeter : public juce::Component, private juce::Timer 7 | { 8 | public: 9 | LevelMeter(Measurement& measurementL, Measurement& measurementR); 10 | ~LevelMeter() override; 11 | 12 | void paint (juce::Graphics&) override; 13 | void resized() override; 14 | 15 | private: 16 | void timerCallback() override; 17 | 18 | int positionForLevel(float dbLevel) const noexcept 19 | { 20 | return int(std::round(juce::jmap(dbLevel, maxdB, mindB, maxPos, minPos))); 21 | } 22 | 23 | void drawLevel(juce::Graphics& g, float level, int x, int width); 24 | void updateLevel(float newLevel, float& smoothedLevel, float& leveldB) const; 25 | 26 | Measurement& measurementL; 27 | Measurement& measurementR; 28 | 29 | static constexpr float maxdB = 6.0f; 30 | static constexpr float mindB = -60.0f; 31 | static constexpr float stepdB = 6.0f; 32 | 33 | float maxPos = 0.0f; 34 | float minPos = 0.0f; 35 | 36 | static constexpr float clampdB = -120.0f; 37 | static constexpr float clampLevel = 0.000001f; // -120 dB 38 | 39 | float dbLevelL; 40 | float dbLevelR; 41 | 42 | static constexpr int refreshRate = 60; 43 | 44 | float decay = 0.0f; 45 | float levelL = clampLevel; 46 | float levelR = clampLevel; 47 | 48 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LevelMeter) 49 | }; 50 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/Measurement.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Measurement 6 | { 7 | void reset() noexcept 8 | { 9 | value.store(0.0f); 10 | } 11 | 12 | void updateIfGreater(float newValue) noexcept 13 | { 14 | auto oldValue = value.load(); 15 | while (newValue > oldValue && !value.compare_exchange_weak(oldValue, newValue)); 16 | } 17 | 18 | float readAndReset() noexcept 19 | { 20 | return value.exchange(0.0f); 21 | } 22 | 23 | std::atomic value; 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | const juce::ParameterID delayTimeParamID { "delayTime", 1 }; 7 | const juce::ParameterID mixParamID { "mix", 1 }; 8 | const juce::ParameterID feedbackParamID { "feedback", 1 }; 9 | const juce::ParameterID stereoParamID { "stereo", 1 }; 10 | const juce::ParameterID lowCutParamID { "lowCut", 1 }; 11 | const juce::ParameterID highCutParamID { "highCut", 1 }; 12 | const juce::ParameterID tempoSyncParamID { "tempoSync", 1 }; 13 | const juce::ParameterID delayNoteParamID { "delayNote", 1 }; 14 | 15 | class Parameters 16 | { 17 | public: 18 | Parameters(juce::AudioProcessorValueTreeState& apvts); 19 | 20 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 21 | 22 | void prepareToPlay(double sampleRate) noexcept; 23 | void reset() noexcept; 24 | void update() noexcept; 25 | void smoothen() noexcept; 26 | 27 | float gain = 0.0f; 28 | float delayTime = 0.0f; 29 | float mix = 1.0f; 30 | float feedback = 0.0f; 31 | float panL = 0.0f; 32 | float panR = 1.0f; 33 | float lowCut = 20.0f; 34 | float highCut = 20000.0f; 35 | int delayNote = 0; 36 | bool tempoSync = false; 37 | 38 | static constexpr float minDelayTime = 5.0f; 39 | static constexpr float maxDelayTime = 5000.0f; 40 | 41 | juce::AudioParameterBool* tempoSyncParam; 42 | 43 | private: 44 | juce::AudioParameterFloat* gainParam; 45 | juce::LinearSmoothedValue gainSmoother; 46 | 47 | juce::AudioParameterFloat* delayTimeParam; 48 | 49 | float targetDelayTime = 0.0f; 50 | float coeff = 0.0f; // one-pole smoothing 51 | 52 | juce::AudioParameterFloat* mixParam; 53 | juce::LinearSmoothedValue mixSmoother; 54 | 55 | juce::AudioParameterFloat* feedbackParam; 56 | juce::LinearSmoothedValue feedbackSmoother; 57 | 58 | juce::AudioParameterFloat* stereoParam; 59 | juce::LinearSmoothedValue stereoSmoother; 60 | 61 | juce::AudioParameterFloat* lowCutParam; 62 | juce::LinearSmoothedValue lowCutSmoother; 63 | 64 | juce::AudioParameterFloat* highCutParam; 65 | juce::LinearSmoothedValue highCutSmoother; 66 | 67 | juce::AudioParameterChoice* delayNoteParam; 68 | }; 69 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | #include "Parameters.h" 14 | #include "RotaryKnob.h" 15 | #include "LookAndFeel.h" 16 | #include "LevelMeter.h" 17 | 18 | //============================================================================== 19 | /** 20 | */ 21 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor, 22 | private juce::AudioProcessorParameter::Listener 23 | { 24 | public: 25 | DelayAudioProcessorEditor (DelayAudioProcessor&); 26 | ~DelayAudioProcessorEditor() override; 27 | 28 | //============================================================================== 29 | void paint (juce::Graphics&) override; 30 | void resized() override; 31 | 32 | private: 33 | void parameterValueChanged(int, float) override; 34 | void parameterGestureChanged(int, bool) override { } 35 | 36 | void updateDelayKnobs(bool tempoSyncActive); 37 | 38 | // This reference is provided as a quick way for your editor to 39 | // access the processor object that created it. 40 | DelayAudioProcessor& audioProcessor; 41 | 42 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 43 | 44 | RotaryKnob gainKnob { "Gain", audioProcessor.apvts, gainParamID, true }; 45 | RotaryKnob mixKnob { "Mix", audioProcessor.apvts, mixParamID }; 46 | RotaryKnob delayTimeKnob { "Time", audioProcessor.apvts, delayTimeParamID }; 47 | RotaryKnob feedbackKnob { "Feedback", audioProcessor.apvts, feedbackParamID, true }; 48 | RotaryKnob stereoKnob { "Stereo", audioProcessor.apvts, stereoParamID, true }; 49 | RotaryKnob lowCutKnob { "Low Cut", audioProcessor.apvts, lowCutParamID }; 50 | RotaryKnob highCutKnob { "High Cut", audioProcessor.apvts, highCutParamID }; 51 | RotaryKnob delayNoteKnob { "Note", audioProcessor.apvts, delayNoteParamID }; 52 | 53 | juce::TextButton tempoSyncButton; 54 | 55 | juce::AudioProcessorValueTreeState::ButtonAttachment tempoSyncAttachment { 56 | audioProcessor.apvts, tempoSyncParamID.getParamID(), tempoSyncButton 57 | }; 58 | 59 | juce::GroupComponent delayGroup, feedbackGroup, outputGroup; 60 | 61 | MainLookAndFeel mainLF; 62 | 63 | LevelMeter meter; 64 | }; 65 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "Parameters.h" 13 | #include "Tempo.h" 14 | #include "DelayLine.h" 15 | #include "Measurement.h" 16 | 17 | //============================================================================== 18 | /** 19 | */ 20 | class DelayAudioProcessor : public juce::AudioProcessor 21 | { 22 | public: 23 | //============================================================================== 24 | DelayAudioProcessor(); 25 | ~DelayAudioProcessor() override; 26 | 27 | //============================================================================== 28 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 29 | void releaseResources() override; 30 | 31 | #ifndef JucePlugin_PreferredChannelConfigurations 32 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 33 | #endif 34 | 35 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 36 | 37 | //============================================================================== 38 | juce::AudioProcessorEditor* createEditor() override; 39 | bool hasEditor() const override; 40 | 41 | //============================================================================== 42 | const juce::String getName() const override; 43 | 44 | bool acceptsMidi() const override; 45 | bool producesMidi() const override; 46 | bool isMidiEffect() const override; 47 | double getTailLengthSeconds() const override; 48 | 49 | //============================================================================== 50 | int getNumPrograms() override; 51 | int getCurrentProgram() override; 52 | void setCurrentProgram (int index) override; 53 | const juce::String getProgramName (int index) override; 54 | void changeProgramName (int index, const juce::String& newName) override; 55 | 56 | //============================================================================== 57 | void getStateInformation (juce::MemoryBlock& destData) override; 58 | void setStateInformation (const void* data, int sizeInBytes) override; 59 | 60 | juce::AudioProcessorValueTreeState apvts { 61 | *this, nullptr, "Parameters", Parameters::createParameterLayout() 62 | }; 63 | 64 | Parameters params; 65 | 66 | Measurement levelL, levelR; 67 | 68 | private: 69 | DelayLine delayLineL, delayLineR; 70 | 71 | float feedbackL = 0.0f; 72 | float feedbackR = 0.0f; 73 | 74 | juce::dsp::StateVariableTPTFilter lowCutFilter; 75 | juce::dsp::StateVariableTPTFilter highCutFilter; 76 | 77 | float lastLowCut = -1.0f; 78 | float lastHighCut = -1.0f; 79 | 80 | Tempo tempo; 81 | 82 | //============================================================================== 83 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 84 | }; 85 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/ProtectYourEars.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Silences the buffer if bad or loud values are detected in the output buffer. 6 | // Use this during debugging to avoid blowing out your eardrums on headphones. 7 | inline void protectYourEars(juce::AudioBuffer& buffer) 8 | { 9 | bool firstWarning = true; 10 | for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { 11 | float* channelData = buffer.getWritePointer(channel); 12 | for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { 13 | float x = channelData[sample]; 14 | bool silence = false; 15 | if (std::isnan(x)) { 16 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 17 | silence = true; 18 | } else if (std::isinf(x)) { 19 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 20 | silence = true; 21 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 22 | DBG("!!! WARNING: sample out of range, silencing !!!"); 23 | silence = true; 24 | } else if (x < -1.0f || x > 1.0f) { 25 | if (firstWarning) { 26 | DBG("!!! WARNING: sample out of range: " << x << " !!!"); 27 | firstWarning = false; 28 | } 29 | } 30 | if (silence) { 31 | buffer.clear(); 32 | return; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/RotaryKnob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RotaryKnob.h" 3 | #include "LookAndFeel.h" 4 | 5 | RotaryKnob::RotaryKnob(const juce::String& text, 6 | juce::AudioProcessorValueTreeState& apvts, 7 | const juce::ParameterID& parameterID, 8 | bool drawFromMiddle) 9 | : attachment(apvts, parameterID.getParamID(), slider) 10 | { 11 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); 12 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 70, 16); 13 | slider.setBounds(0, 0, 70, 86); 14 | addAndMakeVisible(slider); 15 | 16 | label.setText(text, juce::NotificationType::dontSendNotification); 17 | label.setJustificationType(juce::Justification::horizontallyCentred); 18 | label.setBorderSize(juce::BorderSize{ 0, 0, 2, 0 }); 19 | label.attachToComponent(&slider, false); 20 | addAndMakeVisible(label); 21 | 22 | setSize(70, 110); 23 | 24 | setLookAndFeel(RotaryKnobLookAndFeel::get()); 25 | 26 | float pi = juce::MathConstants::pi; 27 | slider.setRotaryParameters(1.25f * pi, 2.75f * pi, true); 28 | 29 | slider.getProperties().set("drawFromMiddle", drawFromMiddle); 30 | } 31 | 32 | RotaryKnob::~RotaryKnob() 33 | { 34 | } 35 | 36 | void RotaryKnob::resized() 37 | { 38 | slider.setTopLeftPosition(0, 24); 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/RotaryKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RotaryKnob : public juce::Component 6 | { 7 | public: 8 | RotaryKnob(const juce::String& text, 9 | juce::AudioProcessorValueTreeState& apvts, 10 | const juce::ParameterID& parameterID, 11 | bool drawFromMiddle = false); 12 | 13 | ~RotaryKnob() override; 14 | 15 | void resized() override; 16 | 17 | juce::Slider slider; 18 | juce::Label label; 19 | 20 | juce::AudioProcessorValueTreeState::SliderAttachment attachment; 21 | 22 | private: 23 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RotaryKnob) 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/Tempo.cpp: -------------------------------------------------------------------------------- 1 | #include "Tempo.h" 2 | 3 | static std::array noteLengthMultipliers = 4 | { 5 | 0.125, // 0 = 1/32 6 | 0.5 / 3.0, // 1 = 1/16 triplet 7 | 0.1875, // 2 = 1/32 dotted 8 | 0.25, // 3 = 1/16 9 | 1.0 / 3.0, // 4 = 1/8 triplet 10 | 0.375, // 5 = 1/16 dotted 11 | 0.5, // 6 = 1/8 12 | 2.0 / 3.0, // 7 = 1/4 triplet 13 | 0.75, // 8 = 1/8 dotted 14 | 1.0, // 9 = 1/4 15 | 4.0 / 3.0, // 10 = 1/2 triplet 16 | 1.5, // 11 = 1/4 dotted 17 | 2.0, // 12 = 1/2 18 | 8.0 / 3.0, // 13 = 1/1 triplet 19 | 3.0, // 14 = 1/2 dotted 20 | 4.0, // 15 = 1/1 21 | }; 22 | 23 | void Tempo::reset() noexcept 24 | { 25 | bpm = 120.0; 26 | } 27 | 28 | void Tempo::update(const juce::AudioPlayHead* playhead) noexcept 29 | { 30 | reset(); 31 | 32 | if (playhead == nullptr) { return; } 33 | 34 | const auto opt = playhead->getPosition(); 35 | if (!opt.hasValue()) { return; } 36 | 37 | const auto& pos = *opt; 38 | 39 | if (pos.getBpm().hasValue()) { 40 | bpm = *pos.getBpm(); 41 | } 42 | } 43 | 44 | double Tempo::getMillisecondsForNoteLength(int index) const noexcept 45 | { 46 | return 60000.0 * noteLengthMultipliers[size_t(index)] / bpm; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 17/Source/Tempo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Tempo 6 | { 7 | public: 8 | void reset() noexcept; 9 | 10 | void update(const juce::AudioPlayHead* playhead) noexcept; 11 | 12 | double getMillisecondsForNoteLength(int index) const noexcept; 13 | 14 | double getTempo() const noexcept 15 | { 16 | return bpm; 17 | } 18 | 19 | private: 20 | double bpm = 120.0; 21 | }; 22 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/DSP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | Calculates panning using equal power curves formula. Panning must be [-1, 1]. 7 | */ 8 | inline void panningEqualPower(float panning, float& left, float& right) 9 | { 10 | float x = 0.7853981633974483f * (panning + 1.0f); 11 | left = std::cos(x); 12 | right = std::sin(x); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/DelayLine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "DelayLine.h" 3 | 4 | void DelayLine::setMaximumDelayInSamples(int maxLengthInSamples) 5 | { 6 | jassert(maxLengthInSamples > 0); 7 | 8 | int paddedLength = maxLengthInSamples + 2; 9 | if (bufferLength < paddedLength) { 10 | bufferLength = paddedLength; 11 | buffer.reset(new float[size_t(bufferLength)]); 12 | } 13 | } 14 | 15 | void DelayLine::reset() noexcept 16 | { 17 | writeIndex = bufferLength - 1; 18 | 19 | for (size_t i = 0; i < size_t(bufferLength); ++i) { 20 | buffer[i] = 0.0f; 21 | } 22 | } 23 | 24 | void DelayLine::write(float input) noexcept 25 | { 26 | jassert(bufferLength > 0); 27 | 28 | writeIndex += 1; 29 | if (writeIndex >= bufferLength) { 30 | writeIndex = 0; 31 | } 32 | 33 | buffer[size_t(writeIndex)] = input; 34 | } 35 | 36 | /* 37 | // Nearest neighbors 38 | float DelayLine::read(float delayInSamples) const noexcept 39 | { 40 | jassert(delayInSamples >= 0.0f); 41 | jassert(delayInSamples <= bufferLength - 1.0f); 42 | 43 | int readIndex = int(std::round(writeIndex - delayInSamples)); 44 | if (readIndex < 0) { 45 | readIndex += bufferLength; 46 | } 47 | 48 | return buffer[size_t(readIndex)]; 49 | } 50 | */ 51 | 52 | /* 53 | // Linear interpolation 54 | float DelayLine::read(float delayInSamples) const noexcept 55 | { 56 | jassert(delayInSamples >= 0.0f); 57 | jassert(delayInSamples <= bufferLength - 1.0f); 58 | 59 | int integerDelay = int(delayInSamples); 60 | 61 | int readIndexA = writeIndex - integerDelay; 62 | if (readIndexA < 0) { 63 | readIndexA += bufferLength; 64 | } 65 | 66 | int readIndexB = readIndexA - 1; 67 | if (readIndexB < 0) { 68 | readIndexB += bufferLength; 69 | } 70 | 71 | float sampleA = buffer[size_t(readIndexA)]; 72 | float sampleB = buffer[size_t(readIndexB)]; 73 | 74 | float fraction = delayInSamples - float(integerDelay); 75 | return sampleA + fraction * (sampleB - sampleA); 76 | } 77 | */ 78 | 79 | float DelayLine::read(float delayInSamples) const noexcept 80 | { 81 | jassert(delayInSamples >= 1.0f); 82 | jassert(delayInSamples <= bufferLength - 2.0f); 83 | 84 | int integerDelay = int(delayInSamples); 85 | 86 | int readIndexA = writeIndex - integerDelay + 1; 87 | int readIndexB = readIndexA - 1; 88 | int readIndexC = readIndexA - 2; 89 | int readIndexD = readIndexA - 3; 90 | 91 | if (readIndexD < 0) { 92 | readIndexD += bufferLength; 93 | if (readIndexC < 0) { 94 | readIndexC += bufferLength; 95 | if (readIndexB < 0) { 96 | readIndexB += bufferLength; 97 | if (readIndexA < 0) { 98 | readIndexA += bufferLength; 99 | } 100 | } 101 | } 102 | } 103 | 104 | float sampleA = buffer[size_t(readIndexA)]; 105 | float sampleB = buffer[size_t(readIndexB)]; 106 | float sampleC = buffer[size_t(readIndexC)]; 107 | float sampleD = buffer[size_t(readIndexD)]; 108 | 109 | float fraction = delayInSamples - float(integerDelay); 110 | float slope0 = (sampleC - sampleA) * 0.5f; 111 | float slope1 = (sampleD - sampleB) * 0.5f; 112 | float v = sampleB - sampleC; 113 | float w = slope0 + v; 114 | float a = w + v + slope1; 115 | float b = w + a; 116 | float stage1 = a * fraction - b; 117 | float stage2 = stage1 * fraction + slope0; 118 | return stage2 * fraction + sampleB; 119 | } 120 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/DelayLine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class DelayLine 6 | { 7 | public: 8 | void setMaximumDelayInSamples(int maxLengthInSamples); 9 | void reset() noexcept; 10 | 11 | void write(float input) noexcept; 12 | float read(float delayInSamples) const noexcept; 13 | 14 | int getBufferLength() const noexcept 15 | { 16 | return bufferLength; 17 | } 18 | 19 | private: 20 | std::unique_ptr buffer; 21 | int bufferLength = 0; 22 | int writeIndex = 0; // where the most recent value was written 23 | }; 24 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/LevelMeter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "LevelMeter.h" 3 | #include "LookAndFeel.h" 4 | 5 | LevelMeter::LevelMeter(Measurement& measurementL_, Measurement& measurementR_) 6 | : measurementL(measurementL_), measurementR(measurementR_), 7 | dbLevelL(clampdB), dbLevelR(clampdB) 8 | { 9 | setOpaque(true); 10 | startTimerHz(refreshRate); 11 | decay = 1.0f - std::exp(-1.0f / (float(refreshRate) * 0.2f)); 12 | } 13 | 14 | LevelMeter::~LevelMeter() 15 | { 16 | } 17 | 18 | void LevelMeter::paint (juce::Graphics& g) 19 | { 20 | const auto bounds = getLocalBounds(); 21 | 22 | g.fillAll(Colors::LevelMeter::background); 23 | 24 | drawLevel(g, dbLevelL, 0, 7); 25 | drawLevel(g, dbLevelR, 9, 7); 26 | 27 | g.setFont(Fonts::getFont(10.0f)); 28 | for (float db = maxdB; db >= mindB; db -= stepdB) { 29 | int y = positionForLevel(db); 30 | 31 | g.setColour(Colors::LevelMeter::tickLine); 32 | g.fillRect(0, y, 16, 1); 33 | 34 | g.setColour(Colors::LevelMeter::tickLabel); 35 | g.drawSingleLineText(juce::String(int(db)), bounds.getWidth(), y + 3, 36 | juce::Justification::right); 37 | } 38 | } 39 | 40 | void LevelMeter::resized() 41 | { 42 | maxPos = 4.0f; 43 | minPos = float(getHeight()) - 4.0f; 44 | } 45 | 46 | void LevelMeter::timerCallback() 47 | { 48 | updateLevel(measurementL.readAndReset(), levelL, dbLevelL); 49 | updateLevel(measurementR.readAndReset(), levelR, dbLevelR); 50 | 51 | repaint(); 52 | } 53 | 54 | void LevelMeter::drawLevel(juce::Graphics& g, float level, int x, int width) 55 | { 56 | int y = positionForLevel(level); 57 | if (level > 0.0f) { 58 | int y0 = positionForLevel(0.0f); 59 | g.setColour(Colors::LevelMeter::tooLoud); 60 | g.fillRect(x, y, width, y0 - y); 61 | g.setColour(Colors::LevelMeter::levelOK); 62 | g.fillRect(x, y0, width, getHeight() - y0); 63 | } else if (y < getHeight()) { 64 | g.setColour(Colors::LevelMeter::levelOK); 65 | g.fillRect(x, y, width, getHeight() - y); 66 | } 67 | } 68 | 69 | void LevelMeter::updateLevel(float newLevel, float& smoothedLevel, float& leveldB) const 70 | { 71 | if (newLevel > smoothedLevel) { 72 | smoothedLevel = newLevel; // instantaneous attack 73 | } else { 74 | smoothedLevel += (newLevel - smoothedLevel) * decay; 75 | } 76 | if (smoothedLevel > clampLevel) { 77 | leveldB = juce::Decibels::gainToDecibels(smoothedLevel); 78 | } else { 79 | leveldB = clampdB; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/LevelMeter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Measurement.h" 5 | 6 | class LevelMeter : public juce::Component, private juce::Timer 7 | { 8 | public: 9 | LevelMeter(Measurement& measurementL, Measurement& measurementR); 10 | ~LevelMeter() override; 11 | 12 | void paint (juce::Graphics&) override; 13 | void resized() override; 14 | 15 | private: 16 | void timerCallback() override; 17 | 18 | int positionForLevel(float dbLevel) const noexcept 19 | { 20 | return int(std::round(juce::jmap(dbLevel, maxdB, mindB, maxPos, minPos))); 21 | } 22 | 23 | void drawLevel(juce::Graphics& g, float level, int x, int width); 24 | void updateLevel(float newLevel, float& smoothedLevel, float& leveldB) const; 25 | 26 | Measurement& measurementL; 27 | Measurement& measurementR; 28 | 29 | static constexpr float maxdB = 6.0f; 30 | static constexpr float mindB = -60.0f; 31 | static constexpr float stepdB = 6.0f; 32 | 33 | float maxPos = 0.0f; 34 | float minPos = 0.0f; 35 | 36 | static constexpr float clampdB = -120.0f; 37 | static constexpr float clampLevel = 0.000001f; // -120 dB 38 | 39 | float dbLevelL; 40 | float dbLevelR; 41 | 42 | static constexpr int refreshRate = 60; 43 | 44 | float decay = 0.0f; 45 | float levelL = clampLevel; 46 | float levelR = clampLevel; 47 | 48 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LevelMeter) 49 | }; 50 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/Measurement.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Measurement 6 | { 7 | void reset() noexcept 8 | { 9 | value.store(0.0f); 10 | } 11 | 12 | void updateIfGreater(float newValue) noexcept 13 | { 14 | auto oldValue = value.load(); 15 | while (newValue > oldValue && !value.compare_exchange_weak(oldValue, newValue)); 16 | } 17 | 18 | float readAndReset() noexcept 19 | { 20 | return value.exchange(0.0f); 21 | } 22 | 23 | std::atomic value; 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | const juce::ParameterID delayTimeParamID { "delayTime", 1 }; 7 | const juce::ParameterID mixParamID { "mix", 1 }; 8 | const juce::ParameterID feedbackParamID { "feedback", 1 }; 9 | const juce::ParameterID stereoParamID { "stereo", 1 }; 10 | const juce::ParameterID lowCutParamID { "lowCut", 1 }; 11 | const juce::ParameterID highCutParamID { "highCut", 1 }; 12 | const juce::ParameterID tempoSyncParamID { "tempoSync", 1 }; 13 | const juce::ParameterID delayNoteParamID { "delayNote", 1 }; 14 | const juce::ParameterID bypassParamID { "bypass", 1 }; 15 | 16 | class Parameters 17 | { 18 | public: 19 | Parameters(juce::AudioProcessorValueTreeState& apvts); 20 | 21 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 22 | 23 | void prepareToPlay(double sampleRate) noexcept; 24 | void reset() noexcept; 25 | void update() noexcept; 26 | void smoothen() noexcept; 27 | 28 | float gain = 0.0f; 29 | float delayTime = 0.0f; 30 | float mix = 1.0f; 31 | float feedback = 0.0f; 32 | float panL = 0.0f; 33 | float panR = 1.0f; 34 | float lowCut = 20.0f; 35 | float highCut = 20000.0f; 36 | int delayNote = 0; 37 | bool tempoSync = false; 38 | bool bypassed = false; 39 | 40 | static constexpr float minDelayTime = 5.0f; 41 | static constexpr float maxDelayTime = 5000.0f; 42 | 43 | juce::AudioParameterBool* tempoSyncParam; 44 | juce::AudioParameterBool* bypassParam; 45 | 46 | private: 47 | juce::AudioParameterFloat* gainParam; 48 | juce::LinearSmoothedValue gainSmoother; 49 | 50 | juce::AudioParameterFloat* delayTimeParam; 51 | 52 | float targetDelayTime = 0.0f; 53 | float coeff = 0.0f; // one-pole smoothing 54 | 55 | juce::AudioParameterFloat* mixParam; 56 | juce::LinearSmoothedValue mixSmoother; 57 | 58 | juce::AudioParameterFloat* feedbackParam; 59 | juce::LinearSmoothedValue feedbackSmoother; 60 | 61 | juce::AudioParameterFloat* stereoParam; 62 | juce::LinearSmoothedValue stereoSmoother; 63 | 64 | juce::AudioParameterFloat* lowCutParam; 65 | juce::LinearSmoothedValue lowCutSmoother; 66 | 67 | juce::AudioParameterFloat* highCutParam; 68 | juce::LinearSmoothedValue highCutSmoother; 69 | 70 | juce::AudioParameterChoice* delayNoteParam; 71 | }; 72 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | #include "Parameters.h" 14 | #include "RotaryKnob.h" 15 | #include "LookAndFeel.h" 16 | #include "LevelMeter.h" 17 | 18 | //============================================================================== 19 | /** 20 | */ 21 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor, 22 | private juce::AudioProcessorParameter::Listener 23 | { 24 | public: 25 | DelayAudioProcessorEditor (DelayAudioProcessor&); 26 | ~DelayAudioProcessorEditor() override; 27 | 28 | //============================================================================== 29 | void paint (juce::Graphics&) override; 30 | void resized() override; 31 | 32 | private: 33 | void parameterValueChanged(int, float) override; 34 | void parameterGestureChanged(int, bool) override { } 35 | 36 | void updateDelayKnobs(bool tempoSyncActive); 37 | 38 | // This reference is provided as a quick way for your editor to 39 | // access the processor object that created it. 40 | DelayAudioProcessor& audioProcessor; 41 | 42 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 43 | 44 | RotaryKnob gainKnob { "Gain", audioProcessor.apvts, gainParamID, true }; 45 | RotaryKnob mixKnob { "Mix", audioProcessor.apvts, mixParamID }; 46 | RotaryKnob delayTimeKnob { "Time", audioProcessor.apvts, delayTimeParamID }; 47 | RotaryKnob feedbackKnob { "Feedback", audioProcessor.apvts, feedbackParamID, true }; 48 | RotaryKnob stereoKnob { "Stereo", audioProcessor.apvts, stereoParamID, true }; 49 | RotaryKnob lowCutKnob { "Low Cut", audioProcessor.apvts, lowCutParamID }; 50 | RotaryKnob highCutKnob { "High Cut", audioProcessor.apvts, highCutParamID }; 51 | RotaryKnob delayNoteKnob { "Note", audioProcessor.apvts, delayNoteParamID }; 52 | 53 | juce::TextButton tempoSyncButton; 54 | juce::ImageButton bypassButton; 55 | 56 | juce::AudioProcessorValueTreeState::ButtonAttachment tempoSyncAttachment { 57 | audioProcessor.apvts, tempoSyncParamID.getParamID(), tempoSyncButton 58 | }; 59 | juce::AudioProcessorValueTreeState::ButtonAttachment bypassAttachment { 60 | audioProcessor.apvts, bypassParamID.getParamID(), bypassButton 61 | }; 62 | 63 | juce::GroupComponent delayGroup, feedbackGroup, outputGroup; 64 | 65 | MainLookAndFeel mainLF; 66 | 67 | LevelMeter meter; 68 | }; 69 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/ProtectYourEars.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Silences the buffer if bad or loud values are detected in the output buffer. 6 | // Use this during debugging to avoid blowing out your eardrums on headphones. 7 | inline void protectYourEars(juce::AudioBuffer& buffer) 8 | { 9 | bool firstWarning = true; 10 | for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { 11 | float* channelData = buffer.getWritePointer(channel); 12 | for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { 13 | float x = channelData[sample]; 14 | bool silence = false; 15 | if (std::isnan(x)) { 16 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 17 | silence = true; 18 | } else if (std::isinf(x)) { 19 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 20 | silence = true; 21 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 22 | DBG("!!! WARNING: sample out of range, silencing !!!"); 23 | silence = true; 24 | } else if (x < -1.0f || x > 1.0f) { 25 | if (firstWarning) { 26 | DBG("!!! WARNING: sample out of range: " << x << " !!!"); 27 | firstWarning = false; 28 | } 29 | } 30 | if (silence) { 31 | buffer.clear(); 32 | return; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/RotaryKnob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RotaryKnob.h" 3 | #include "LookAndFeel.h" 4 | 5 | RotaryKnob::RotaryKnob(const juce::String& text, 6 | juce::AudioProcessorValueTreeState& apvts, 7 | const juce::ParameterID& parameterID, 8 | bool drawFromMiddle) 9 | : attachment(apvts, parameterID.getParamID(), slider) 10 | { 11 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); 12 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 70, 16); 13 | slider.setBounds(0, 0, 70, 86); 14 | addAndMakeVisible(slider); 15 | 16 | label.setText(text, juce::NotificationType::dontSendNotification); 17 | label.setJustificationType(juce::Justification::horizontallyCentred); 18 | label.setBorderSize(juce::BorderSize{ 0, 0, 2, 0 }); 19 | label.attachToComponent(&slider, false); 20 | addAndMakeVisible(label); 21 | 22 | setSize(70, 110); 23 | 24 | setLookAndFeel(RotaryKnobLookAndFeel::get()); 25 | 26 | float pi = juce::MathConstants::pi; 27 | slider.setRotaryParameters(1.25f * pi, 2.75f * pi, true); 28 | 29 | slider.getProperties().set("drawFromMiddle", drawFromMiddle); 30 | } 31 | 32 | RotaryKnob::~RotaryKnob() 33 | { 34 | } 35 | 36 | void RotaryKnob::resized() 37 | { 38 | slider.setTopLeftPosition(0, 24); 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/RotaryKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RotaryKnob : public juce::Component 6 | { 7 | public: 8 | RotaryKnob(const juce::String& text, 9 | juce::AudioProcessorValueTreeState& apvts, 10 | const juce::ParameterID& parameterID, 11 | bool drawFromMiddle = false); 12 | 13 | ~RotaryKnob() override; 14 | 15 | void resized() override; 16 | 17 | juce::Slider slider; 18 | juce::Label label; 19 | 20 | juce::AudioProcessorValueTreeState::SliderAttachment attachment; 21 | 22 | private: 23 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RotaryKnob) 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/Tempo.cpp: -------------------------------------------------------------------------------- 1 | #include "Tempo.h" 2 | 3 | static std::array noteLengthMultipliers = 4 | { 5 | 0.125, // 0 = 1/32 6 | 0.5 / 3.0, // 1 = 1/16 triplet 7 | 0.1875, // 2 = 1/32 dotted 8 | 0.25, // 3 = 1/16 9 | 1.0 / 3.0, // 4 = 1/8 triplet 10 | 0.375, // 5 = 1/16 dotted 11 | 0.5, // 6 = 1/8 12 | 2.0 / 3.0, // 7 = 1/4 triplet 13 | 0.75, // 8 = 1/8 dotted 14 | 1.0, // 9 = 1/4 15 | 4.0 / 3.0, // 10 = 1/2 triplet 16 | 1.5, // 11 = 1/4 dotted 17 | 2.0, // 12 = 1/2 18 | 8.0 / 3.0, // 13 = 1/1 triplet 19 | 3.0, // 14 = 1/2 dotted 20 | 4.0, // 15 = 1/1 21 | }; 22 | 23 | void Tempo::reset() noexcept 24 | { 25 | bpm = 120.0; 26 | } 27 | 28 | void Tempo::update(const juce::AudioPlayHead* playhead) noexcept 29 | { 30 | reset(); 31 | 32 | if (playhead == nullptr) { return; } 33 | 34 | const auto opt = playhead->getPosition(); 35 | if (!opt.hasValue()) { return; } 36 | 37 | const auto& pos = *opt; 38 | 39 | if (pos.getBpm().hasValue()) { 40 | bpm = *pos.getBpm(); 41 | } 42 | } 43 | 44 | double Tempo::getMillisecondsForNoteLength(int index) const noexcept 45 | { 46 | return 60000.0 * noteLengthMultipliers[size_t(index)] / bpm; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 18/Source/Tempo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Tempo 6 | { 7 | public: 8 | void reset() noexcept; 9 | 10 | void update(const juce::AudioPlayHead* playhead) noexcept; 11 | 12 | double getMillisecondsForNoteLength(int index) const noexcept; 13 | 14 | double getTempo() const noexcept 15 | { 16 | return bpm; 17 | } 18 | 19 | private: 20 | double bpm = 120.0; 21 | }; 22 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 6/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | //============================================================================== 13 | DelayAudioProcessorEditor::DelayAudioProcessorEditor (DelayAudioProcessor& p) 14 | : AudioProcessorEditor (&p), audioProcessor (p) 15 | { 16 | // Make sure that before the constructor has finished, you've set the 17 | // editor's size to whatever you need it to be. 18 | setSize (400, 300); 19 | } 20 | 21 | DelayAudioProcessorEditor::~DelayAudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void DelayAudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | g.fillAll (juce::Colours::blue); // 1 29 | g.setColour (juce::Colours::white); 30 | g.setFont (40.0f); // 2 31 | g.drawFittedText ("My First Plug-in!", // 3 32 | getLocalBounds(), juce::Justification::centred, 1); 33 | } 34 | 35 | void DelayAudioProcessorEditor::resized() 36 | { 37 | // This is generally where you'll want to lay out the positions of any 38 | // subcomponents in your editor.. 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 6/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | DelayAudioProcessorEditor (DelayAudioProcessor&); 21 | ~DelayAudioProcessorEditor() override; 22 | 23 | //============================================================================== 24 | void paint (juce::Graphics&) override; 25 | void resized() override; 26 | 27 | private: 28 | // This reference is provided as a quick way for your editor to 29 | // access the processor object that created it. 30 | DelayAudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 6/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | //============================================================================== 14 | /** 15 | */ 16 | class DelayAudioProcessor : public juce::AudioProcessor 17 | { 18 | public: 19 | //============================================================================== 20 | DelayAudioProcessor(); 21 | ~DelayAudioProcessor() override; 22 | 23 | //============================================================================== 24 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 25 | void releaseResources() override; 26 | 27 | #ifndef JucePlugin_PreferredChannelConfigurations 28 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 29 | #endif 30 | 31 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 32 | 33 | //============================================================================== 34 | juce::AudioProcessorEditor* createEditor() override; 35 | bool hasEditor() const override; 36 | 37 | //============================================================================== 38 | const juce::String getName() const override; 39 | 40 | bool acceptsMidi() const override; 41 | bool producesMidi() const override; 42 | bool isMidiEffect() const override; 43 | double getTailLengthSeconds() const override; 44 | 45 | //============================================================================== 46 | int getNumPrograms() override; 47 | int getCurrentProgram() override; 48 | void setCurrentProgram (int index) override; 49 | const juce::String getProgramName (int index) override; 50 | void changeProgramName (int index, const juce::String& newName) override; 51 | 52 | //============================================================================== 53 | void getStateInformation (juce::MemoryBlock& destData) override; 54 | void setStateInformation (const void* data, int sizeInBytes) override; 55 | 56 | private: 57 | //============================================================================== 58 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 59 | }; 60 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 7/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | //============================================================================== 13 | DelayAudioProcessorEditor::DelayAudioProcessorEditor (DelayAudioProcessor& p) 14 | : AudioProcessorEditor (&p), audioProcessor (p) 15 | { 16 | // Make sure that before the constructor has finished, you've set the 17 | // editor's size to whatever you need it to be. 18 | setSize (400, 300); 19 | } 20 | 21 | DelayAudioProcessorEditor::~DelayAudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void DelayAudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | g.fillAll (juce::Colours::blue); // 1 29 | g.setColour (juce::Colours::white); 30 | g.setFont (40.0f); // 2 31 | g.drawFittedText ("My First Plug-in!", // 3 32 | getLocalBounds(), juce::Justification::centred, 1); 33 | } 34 | 35 | void DelayAudioProcessorEditor::resized() 36 | { 37 | // This is generally where you'll want to lay out the positions of any 38 | // subcomponents in your editor.. 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 7/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | DelayAudioProcessorEditor (DelayAudioProcessor&); 21 | ~DelayAudioProcessorEditor() override; 22 | 23 | //============================================================================== 24 | void paint (juce::Graphics&) override; 25 | void resized() override; 26 | 27 | private: 28 | // This reference is provided as a quick way for your editor to 29 | // access the processor object that created it. 30 | DelayAudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 7/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | //============================================================================== 14 | /** 15 | */ 16 | class DelayAudioProcessor : public juce::AudioProcessor 17 | { 18 | public: 19 | //============================================================================== 20 | DelayAudioProcessor(); 21 | ~DelayAudioProcessor() override; 22 | 23 | //============================================================================== 24 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 25 | void releaseResources() override; 26 | 27 | #ifndef JucePlugin_PreferredChannelConfigurations 28 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 29 | #endif 30 | 31 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 32 | 33 | //============================================================================== 34 | juce::AudioProcessorEditor* createEditor() override; 35 | bool hasEditor() const override; 36 | 37 | //============================================================================== 38 | const juce::String getName() const override; 39 | 40 | bool acceptsMidi() const override; 41 | bool producesMidi() const override; 42 | bool isMidiEffect() const override; 43 | double getTailLengthSeconds() const override; 44 | 45 | //============================================================================== 46 | int getNumPrograms() override; 47 | int getCurrentProgram() override; 48 | void setCurrentProgram (int index) override; 49 | const juce::String getProgramName (int index) override; 50 | void changeProgramName (int index, const juce::String& newName) override; 51 | 52 | //============================================================================== 53 | void getStateInformation (juce::MemoryBlock& destData) override; 54 | void setStateInformation (const void* data, int sizeInBytes) override; 55 | 56 | private: 57 | //============================================================================== 58 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 59 | }; 60 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/Parameters.cpp: -------------------------------------------------------------------------------- 1 | #include "Parameters.h" 2 | 3 | template 4 | static void castParameter(juce::AudioProcessorValueTreeState& apvts, 5 | const juce::ParameterID& id, T& destination) 6 | { 7 | destination = dynamic_cast(apvts.getParameter(id.getParamID())); 8 | jassert(destination); // parameter does not exist or wrong type 9 | } 10 | 11 | Parameters::Parameters(juce::AudioProcessorValueTreeState& apvts) 12 | { 13 | castParameter(apvts, gainParamID, gainParam); 14 | } 15 | 16 | juce::AudioProcessorValueTreeState::ParameterLayout Parameters::createParameterLayout() 17 | { 18 | juce::AudioProcessorValueTreeState::ParameterLayout layout; 19 | 20 | layout.add(std::make_unique( 21 | gainParamID, 22 | "Output Gain", 23 | juce::NormalisableRange { -12.0f, 12.0f }, 24 | 0.0f)); 25 | 26 | return layout; 27 | } 28 | 29 | void Parameters::prepareToPlay(double sampleRate) noexcept 30 | { 31 | double duration = 0.02; 32 | gainSmoother.reset(sampleRate, duration); 33 | } 34 | 35 | void Parameters::reset() noexcept 36 | { 37 | gain = 0.0f; 38 | gainSmoother.setCurrentAndTargetValue(juce::Decibels::decibelsToGain(gainParam->get())); 39 | } 40 | 41 | void Parameters::update() noexcept 42 | { 43 | gainSmoother.setTargetValue(juce::Decibels::decibelsToGain(gainParam->get())); 44 | } 45 | 46 | void Parameters::smoothen() noexcept 47 | { 48 | gain = gainSmoother.getNextValue(); 49 | } 50 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | 7 | class Parameters 8 | { 9 | public: 10 | Parameters(juce::AudioProcessorValueTreeState& apvts); 11 | 12 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 13 | 14 | void prepareToPlay(double sampleRate) noexcept; 15 | void reset() noexcept; 16 | void update() noexcept; 17 | void smoothen() noexcept; 18 | 19 | float gain = 0.0f; 20 | 21 | private: 22 | juce::AudioParameterFloat* gainParam; 23 | juce::LinearSmoothedValue gainSmoother; 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | //============================================================================== 13 | DelayAudioProcessorEditor::DelayAudioProcessorEditor (DelayAudioProcessor& p) 14 | : AudioProcessorEditor (&p), audioProcessor (p) 15 | { 16 | // Make sure that before the constructor has finished, you've set the 17 | // editor's size to whatever you need it to be. 18 | setSize (400, 300); 19 | } 20 | 21 | DelayAudioProcessorEditor::~DelayAudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void DelayAudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | g.fillAll (juce::Colours::blue); // 1 29 | g.setColour (juce::Colours::white); 30 | g.setFont (40.0f); // 2 31 | g.drawFittedText ("My First Plug-in!", // 3 32 | getLocalBounds(), juce::Justification::centred, 1); 33 | } 34 | 35 | void DelayAudioProcessorEditor::resized() 36 | { 37 | // This is generally where you'll want to lay out the positions of any 38 | // subcomponents in your editor.. 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | DelayAudioProcessorEditor (DelayAudioProcessor&); 21 | ~DelayAudioProcessorEditor() override; 22 | 23 | //============================================================================== 24 | void paint (juce::Graphics&) override; 25 | void resized() override; 26 | 27 | private: 28 | // This reference is provided as a quick way for your editor to 29 | // access the processor object that created it. 30 | DelayAudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "Parameters.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class DelayAudioProcessor : public juce::AudioProcessor 18 | { 19 | public: 20 | //============================================================================== 21 | DelayAudioProcessor(); 22 | ~DelayAudioProcessor() override; 23 | 24 | //============================================================================== 25 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 26 | void releaseResources() override; 27 | 28 | #ifndef JucePlugin_PreferredChannelConfigurations 29 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 30 | #endif 31 | 32 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 33 | 34 | //============================================================================== 35 | juce::AudioProcessorEditor* createEditor() override; 36 | bool hasEditor() const override; 37 | 38 | //============================================================================== 39 | const juce::String getName() const override; 40 | 41 | bool acceptsMidi() const override; 42 | bool producesMidi() const override; 43 | bool isMidiEffect() const override; 44 | double getTailLengthSeconds() const override; 45 | 46 | //============================================================================== 47 | int getNumPrograms() override; 48 | int getCurrentProgram() override; 49 | void setCurrentProgram (int index) override; 50 | const juce::String getProgramName (int index) override; 51 | void changeProgramName (int index, const juce::String& newName) override; 52 | 53 | //============================================================================== 54 | void getStateInformation (juce::MemoryBlock& destData) override; 55 | void setStateInformation (const void* data, int sizeInBytes) override; 56 | 57 | private: 58 | juce::AudioProcessorValueTreeState apvts { 59 | *this, nullptr, "Parameters", Parameters::createParameterLayout() 60 | }; 61 | 62 | Parameters params; 63 | 64 | //============================================================================== 65 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 66 | }; 67 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/Parameters.cpp: -------------------------------------------------------------------------------- 1 | #include "Parameters.h" 2 | 3 | template 4 | static void castParameter(juce::AudioProcessorValueTreeState& apvts, 5 | const juce::ParameterID& id, T& destination) 6 | { 7 | destination = dynamic_cast(apvts.getParameter(id.getParamID())); 8 | jassert(destination); // parameter does not exist or wrong type 9 | } 10 | 11 | static juce::String stringFromMilliseconds(float value, int) 12 | { 13 | if (value < 10.0f) { 14 | return juce::String(value, 2) + " ms"; 15 | } else if (value < 100.0f) { 16 | return juce::String(value, 1) + " ms"; 17 | } else if (value < 1000.0f) { 18 | return juce::String(int(value)) + " ms"; 19 | } else { 20 | return juce::String(value * 0.001f, 2) + " s"; 21 | } 22 | } 23 | 24 | static juce::String stringFromDecibels(float value, int) 25 | { 26 | return juce::String(value, 1) + " dB"; 27 | } 28 | 29 | static juce::String stringFromPercent(float value, int) 30 | { 31 | return juce::String(int(value)) + " %"; 32 | } 33 | 34 | Parameters::Parameters(juce::AudioProcessorValueTreeState& apvts) 35 | { 36 | castParameter(apvts, gainParamID, gainParam); 37 | castParameter(apvts, delayTimeParamID, delayTimeParam); 38 | castParameter(apvts, mixParamID, mixParam); 39 | } 40 | 41 | juce::AudioProcessorValueTreeState::ParameterLayout Parameters::createParameterLayout() 42 | { 43 | juce::AudioProcessorValueTreeState::ParameterLayout layout; 44 | 45 | layout.add(std::make_unique( 46 | gainParamID, 47 | "Output Gain", 48 | juce::NormalisableRange { -12.0f, 12.0f }, 49 | 0.0f, 50 | juce::AudioParameterFloatAttributes().withStringFromValueFunction(stringFromDecibels) 51 | )); 52 | 53 | layout.add(std::make_unique( 54 | delayTimeParamID, 55 | "Delay Time", 56 | juce::NormalisableRange { minDelayTime, maxDelayTime, 0.001f, 0.25f }, 57 | 100.0f, 58 | juce::AudioParameterFloatAttributes().withStringFromValueFunction(stringFromMilliseconds) 59 | )); 60 | 61 | layout.add(std::make_unique( 62 | mixParamID, 63 | "Mix", 64 | juce::NormalisableRange(0.0f, 100.0f, 1.0f), 65 | 100.0f, 66 | juce::AudioParameterFloatAttributes().withStringFromValueFunction(stringFromPercent) 67 | )); 68 | 69 | return layout; 70 | } 71 | 72 | void Parameters::prepareToPlay(double sampleRate) noexcept 73 | { 74 | double duration = 0.02; 75 | gainSmoother.reset(sampleRate, duration); 76 | 77 | coeff = 1.0f - std::exp(-1.0f / (0.2f * float(sampleRate))); 78 | 79 | mixSmoother.reset(sampleRate, duration); 80 | } 81 | 82 | void Parameters::reset() noexcept 83 | { 84 | gain = 0.0f; 85 | gainSmoother.setCurrentAndTargetValue(juce::Decibels::decibelsToGain(gainParam->get())); 86 | 87 | delayTime = 0.0f; 88 | 89 | mix = 1.0f; 90 | mixSmoother.setCurrentAndTargetValue(mixParam->get() * 0.01f); 91 | } 92 | 93 | void Parameters::update() noexcept 94 | { 95 | gainSmoother.setTargetValue(juce::Decibels::decibelsToGain(gainParam->get())); 96 | 97 | targetDelayTime = delayTimeParam->get(); 98 | if (delayTime == 0.0f) { 99 | delayTime = targetDelayTime; 100 | } 101 | 102 | mixSmoother.setTargetValue(mixParam->get() * 0.01f); 103 | } 104 | 105 | void Parameters::smoothen() noexcept 106 | { 107 | gain = gainSmoother.getNextValue(); 108 | 109 | delayTime += (targetDelayTime - delayTime) * coeff; 110 | 111 | mix = mixSmoother.getNextValue(); 112 | } 113 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | const juce::ParameterID delayTimeParamID { "delayTime", 1 }; 7 | const juce::ParameterID mixParamID { "mix", 1 }; 8 | 9 | class Parameters 10 | { 11 | public: 12 | Parameters(juce::AudioProcessorValueTreeState& apvts); 13 | 14 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 15 | 16 | void prepareToPlay(double sampleRate) noexcept; 17 | void reset() noexcept; 18 | void update() noexcept; 19 | void smoothen() noexcept; 20 | 21 | float gain = 0.0f; 22 | float delayTime = 0.0f; 23 | float mix = 1.0f; 24 | 25 | static constexpr float minDelayTime = 5.0f; 26 | static constexpr float maxDelayTime = 5000.0f; 27 | 28 | private: 29 | juce::AudioParameterFloat* gainParam; 30 | juce::LinearSmoothedValue gainSmoother; 31 | 32 | juce::AudioParameterFloat* delayTimeParam; 33 | 34 | float targetDelayTime = 0.0f; 35 | float coeff = 0.0f; // one-pole smoothing 36 | 37 | juce::AudioParameterFloat* mixParam; 38 | juce::LinearSmoothedValue mixSmoother; 39 | }; 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | //============================================================================== 13 | DelayAudioProcessorEditor::DelayAudioProcessorEditor (DelayAudioProcessor& p) 14 | : AudioProcessorEditor (&p), audioProcessor (p) 15 | { 16 | // Make sure that before the constructor has finished, you've set the 17 | // editor's size to whatever you need it to be. 18 | setSize (400, 300); 19 | } 20 | 21 | DelayAudioProcessorEditor::~DelayAudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void DelayAudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | g.fillAll (juce::Colours::blue); // 1 29 | g.setColour (juce::Colours::white); 30 | g.setFont (40.0f); // 2 31 | g.drawFittedText ("My First Plug-in!", // 3 32 | getLocalBounds(), juce::Justification::centred, 1); 33 | } 34 | 35 | void DelayAudioProcessorEditor::resized() 36 | { 37 | // This is generally where you'll want to lay out the positions of any 38 | // subcomponents in your editor.. 39 | } 40 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | DelayAudioProcessorEditor (DelayAudioProcessor&); 21 | ~DelayAudioProcessorEditor() override; 22 | 23 | //============================================================================== 24 | void paint (juce::Graphics&) override; 25 | void resized() override; 26 | 27 | private: 28 | // This reference is provided as a quick way for your editor to 29 | // access the processor object that created it. 30 | DelayAudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "Parameters.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class DelayAudioProcessor : public juce::AudioProcessor 18 | { 19 | public: 20 | //============================================================================== 21 | DelayAudioProcessor(); 22 | ~DelayAudioProcessor() override; 23 | 24 | //============================================================================== 25 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 26 | void releaseResources() override; 27 | 28 | #ifndef JucePlugin_PreferredChannelConfigurations 29 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 30 | #endif 31 | 32 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 33 | 34 | //============================================================================== 35 | juce::AudioProcessorEditor* createEditor() override; 36 | bool hasEditor() const override; 37 | 38 | //============================================================================== 39 | const juce::String getName() const override; 40 | 41 | bool acceptsMidi() const override; 42 | bool producesMidi() const override; 43 | bool isMidiEffect() const override; 44 | double getTailLengthSeconds() const override; 45 | 46 | //============================================================================== 47 | int getNumPrograms() override; 48 | int getCurrentProgram() override; 49 | void setCurrentProgram (int index) override; 50 | const juce::String getProgramName (int index) override; 51 | void changeProgramName (int index, const juce::String& newName) override; 52 | 53 | //============================================================================== 54 | void getStateInformation (juce::MemoryBlock& destData) override; 55 | void setStateInformation (const void* data, int sizeInBytes) override; 56 | 57 | private: 58 | juce::AudioProcessorValueTreeState apvts { 59 | *this, nullptr, "Parameters", Parameters::createParameterLayout() 60 | }; 61 | 62 | Parameters params; 63 | 64 | juce::dsp::DelayLine delayLine; 65 | 66 | //============================================================================== 67 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 68 | }; 69 | -------------------------------------------------------------------------------- /Errata.markdown: -------------------------------------------------------------------------------- 1 | # Errata 2 | 3 | This is a list of known mistakes and bugs in the book *The Complete Beginner's Guide to Audio Plug-in Development* and/or the accompanying source code. 4 | 5 | ## EPUB issues 6 | 7 | Chapters 8 and 11 of the EPUB version of the book may show an error message in Apple Books and other e-readers. The text of these chapters is incomplete due to this error. 8 | 9 | On certain e-readers (such as the Kindle app), reading in dark mode used low-contrast colors. 10 | 11 | These issues have been fixed and an updated EPUB file is available. Please go to [your Lemon Squeezy order page](https://app.lemonsqueezy.com/my-orders) to download the latest version of the EPUB file. (The PDF version does not have these errors.) 12 | 13 | ## Chapter 6: AUAudioFilePlayer not listed in AudioPluginHost 14 | 15 | The section *Playing audio files (Mac)* says, "Right-click inside the AudioPluginHost window again and from the pop-up choose Apple > AUAudioFilePlayer." It's possible AUAudioFilePlayer is not listed as a plug-in that you can choose. 16 | 17 | To fix this, in AudioPluginHost go to the **Options** menu in the menu bar and select **Edit the List of Available Plug-Ins**. In the window that appears, click on **Options...** at the bottom and choose **Scan for new or updated AudioUnit plug-ins**. After a short while, AUAudioFilePlayer should appear in the list. 18 | 19 | Additionally, you can follow the instructions from the next section, *Playing audio files (Windows)*, and use the AudioFilePlayer project to play sounds. 20 | 21 | ## Chapter 8, page 170: Missing line of code 22 | 23 | In the section *Improving the class*, at the top of page 170 in the PDF and print versions of the book, the reader is asked to change the code in **Parameters.h**. In the provided source code the following line has gone missing: 24 | 25 | ```c++ 26 | const juce::ParameterID gainParamID { "gain", 1 }; 27 | ``` 28 | 29 | This line was first added on page 167 and it reappears on page 174. You're not supposed to delete this line. It is a mistake that this line is not included in the code on page 170. 30 | 31 | ## Xcode error: A build only device cannot be used to run this target 32 | 33 | You may get the following error message from Xcode: 34 | 35 | ![](Images/BuildOnlyDeviceError.png) 36 | 37 | This happens when the run destination at the top of the Xcode window is set to **Any Mac**. When Any Mac is selected, Xcode only lets you build the project but not run it. To fix this, click on Any Mac and change this setting to **My Mac**. 38 | 39 | ## Xcode error: Command PhaseScriptExecution failed with a nonzero exit code 40 | 41 | Xcode may give the error "Command PhaseScriptExecution failed with a nonzero exit code" with an additional message saying, "resource fork, Finder information, or similar detritus not allowed". 42 | 43 | This happens when the project folder is placed in the iCloud directory. Apparently the iCloud folder has extra files in it that break the building process. 44 | 45 | Try moving the folder to the Desktop. Then delete the **Builds** folder and export again from Projucer. Now it should build without issues. 46 | -------------------------------------------------------------------------------- /Finished Project/Source/DSP.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | Calculates panning using equal power curves formula. Panning must be [-1, 1]. 7 | */ 8 | inline void panningEqualPower(float panning, float& left, float& right) 9 | { 10 | float x = 0.7853981633974483f * (panning + 1.0f); 11 | left = std::cos(x); 12 | right = std::sin(x); 13 | } 14 | -------------------------------------------------------------------------------- /Finished Project/Source/DelayLine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "DelayLine.h" 3 | 4 | void DelayLine::setMaximumDelayInSamples(int maxLengthInSamples) 5 | { 6 | jassert(maxLengthInSamples > 0); 7 | 8 | int paddedLength = maxLengthInSamples + 2; 9 | if (bufferLength < paddedLength) { 10 | bufferLength = paddedLength; 11 | buffer.reset(new float[size_t(bufferLength)]); 12 | } 13 | } 14 | 15 | void DelayLine::reset() noexcept 16 | { 17 | writeIndex = bufferLength - 1; 18 | 19 | for (size_t i = 0; i < size_t(bufferLength); ++i) { 20 | buffer[i] = 0.0f; 21 | } 22 | } 23 | 24 | void DelayLine::write(float input) noexcept 25 | { 26 | jassert(bufferLength > 0); 27 | 28 | writeIndex += 1; 29 | if (writeIndex >= bufferLength) { 30 | writeIndex = 0; 31 | } 32 | 33 | buffer[size_t(writeIndex)] = input; 34 | } 35 | 36 | /* 37 | // Nearest neighbors 38 | float DelayLine::read(float delayInSamples) const noexcept 39 | { 40 | jassert(delayInSamples >= 0.0f); 41 | jassert(delayInSamples <= bufferLength - 1.0f); 42 | 43 | int readIndex = int(std::round(writeIndex - delayInSamples)); 44 | if (readIndex < 0) { 45 | readIndex += bufferLength; 46 | } 47 | 48 | return buffer[size_t(readIndex)]; 49 | } 50 | */ 51 | 52 | /* 53 | // Linear interpolation 54 | float DelayLine::read(float delayInSamples) const noexcept 55 | { 56 | jassert(delayInSamples >= 0.0f); 57 | jassert(delayInSamples <= bufferLength - 1.0f); 58 | 59 | int integerDelay = int(delayInSamples); 60 | 61 | int readIndexA = writeIndex - integerDelay; 62 | if (readIndexA < 0) { 63 | readIndexA += bufferLength; 64 | } 65 | 66 | int readIndexB = readIndexA - 1; 67 | if (readIndexB < 0) { 68 | readIndexB += bufferLength; 69 | } 70 | 71 | float sampleA = buffer[size_t(readIndexA)]; 72 | float sampleB = buffer[size_t(readIndexB)]; 73 | 74 | float fraction = delayInSamples - float(integerDelay); 75 | return sampleA + fraction * (sampleB - sampleA); 76 | } 77 | */ 78 | 79 | float DelayLine::read(float delayInSamples) const noexcept 80 | { 81 | jassert(delayInSamples >= 1.0f); 82 | jassert(delayInSamples <= bufferLength - 2.0f); 83 | 84 | int integerDelay = int(delayInSamples); 85 | 86 | int readIndexA = writeIndex - integerDelay + 1; 87 | int readIndexB = readIndexA - 1; 88 | int readIndexC = readIndexA - 2; 89 | int readIndexD = readIndexA - 3; 90 | 91 | if (readIndexD < 0) { 92 | readIndexD += bufferLength; 93 | if (readIndexC < 0) { 94 | readIndexC += bufferLength; 95 | if (readIndexB < 0) { 96 | readIndexB += bufferLength; 97 | if (readIndexA < 0) { 98 | readIndexA += bufferLength; 99 | } 100 | } 101 | } 102 | } 103 | 104 | float sampleA = buffer[size_t(readIndexA)]; 105 | float sampleB = buffer[size_t(readIndexB)]; 106 | float sampleC = buffer[size_t(readIndexC)]; 107 | float sampleD = buffer[size_t(readIndexD)]; 108 | 109 | float fraction = delayInSamples - float(integerDelay); 110 | float slope0 = (sampleC - sampleA) * 0.5f; 111 | float slope1 = (sampleD - sampleB) * 0.5f; 112 | float v = sampleB - sampleC; 113 | float w = slope0 + v; 114 | float a = w + v + slope1; 115 | float b = w + a; 116 | float stage1 = a * fraction - b; 117 | float stage2 = stage1 * fraction + slope0; 118 | return stage2 * fraction + sampleB; 119 | } 120 | -------------------------------------------------------------------------------- /Finished Project/Source/DelayLine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class DelayLine 6 | { 7 | public: 8 | void setMaximumDelayInSamples(int maxLengthInSamples); 9 | void reset() noexcept; 10 | 11 | void write(float input) noexcept; 12 | float read(float delayInSamples) const noexcept; 13 | 14 | int getBufferLength() const noexcept 15 | { 16 | return bufferLength; 17 | } 18 | 19 | private: 20 | std::unique_ptr buffer; 21 | int bufferLength = 0; 22 | int writeIndex = 0; // where the most recent value was written 23 | }; 24 | -------------------------------------------------------------------------------- /Finished Project/Source/LevelMeter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "LevelMeter.h" 3 | #include "LookAndFeel.h" 4 | 5 | LevelMeter::LevelMeter(Measurement& measurementL_, Measurement& measurementR_) 6 | : measurementL(measurementL_), measurementR(measurementR_), 7 | dbLevelL(clampdB), dbLevelR(clampdB) 8 | { 9 | setOpaque(true); 10 | startTimerHz(refreshRate); 11 | decay = 1.0f - std::exp(-1.0f / (float(refreshRate) * 0.2f)); 12 | } 13 | 14 | LevelMeter::~LevelMeter() 15 | { 16 | } 17 | 18 | void LevelMeter::paint (juce::Graphics& g) 19 | { 20 | const auto bounds = getLocalBounds(); 21 | 22 | g.fillAll(Colors::LevelMeter::background); 23 | 24 | drawLevel(g, dbLevelL, 0, 7); 25 | drawLevel(g, dbLevelR, 9, 7); 26 | 27 | g.setFont(Fonts::getFont(10.0f)); 28 | for (float db = maxdB; db >= mindB; db -= stepdB) { 29 | int y = positionForLevel(db); 30 | 31 | g.setColour(Colors::LevelMeter::tickLine); 32 | g.fillRect(0, y, 16, 1); 33 | 34 | g.setColour(Colors::LevelMeter::tickLabel); 35 | g.drawSingleLineText(juce::String(int(db)), bounds.getWidth(), y + 3, 36 | juce::Justification::right); 37 | } 38 | } 39 | 40 | void LevelMeter::resized() 41 | { 42 | maxPos = 4.0f; 43 | minPos = float(getHeight()) - 4.0f; 44 | } 45 | 46 | void LevelMeter::timerCallback() 47 | { 48 | updateLevel(measurementL.readAndReset(), levelL, dbLevelL); 49 | updateLevel(measurementR.readAndReset(), levelR, dbLevelR); 50 | 51 | repaint(); 52 | } 53 | 54 | void LevelMeter::drawLevel(juce::Graphics& g, float level, int x, int width) 55 | { 56 | int y = positionForLevel(level); 57 | if (level > 0.0f) { 58 | int y0 = positionForLevel(0.0f); 59 | g.setColour(Colors::LevelMeter::tooLoud); 60 | g.fillRect(x, y, width, y0 - y); 61 | g.setColour(Colors::LevelMeter::levelOK); 62 | g.fillRect(x, y0, width, getHeight() - y0); 63 | } else if (y < getHeight()) { 64 | g.setColour(Colors::LevelMeter::levelOK); 65 | g.fillRect(x, y, width, getHeight() - y); 66 | } 67 | } 68 | 69 | void LevelMeter::updateLevel(float newLevel, float& smoothedLevel, float& leveldB) const 70 | { 71 | if (newLevel > smoothedLevel) { 72 | smoothedLevel = newLevel; // instantaneous attack 73 | } else { 74 | smoothedLevel += (newLevel - smoothedLevel) * decay; 75 | } 76 | if (smoothedLevel > clampLevel) { 77 | leveldB = juce::Decibels::gainToDecibels(smoothedLevel); 78 | } else { 79 | leveldB = clampdB; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Finished Project/Source/LevelMeter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Measurement.h" 5 | 6 | class LevelMeter : public juce::Component, private juce::Timer 7 | { 8 | public: 9 | LevelMeter(Measurement& measurementL, Measurement& measurementR); 10 | ~LevelMeter() override; 11 | 12 | void paint (juce::Graphics&) override; 13 | void resized() override; 14 | 15 | private: 16 | void timerCallback() override; 17 | 18 | int positionForLevel(float dbLevel) const noexcept 19 | { 20 | return int(std::round(juce::jmap(dbLevel, maxdB, mindB, maxPos, minPos))); 21 | } 22 | 23 | void drawLevel(juce::Graphics& g, float level, int x, int width); 24 | void updateLevel(float newLevel, float& smoothedLevel, float& leveldB) const; 25 | 26 | Measurement& measurementL; 27 | Measurement& measurementR; 28 | 29 | static constexpr float maxdB = 6.0f; 30 | static constexpr float mindB = -60.0f; 31 | static constexpr float stepdB = 6.0f; 32 | 33 | float maxPos = 0.0f; 34 | float minPos = 0.0f; 35 | 36 | static constexpr float clampdB = -120.0f; 37 | static constexpr float clampLevel = 0.000001f; // -120 dB 38 | 39 | float dbLevelL; 40 | float dbLevelR; 41 | 42 | static constexpr int refreshRate = 60; 43 | 44 | float decay = 0.0f; 45 | float levelL = clampLevel; 46 | float levelR = clampLevel; 47 | 48 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LevelMeter) 49 | }; 50 | -------------------------------------------------------------------------------- /Finished Project/Source/Measurement.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct Measurement 6 | { 7 | void reset() noexcept 8 | { 9 | value.store(0.0f); 10 | } 11 | 12 | void updateIfGreater(float newValue) noexcept 13 | { 14 | auto oldValue = value.load(); 15 | while (newValue > oldValue && !value.compare_exchange_weak(oldValue, newValue)); 16 | } 17 | 18 | float readAndReset() noexcept 19 | { 20 | return value.exchange(0.0f); 21 | } 22 | 23 | std::atomic value; 24 | }; 25 | -------------------------------------------------------------------------------- /Finished Project/Source/Parameters.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const juce::ParameterID gainParamID { "gain", 1 }; 6 | const juce::ParameterID delayTimeParamID { "delayTime", 1 }; 7 | const juce::ParameterID mixParamID { "mix", 1 }; 8 | const juce::ParameterID feedbackParamID { "feedback", 1 }; 9 | const juce::ParameterID stereoParamID { "stereo", 1 }; 10 | const juce::ParameterID lowCutParamID { "lowCut", 1 }; 11 | const juce::ParameterID highCutParamID { "highCut", 1 }; 12 | const juce::ParameterID tempoSyncParamID { "tempoSync", 1 }; 13 | const juce::ParameterID delayNoteParamID { "delayNote", 1 }; 14 | const juce::ParameterID bypassParamID { "bypass", 1 }; 15 | 16 | class Parameters 17 | { 18 | public: 19 | Parameters(juce::AudioProcessorValueTreeState& apvts); 20 | 21 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 22 | 23 | void prepareToPlay(double sampleRate) noexcept; 24 | void reset() noexcept; 25 | void update() noexcept; 26 | void smoothen() noexcept; 27 | 28 | float gain = 0.0f; 29 | float delayTime = 0.0f; 30 | float mix = 1.0f; 31 | float feedback = 0.0f; 32 | float panL = 0.0f; 33 | float panR = 1.0f; 34 | float lowCut = 20.0f; 35 | float highCut = 20000.0f; 36 | int delayNote = 0; 37 | bool tempoSync = false; 38 | bool bypassed = false; 39 | 40 | static constexpr float minDelayTime = 5.0f; 41 | static constexpr float maxDelayTime = 5000.0f; 42 | 43 | juce::AudioParameterBool* tempoSyncParam; 44 | juce::AudioParameterBool* bypassParam; 45 | 46 | private: 47 | juce::AudioParameterFloat* gainParam; 48 | juce::LinearSmoothedValue gainSmoother; 49 | 50 | juce::AudioParameterFloat* delayTimeParam; 51 | 52 | float targetDelayTime = 0.0f; 53 | float coeff = 0.0f; // one-pole smoothing 54 | 55 | juce::AudioParameterFloat* mixParam; 56 | juce::LinearSmoothedValue mixSmoother; 57 | 58 | juce::AudioParameterFloat* feedbackParam; 59 | juce::LinearSmoothedValue feedbackSmoother; 60 | 61 | juce::AudioParameterFloat* stereoParam; 62 | juce::LinearSmoothedValue stereoSmoother; 63 | 64 | juce::AudioParameterFloat* lowCutParam; 65 | juce::LinearSmoothedValue lowCutSmoother; 66 | 67 | juce::AudioParameterFloat* highCutParam; 68 | juce::LinearSmoothedValue highCutSmoother; 69 | 70 | juce::AudioParameterChoice* delayNoteParam; 71 | }; 72 | -------------------------------------------------------------------------------- /Finished Project/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | #include "Parameters.h" 14 | #include "RotaryKnob.h" 15 | #include "LookAndFeel.h" 16 | #include "LevelMeter.h" 17 | 18 | //============================================================================== 19 | /** 20 | */ 21 | class DelayAudioProcessorEditor : public juce::AudioProcessorEditor, 22 | private juce::AudioProcessorParameter::Listener 23 | { 24 | public: 25 | DelayAudioProcessorEditor (DelayAudioProcessor&); 26 | ~DelayAudioProcessorEditor() override; 27 | 28 | //============================================================================== 29 | void paint (juce::Graphics&) override; 30 | void resized() override; 31 | 32 | private: 33 | void parameterValueChanged(int, float) override; 34 | void parameterGestureChanged(int, bool) override { } 35 | 36 | void updateDelayKnobs(bool tempoSyncActive); 37 | 38 | // This reference is provided as a quick way for your editor to 39 | // access the processor object that created it. 40 | DelayAudioProcessor& audioProcessor; 41 | 42 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessorEditor) 43 | 44 | RotaryKnob gainKnob { "Gain", audioProcessor.apvts, gainParamID, true }; 45 | RotaryKnob mixKnob { "Mix", audioProcessor.apvts, mixParamID }; 46 | RotaryKnob delayTimeKnob { "Time", audioProcessor.apvts, delayTimeParamID }; 47 | RotaryKnob feedbackKnob { "Feedback", audioProcessor.apvts, feedbackParamID, true }; 48 | RotaryKnob stereoKnob { "Stereo", audioProcessor.apvts, stereoParamID, true }; 49 | RotaryKnob lowCutKnob { "Low Cut", audioProcessor.apvts, lowCutParamID }; 50 | RotaryKnob highCutKnob { "High Cut", audioProcessor.apvts, highCutParamID }; 51 | RotaryKnob delayNoteKnob { "Note", audioProcessor.apvts, delayNoteParamID }; 52 | 53 | juce::TextButton tempoSyncButton; 54 | juce::ImageButton bypassButton; 55 | 56 | juce::AudioProcessorValueTreeState::ButtonAttachment tempoSyncAttachment { 57 | audioProcessor.apvts, tempoSyncParamID.getParamID(), tempoSyncButton 58 | }; 59 | juce::AudioProcessorValueTreeState::ButtonAttachment bypassAttachment { 60 | audioProcessor.apvts, bypassParamID.getParamID(), bypassButton 61 | }; 62 | 63 | juce::GroupComponent delayGroup, feedbackGroup, outputGroup; 64 | 65 | MainLookAndFeel mainLF; 66 | 67 | LevelMeter meter; 68 | }; 69 | -------------------------------------------------------------------------------- /Finished Project/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "Parameters.h" 13 | #include "Tempo.h" 14 | #include "DelayLine.h" 15 | #include "Measurement.h" 16 | 17 | //============================================================================== 18 | /** 19 | */ 20 | class DelayAudioProcessor : public juce::AudioProcessor 21 | { 22 | public: 23 | //============================================================================== 24 | DelayAudioProcessor(); 25 | ~DelayAudioProcessor() override; 26 | 27 | //============================================================================== 28 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 29 | void releaseResources() override; 30 | 31 | #ifndef JucePlugin_PreferredChannelConfigurations 32 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 33 | #endif 34 | 35 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 36 | 37 | //============================================================================== 38 | juce::AudioProcessorEditor* createEditor() override; 39 | bool hasEditor() const override; 40 | 41 | //============================================================================== 42 | const juce::String getName() const override; 43 | 44 | bool acceptsMidi() const override; 45 | bool producesMidi() const override; 46 | bool isMidiEffect() const override; 47 | double getTailLengthSeconds() const override; 48 | 49 | //============================================================================== 50 | int getNumPrograms() override; 51 | int getCurrentProgram() override; 52 | void setCurrentProgram (int index) override; 53 | const juce::String getProgramName (int index) override; 54 | void changeProgramName (int index, const juce::String& newName) override; 55 | 56 | //============================================================================== 57 | void getStateInformation (juce::MemoryBlock& destData) override; 58 | void setStateInformation (const void* data, int sizeInBytes) override; 59 | 60 | juce::AudioProcessorParameter* getBypassParameter() const override; 61 | 62 | juce::AudioProcessorValueTreeState apvts { 63 | *this, nullptr, "Parameters", Parameters::createParameterLayout() 64 | }; 65 | 66 | Parameters params; 67 | 68 | Measurement levelL, levelR; 69 | 70 | private: 71 | DelayLine delayLineL, delayLineR; 72 | 73 | float feedbackL = 0.0f; 74 | float feedbackR = 0.0f; 75 | 76 | juce::dsp::StateVariableTPTFilter lowCutFilter; 77 | juce::dsp::StateVariableTPTFilter highCutFilter; 78 | 79 | float lastLowCut = -1.0f; 80 | float lastHighCut = -1.0f; 81 | 82 | Tempo tempo; 83 | 84 | /* 85 | // For crossfading: 86 | float delayInSamples = 0.0f; 87 | float targetDelay = 0.0f; 88 | float xfade = 0.0f; 89 | float xfadeInc = 0.0f; 90 | */ 91 | 92 | // For ducking: 93 | float delayInSamples = 0.0f; 94 | float targetDelay = 0.0f; 95 | float fade = 0.0f; 96 | float fadeTarget = 0.0f; 97 | float coeff = 0.0f; 98 | float wait = 0.0f; 99 | float waitInc = 0.0f; 100 | 101 | //============================================================================== 102 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DelayAudioProcessor) 103 | }; 104 | -------------------------------------------------------------------------------- /Finished Project/Source/ProtectYourEars.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Silences the buffer if bad or loud values are detected in the output buffer. 6 | // Use this during debugging to avoid blowing out your eardrums on headphones. 7 | inline void protectYourEars(juce::AudioBuffer& buffer) 8 | { 9 | bool firstWarning = true; 10 | for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { 11 | float* channelData = buffer.getWritePointer(channel); 12 | for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { 13 | float x = channelData[sample]; 14 | bool silence = false; 15 | if (std::isnan(x)) { 16 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 17 | silence = true; 18 | } else if (std::isinf(x)) { 19 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 20 | silence = true; 21 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 22 | DBG("!!! WARNING: sample out of range, silencing !!!"); 23 | silence = true; 24 | } else if (x < -1.0f || x > 1.0f) { 25 | if (firstWarning) { 26 | DBG("!!! WARNING: sample out of range: " << x << " !!!"); 27 | firstWarning = false; 28 | } 29 | } 30 | if (silence) { 31 | buffer.clear(); 32 | return; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Finished Project/Source/RotaryKnob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RotaryKnob.h" 3 | #include "LookAndFeel.h" 4 | 5 | RotaryKnob::RotaryKnob(const juce::String& text, 6 | juce::AudioProcessorValueTreeState& apvts, 7 | const juce::ParameterID& parameterID, 8 | bool drawFromMiddle) 9 | : attachment(apvts, parameterID.getParamID(), slider) 10 | { 11 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); 12 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 70, 16); 13 | slider.setBounds(0, 0, 70, 86); 14 | addAndMakeVisible(slider); 15 | 16 | label.setText(text, juce::NotificationType::dontSendNotification); 17 | label.setJustificationType(juce::Justification::horizontallyCentred); 18 | label.setBorderSize(juce::BorderSize{ 0, 0, 2, 0 }); 19 | label.attachToComponent(&slider, false); 20 | addAndMakeVisible(label); 21 | 22 | setSize(70, 110); 23 | 24 | setLookAndFeel(RotaryKnobLookAndFeel::get()); 25 | 26 | float pi = juce::MathConstants::pi; 27 | slider.setRotaryParameters(1.25f * pi, 2.75f * pi, true); 28 | 29 | slider.getProperties().set("drawFromMiddle", drawFromMiddle); 30 | } 31 | 32 | RotaryKnob::~RotaryKnob() 33 | { 34 | } 35 | 36 | void RotaryKnob::resized() 37 | { 38 | slider.setTopLeftPosition(0, 24); 39 | } 40 | -------------------------------------------------------------------------------- /Finished Project/Source/RotaryKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RotaryKnob : public juce::Component 6 | { 7 | public: 8 | RotaryKnob(const juce::String& text, 9 | juce::AudioProcessorValueTreeState& apvts, 10 | const juce::ParameterID& parameterID, 11 | bool drawFromMiddle = false); 12 | 13 | ~RotaryKnob() override; 14 | 15 | void resized() override; 16 | 17 | juce::Slider slider; 18 | juce::Label label; 19 | 20 | juce::AudioProcessorValueTreeState::SliderAttachment attachment; 21 | 22 | private: 23 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RotaryKnob) 24 | }; 25 | -------------------------------------------------------------------------------- /Finished Project/Source/Tempo.cpp: -------------------------------------------------------------------------------- 1 | #include "Tempo.h" 2 | 3 | static std::array noteLengthMultipliers = 4 | { 5 | 0.125, // 0 = 1/32 6 | 0.5 / 3.0, // 1 = 1/16 triplet 7 | 0.1875, // 2 = 1/32 dotted 8 | 0.25, // 3 = 1/16 9 | 1.0 / 3.0, // 4 = 1/8 triplet 10 | 0.375, // 5 = 1/16 dotted 11 | 0.5, // 6 = 1/8 12 | 2.0 / 3.0, // 7 = 1/4 triplet 13 | 0.75, // 8 = 1/8 dotted 14 | 1.0, // 9 = 1/4 15 | 4.0 / 3.0, // 10 = 1/2 triplet 16 | 1.5, // 11 = 1/4 dotted 17 | 2.0, // 12 = 1/2 18 | 8.0 / 3.0, // 13 = 1/1 triplet 19 | 3.0, // 14 = 1/2 dotted 20 | 4.0, // 15 = 1/1 21 | }; 22 | 23 | void Tempo::reset() noexcept 24 | { 25 | bpm = 120.0; 26 | } 27 | 28 | void Tempo::update(const juce::AudioPlayHead* playhead) noexcept 29 | { 30 | reset(); 31 | 32 | if (playhead == nullptr) { return; } 33 | 34 | const auto opt = playhead->getPosition(); 35 | if (!opt.hasValue()) { return; } 36 | 37 | const auto& pos = *opt; 38 | 39 | if (pos.getBpm().hasValue()) { 40 | bpm = *pos.getBpm(); 41 | } 42 | } 43 | 44 | double Tempo::getMillisecondsForNoteLength(int index) const noexcept 45 | { 46 | return 60000.0 * noteLengthMultipliers[size_t(index)] / bpm; 47 | } 48 | -------------------------------------------------------------------------------- /Finished Project/Source/Tempo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Tempo 6 | { 7 | public: 8 | void reset() noexcept; 9 | 10 | void update(const juce::AudioPlayHead* playhead) noexcept; 11 | 12 | double getMillisecondsForNoteLength(int index) const noexcept; 13 | 14 | double getTempo() const noexcept 15 | { 16 | return bpm; 17 | } 18 | 19 | private: 20 | double bpm = 120.0; 21 | }; 22 | -------------------------------------------------------------------------------- /Images/BuildOnlyDeviceError.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BeginnerBookAudioProgramming/c80fb70084b94a3be9305b208248ed7cc64df619/Images/BuildOnlyDeviceError.png -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 The Audio Programmer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # The Complete Beginner's Guide to Audio Plug-in Development 2 | 3 | This is the source code that accompanies the book [The Complete Beginner's Guide to Audio Plug-in Development](https://theaudioprogrammer.com/learn/beginners-plugin-book) by Matthijs Hollemans, available for purchase from [The Audio Programmer](https://theaudioprogrammer.com/learn/beginners-plugin-book) as PDF and EPUB. 4 | 5 | ![The book cover](book-cover.jpg) 6 | 7 | In this 470-page book you'll learn how to make an audio plug-in, also known as a VST — even if you have never written a line of computer code in your life. 8 | 9 | This is a tutorial-style book that goes step-by-step and explains everything you need to know along the way. It is written for complete beginners. No prior programming knowledge is required and there is not a lot of math involved. 10 | 11 | The goal of this book is to make audio programming accessible to everyone — without requiring a computer science degree, audio engineering degree, or any other qualifications. All you need is a functioning brain, an eagerness to learn, and a little patience. The book does the rest by giving detailed explanations for everything, including as many screenshots and illustrations as possible. 12 | 13 | The plug-in that you'll learn to make is a **ping-pong delay** that can be used in all the popular DAWs such as Logic Pro, Ableton Live, REAPER, FL Studio, Cubase, Bitwig Studio, and others. 14 | 15 | ![The finished plug-in](ping-pong-delay.jpg) 16 | 17 | The plug-in is made using industry standard tools for audio programming: the JUCE framework and the C++ programming language. You'll learn all the fundamentals of writing audio plug-ins, in an easy-to-follow guide that is light on math and heavy on being practical. 18 | 19 | ## How to use this repo 20 | 21 | The **Resources** folder contains images and sound files that you will need to follow along with the book. 22 | 23 | For each chapter there is a folder with the finished source code from that chapter. This is useful for verifying that you didn't make any mistakes when typing in the code from the book. 24 | 25 | The **Finished Project** folder contains the completed plug-in in its entirety. 26 | 27 | ## Found a bug or have a question? 28 | 29 | First, please [check the errata](Errata.markdown) to see if the issue has already been covered. 30 | 31 | If not, refer to the [list of open issues](https://github.com/TheAudioProgrammer/getting-started-book/issues) and submit a new issue if you can't find your question in the list. 32 | 33 | Also make sure to join [The Audio Programmer community on Discord](https://www.theaudioprogrammer.com/discord). This is a great place to chat about all things related to audio programming. You can find me there as `matthijs`. 34 | 35 | ## Source code license 36 | 37 | The book is copyright 2024 The Audio Programmer, all rights reserved. 38 | 39 | The source code in this repo is licensed under the terms of the [MIT license](LICENSE.txt). 40 | 41 | JUCE is copyright © Raw Material Software. 42 | 43 | The Lato font is copyright (c) 2010-2014 Łukasz Dziedzic and is licensed under the [SIL Open Font License](OFL.txt). 44 | -------------------------------------------------------------------------------- /Resources/Beep-stereo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BeginnerBookAudioProgramming/c80fb70084b94a3be9305b208248ed7cc64df619/Resources/Beep-stereo.wav -------------------------------------------------------------------------------- /Resources/Beep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BeginnerBookAudioProgramming/c80fb70084b94a3be9305b208248ed7cc64df619/Resources/Beep.wav -------------------------------------------------------------------------------- /Resources/Bypass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BeginnerBookAudioProgramming/c80fb70084b94a3be9305b208248ed7cc64df619/Resources/Bypass.png -------------------------------------------------------------------------------- /Resources/Lato-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BeginnerBookAudioProgramming/c80fb70084b94a3be9305b208248ed7cc64df619/Resources/Lato-Medium.ttf -------------------------------------------------------------------------------- /Resources/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BeginnerBookAudioProgramming/c80fb70084b94a3be9305b208248ed7cc64df619/Resources/Logo.png -------------------------------------------------------------------------------- /Resources/Noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BeginnerBookAudioProgramming/c80fb70084b94a3be9305b208248ed7cc64df619/Resources/Noise.png -------------------------------------------------------------------------------- /Resources/ProtectYourEars.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Silences the buffer if bad or loud values are detected in the output buffer. 6 | // Use this during debugging to avoid blowing out your eardrums on headphones. 7 | inline void protectYourEars(juce::AudioBuffer& buffer) 8 | { 9 | bool firstWarning = true; 10 | for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { 11 | float* channelData = buffer.getWritePointer(channel); 12 | for (int sample = 0; sample < buffer.getNumSamples(); ++sample) { 13 | float x = channelData[sample]; 14 | bool silence = false; 15 | if (std::isnan(x)) { 16 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 17 | silence = true; 18 | } else if (std::isinf(x)) { 19 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 20 | silence = true; 21 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 22 | DBG("!!! WARNING: sample out of range, silencing !!!"); 23 | silence = true; 24 | } else if (x < -1.0f || x > 1.0f) { 25 | if (firstWarning) { 26 | DBG("!!! WARNING: sample out of range: " << x << " !!!"); 27 | firstWarning = false; 28 | } 29 | } 30 | if (silence) { 31 | buffer.clear(); 32 | return; 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Resources/Sine.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BeginnerBookAudioProgramming/c80fb70084b94a3be9305b208248ed7cc64df619/Resources/Sine.wav -------------------------------------------------------------------------------- /Resources/WhiteNoise.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BeginnerBookAudioProgramming/c80fb70084b94a3be9305b208248ed7cc64df619/Resources/WhiteNoise.wav -------------------------------------------------------------------------------- /book-cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BeginnerBookAudioProgramming/c80fb70084b94a3be9305b208248ed7cc64df619/book-cover.jpg -------------------------------------------------------------------------------- /ping-pong-delay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BeginnerBookAudioProgramming/c80fb70084b94a3be9305b208248ed7cc64df619/ping-pong-delay.jpg --------------------------------------------------------------------------------