├── .gitignore ├── Audio Demos ├── 5th Sweep Pad.mp3 ├── Basses.mp3 ├── Echo Pad.mp3 ├── Glide.mp3 ├── Organs+Brass.mp3 ├── PWM.mp3 ├── README.markdown ├── Reso Staccato.mp3 └── Three Pads.mp3 ├── Chapter Code ├── Chapter 10 │ ├── JX11.jucer │ └── Source │ │ ├── Envelope.h │ │ ├── NoiseGenerator.h │ │ ├── Oscillator.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── Preset.h │ │ ├── Synth.cpp │ │ ├── Synth.h │ │ ├── Utils.h │ │ └── Voice.h ├── Chapter 11 │ ├── JX11.jucer │ └── Source │ │ ├── Envelope.h │ │ ├── NoiseGenerator.h │ │ ├── Oscillator.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── Preset.h │ │ ├── Synth.cpp │ │ ├── Synth.h │ │ ├── Utils.h │ │ └── Voice.h ├── Chapter 12 │ ├── JX11.jucer │ └── Source │ │ ├── Envelope.h │ │ ├── Filter.h │ │ ├── NoiseGenerator.h │ │ ├── Oscillator.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── Preset.h │ │ ├── Synth.cpp │ │ ├── Synth.h │ │ ├── Utils.h │ │ └── Voice.h ├── Chapter 13 │ ├── JX11.jucer │ └── Source │ │ ├── Envelope.h │ │ ├── Filter.h │ │ ├── LookAndFeel.cpp │ │ ├── LookAndFeel.h │ │ ├── NoiseGenerator.h │ │ ├── Oscillator.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── Preset.h │ │ ├── RotaryKnob.cpp │ │ ├── RotaryKnob.h │ │ ├── Synth.cpp │ │ ├── Synth.h │ │ ├── Utils.h │ │ └── Voice.h ├── Chapter 2 │ ├── JX11.jucer │ └── Source │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ └── PluginProcessor.h ├── Chapter 3 │ ├── JX11.jucer │ └── Source │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ └── PluginProcessor.h ├── Chapter 4 │ ├── JX11.jucer │ └── Source │ │ ├── NoiseGenerator.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── Synth.cpp │ │ ├── Synth.h │ │ ├── Utils.h │ │ └── Voice.h ├── Chapter 5 │ ├── JX11.jucer │ └── Source │ │ ├── NoiseGenerator.h │ │ ├── Oscillator.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── Synth.cpp │ │ ├── Synth.h │ │ ├── Utils.h │ │ └── Voice.h ├── Chapter 6 │ ├── JX11.jucer │ └── Source │ │ ├── NoiseGenerator.h │ │ ├── Oscillator.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── Synth.cpp │ │ ├── Synth.h │ │ ├── Utils.h │ │ └── Voice.h ├── Chapter 7 │ ├── JX11.jucer │ └── Source │ │ ├── NoiseGenerator.h │ │ ├── Oscillator.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── Preset.h │ │ ├── Synth.cpp │ │ ├── Synth.h │ │ ├── Utils.h │ │ └── Voice.h ├── Chapter 8 │ ├── JX11.jucer │ └── Source │ │ ├── Envelope.h │ │ ├── NoiseGenerator.h │ │ ├── Oscillator.h │ │ ├── PluginEditor.cpp │ │ ├── PluginEditor.h │ │ ├── PluginProcessor.cpp │ │ ├── PluginProcessor.h │ │ ├── Preset.h │ │ ├── Synth.cpp │ │ ├── Synth.h │ │ ├── Utils.h │ │ └── Voice.h └── Chapter 9 │ ├── JX11.jucer │ └── Source │ ├── Envelope.h │ ├── NoiseGenerator.h │ ├── Oscillator.h │ ├── PluginEditor.cpp │ ├── PluginEditor.h │ ├── PluginProcessor.cpp │ ├── PluginProcessor.h │ ├── Preset.h │ ├── Synth.cpp │ ├── Synth.h │ ├── Utils.h │ └── Voice.h ├── Errata.markdown ├── Finished Project ├── JX11.jucer └── Source │ ├── Envelope.h │ ├── Filter.h │ ├── NoiseGenerator.h │ ├── Oscillator.h │ ├── PluginEditor.cpp │ ├── PluginEditor.h │ ├── PluginProcessor.cpp │ ├── PluginProcessor.h │ ├── Preset.h │ ├── Synth.cpp │ ├── Synth.h │ ├── Utils.h │ └── Voice.h ├── LICENSE ├── README.markdown ├── Resources ├── Lato-Medium.ttf └── OFL.txt ├── book-cover.jpg └── midi-note-chart.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | **/Builds 3 | **/JuceLibraryCode 4 | **/*.filtergraph 5 | 6 | -------------------------------------------------------------------------------- /Audio Demos/5th Sweep Pad.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BuildASynthPluginBook/08966f07e8974d7cca2e582e827c7b0be01dd8a3/Audio Demos/5th Sweep Pad.mp3 -------------------------------------------------------------------------------- /Audio Demos/Basses.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BuildASynthPluginBook/08966f07e8974d7cca2e582e827c7b0be01dd8a3/Audio Demos/Basses.mp3 -------------------------------------------------------------------------------- /Audio Demos/Echo Pad.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BuildASynthPluginBook/08966f07e8974d7cca2e582e827c7b0be01dd8a3/Audio Demos/Echo Pad.mp3 -------------------------------------------------------------------------------- /Audio Demos/Glide.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BuildASynthPluginBook/08966f07e8974d7cca2e582e827c7b0be01dd8a3/Audio Demos/Glide.mp3 -------------------------------------------------------------------------------- /Audio Demos/Organs+Brass.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BuildASynthPluginBook/08966f07e8974d7cca2e582e827c7b0be01dd8a3/Audio Demos/Organs+Brass.mp3 -------------------------------------------------------------------------------- /Audio Demos/PWM.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BuildASynthPluginBook/08966f07e8974d7cca2e582e827c7b0be01dd8a3/Audio Demos/PWM.mp3 -------------------------------------------------------------------------------- /Audio Demos/README.markdown: -------------------------------------------------------------------------------- 1 | # JX11 Synth Audio Demos 2 | 3 | These are recordings made with the JX11 synth from the book *Creating Synthesizer Plug-Ins with C++ and JUCE*, to give you an idea of what the synthesizer sounds like. 4 | 5 | Some of these recordings were made using the built-in presets, others using modified settings. No additional effects were used, these sounds come straight out of the synth. In some recordings automation was used to modify the parameters of the synth while the music was playing. 6 | 7 | **5th Sweep Pad**: Demo of the `5th Sweep Pad` preset. You can clearly hear the filter envelope here. 8 | 9 | **Basses**: Various bass presets, with a bit of pitch bend thrown in. 10 | 11 | **Echo Pad**: Playing some chords with the `Echo Pad [SA]` preset and the sustain pedal held down. 12 | 13 | **Glide**: Demonstrates glide or portamento between notes. 14 | 15 | **Organs+Brass**: A sampling of the included organ and brass presets. 16 | 17 | **PWM**: Plays different PWM and vibrato settings for the square wave. The last repetition is a sawtooth wave. 18 | 19 | **Reso Staccato**: Subtle sweep of filter cutoff and resonance. 20 | 21 | **Three Pads**: This has JX11 playing the same notes but on three tracks, with the presets `Synth Strings`, `Space Chimes [SA]`, and `Very Soft Pad`. 22 | 23 | Have a cool recording of your own that you made with this synth? I'd be happy to add your audio file here, or add a link to your SoundCloud etc. Simply open a [pull request](https://github.com/TheAudioProgrammer/synth-plugin-book/pulls) and include your recording. Thanks! 24 | -------------------------------------------------------------------------------- /Audio Demos/Reso Staccato.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BuildASynthPluginBook/08966f07e8974d7cca2e582e827c7b0be01dd8a3/Audio Demos/Reso Staccato.mp3 -------------------------------------------------------------------------------- /Audio Demos/Three Pads.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BuildASynthPluginBook/08966f07e8974d7cca2e582e827c7b0be01dd8a3/Audio Demos/Three Pads.mp3 -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/Envelope.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const float SILENCE = 0.0001f; 4 | 5 | class Envelope 6 | { 7 | public: 8 | void reset() 9 | { 10 | level = 0.0f; 11 | target = 0.0f; 12 | multiplier = 0.0f; 13 | } 14 | 15 | float nextValue() 16 | { 17 | level = multiplier * (level - target) + target; 18 | 19 | if (level + target > 3.0f) { 20 | multiplier = decayMultiplier; 21 | target = sustainLevel; 22 | } 23 | 24 | return level; 25 | } 26 | 27 | inline bool isActive() const 28 | { 29 | return level > SILENCE; 30 | } 31 | 32 | inline bool isInAttack() const 33 | { 34 | return target >= 2.0f; 35 | } 36 | 37 | void attack() 38 | { 39 | level += SILENCE + SILENCE; 40 | target = 2.0f; 41 | multiplier = attackMultiplier; 42 | } 43 | 44 | void release() 45 | { 46 | target = 0.0f; 47 | multiplier = releaseMultiplier; 48 | } 49 | 50 | float attackMultiplier; 51 | float decayMultiplier; 52 | float sustainLevel; 53 | float releaseMultiplier; 54 | 55 | float level; 56 | 57 | private: 58 | float target; 59 | float multiplier; 60 | }; 61 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/NoiseGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class NoiseGenerator 4 | { 5 | public: 6 | void reset() 7 | { 8 | noiseSeed = 22222; 9 | } 10 | 11 | float nextValue() 12 | { 13 | // Generate the next integer pseudorandom number. 14 | noiseSeed = noiseSeed * 196314165 + 907633515; 15 | 16 | // Convert to a signed value. 17 | int temp = int(noiseSeed >> 7) - 16777216; 18 | 19 | // Convert to a floating-point number between -1.0 and 1.0. 20 | return float(temp) / 16777216.0f; 21 | } 22 | 23 | private: 24 | unsigned int noiseSeed; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/Oscillator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const float PI_OVER_4 = 0.7853981633974483f; 6 | const float PI = 3.1415926535897932f; 7 | const float TWO_PI = 6.2831853071795864f; 8 | 9 | class Oscillator 10 | { 11 | public: 12 | float period = 0.0f; 13 | float amplitude = 1.0f; 14 | 15 | void reset() 16 | { 17 | inc = 0.0f; 18 | phase = 0.0f; 19 | sin0 = 0.0f; 20 | sin1 = 0.0f; 21 | dsin = 0.0f; 22 | dc = 0.0f; 23 | } 24 | 25 | float nextSample() 26 | { 27 | float output = 0.0f; 28 | 29 | phase += inc; 30 | if (phase <= PI_OVER_4) { 31 | float halfPeriod = period / 2.0f; 32 | phaseMax = std::floor(0.5f + halfPeriod) - 0.5f; 33 | 34 | dc = 0.5f * amplitude / phaseMax; 35 | phaseMax *= PI; 36 | 37 | inc = phaseMax / halfPeriod; 38 | phase = -phase; 39 | 40 | sin0 = amplitude * std::sin(phase); 41 | sin1 = amplitude * std::sin(phase - inc); 42 | dsin = 2.0f * std::cos(inc); 43 | 44 | if (phase*phase > 1e-9) { 45 | output = sin0 / phase; 46 | } else { 47 | output = amplitude; 48 | } 49 | } else { 50 | if (phase > phaseMax) { 51 | phase = phaseMax + phaseMax - phase; 52 | inc = -inc; 53 | } 54 | 55 | float sinp = dsin * sin0 - sin1; 56 | sin1 = sin0; 57 | sin0 = sinp; 58 | 59 | output = sinp / phase; 60 | } 61 | 62 | return output - dc; 63 | } 64 | 65 | private: 66 | float phase; 67 | float phaseMax; 68 | float inc; 69 | 70 | float sin0; 71 | float sin1; 72 | float dsin; 73 | 74 | float dc; 75 | }; 76 | -------------------------------------------------------------------------------- /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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /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 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/Preset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const int NUM_PARAMS = 26; 6 | 7 | struct Preset 8 | { 9 | Preset(const char* name, 10 | float p0, float p1, float p2, float p3, 11 | float p4, float p5, float p6, float p7, 12 | float p8, float p9, float p10, float p11, 13 | float p12, float p13, float p14, float p15, 14 | float p16, float p17, float p18, float p19, 15 | float p20, float p21, float p22, float p23, 16 | float p24, float p25) 17 | { 18 | strcpy(this->name, name); 19 | param[0] = p0; // Osc Mix 20 | param[1] = p1; // Osc Tune 21 | param[2] = p2; // Osc Fine 22 | param[3] = p3; // Glide Mode 23 | param[4] = p4; // Glide Rate 24 | param[5] = p5; // Glide Bend 25 | param[6] = p6; // Filter Freq 26 | param[7] = p7; // Filter Reso 27 | param[8] = p8; // Filter Env 28 | param[9] = p9; // Filter LFO 29 | param[10] = p10; // Velocity 30 | param[11] = p11; // Filter Attack 31 | param[12] = p12; // Filter Decay 32 | param[13] = p13; // Filter Sustain 33 | param[14] = p14; // Filter Release 34 | param[15] = p15; // Env Attack 35 | param[16] = p16; // Env Decay 36 | param[17] = p17; // Env Sustain 37 | param[18] = p18; // Env Release 38 | param[19] = p19; // LFO Rate 39 | param[20] = p20; // Vibrato 40 | param[21] = p21; // Noise 41 | param[22] = p22; // Octave 42 | param[23] = p23; // Tuning 43 | param[24] = p24; // Output Level 44 | param[25] = p25; // Polyphony 45 | } 46 | 47 | char name[40]; 48 | float param[NUM_PARAMS]; 49 | }; 50 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/Synth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Voice.h" 5 | #include "NoiseGenerator.h" 6 | 7 | class Synth 8 | { 9 | public: 10 | Synth(); 11 | 12 | void allocateResources(double sampleRate, int samplesPerBlock); 13 | void deallocateResources(); 14 | void reset(); 15 | void render(float** outputBuffers, int sampleCount); 16 | void midiMessage(uint8_t data0, uint8_t data1, uint8_t data2); 17 | 18 | float noiseMix; 19 | 20 | float envAttack, envDecay, envSustain, envRelease; 21 | 22 | float oscMix; 23 | float detune; 24 | float tune; 25 | 26 | static constexpr int MAX_VOICES = 8; 27 | int numVoices; 28 | 29 | float volumeTrim; 30 | 31 | juce::LinearSmoothedValue outputLevelSmoother; 32 | 33 | private: 34 | void controlChange(uint8_t data1, uint8_t data2); 35 | 36 | void noteOn(int note, int velocity); 37 | void noteOff(int note); 38 | 39 | void startVoice(int v, int note, int velocity); 40 | void restartMonoVoice(int note, int velocity); 41 | 42 | float calcPeriod(int v, int note) const; 43 | int findFreeVoice() const; 44 | 45 | void shiftQueuedNotes(); 46 | int nextQueuedNote(); 47 | 48 | float sampleRate; 49 | std::array voices; 50 | NoiseGenerator noiseGen; 51 | 52 | float pitchBend; 53 | bool sustainPedalPressed; 54 | }; 55 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | inline void protectYourEars(float* buffer, int sampleCount) 4 | { 5 | if (buffer == nullptr) { return; } 6 | bool firstWarning = true; 7 | for (int i = 0; i < sampleCount; ++i) { 8 | float x = buffer[i]; 9 | bool silence = false; 10 | if (std::isnan(x)) { 11 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 12 | silence = true; 13 | } else if (std::isinf(x)) { 14 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 15 | silence = true; 16 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 17 | DBG("!!! WARNING: sample out of range, silencing !!!"); 18 | silence = true; 19 | } else if (x < -1.0f) { 20 | if (firstWarning) { 21 | DBG("!!! WARNING: sample out of range, clamping !!!"); 22 | firstWarning = false; 23 | } 24 | buffer[i] = -1.0f; 25 | } else if (x > 1.0f) { 26 | if (firstWarning) { 27 | DBG("!!! WARNING: sample out of range, clamping !!!"); 28 | firstWarning = false; 29 | } 30 | buffer[i] = 1.0f; 31 | } 32 | if (silence) { 33 | memset(buffer, 0, sampleCount * sizeof(float)); 34 | return; 35 | } 36 | } 37 | } 38 | 39 | template 40 | inline static void castParameter(juce::AudioProcessorValueTreeState& apvts, const juce::ParameterID& id, T& destination) 41 | { 42 | destination = dynamic_cast(apvts.getParameter(id.getParamID())); 43 | jassert(destination); // parameter does not exist or wrong type 44 | } 45 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 10/Source/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Oscillator.h" 4 | #include "Envelope.h" 5 | 6 | struct Voice 7 | { 8 | int note; 9 | float saw; 10 | float period; 11 | 12 | Oscillator osc1; 13 | Oscillator osc2; 14 | Envelope env; 15 | 16 | float panLeft, panRight; 17 | 18 | void reset() 19 | { 20 | note = 0; 21 | saw = 0.0f; 22 | 23 | osc1.reset(); 24 | osc2.reset(); 25 | env.reset(); 26 | 27 | panLeft = 0.707f; 28 | panRight = 0.707f; 29 | } 30 | 31 | float render(float input) 32 | { 33 | float sample1 = osc1.nextSample(); 34 | float sample2 = osc2.nextSample(); 35 | saw = saw * 0.997f + sample1 - sample2; 36 | 37 | float output = saw + input; 38 | float envelope = env.nextValue(); 39 | return output * envelope; 40 | } 41 | 42 | void updatePanning() 43 | { 44 | float panning = std::clamp((note - 60.0f) / 24.0f, -1.0f, 1.0f); 45 | panLeft = std::sin(PI_OVER_4 * (1.0f - panning)); 46 | panRight = std::sin(PI_OVER_4 * (1.0f + panning)); 47 | } 48 | 49 | void release() 50 | { 51 | env.release(); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/Envelope.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const float SILENCE = 0.0001f; 4 | 5 | class Envelope 6 | { 7 | public: 8 | void reset() 9 | { 10 | level = 0.0f; 11 | target = 0.0f; 12 | multiplier = 0.0f; 13 | } 14 | 15 | float nextValue() 16 | { 17 | level = multiplier * (level - target) + target; 18 | 19 | if (level + target > 3.0f) { 20 | multiplier = decayMultiplier; 21 | target = sustainLevel; 22 | } 23 | 24 | return level; 25 | } 26 | 27 | inline bool isActive() const 28 | { 29 | return level > SILENCE; 30 | } 31 | 32 | inline bool isInAttack() const 33 | { 34 | return target >= 2.0f; 35 | } 36 | 37 | void attack() 38 | { 39 | level += SILENCE + SILENCE; 40 | target = 2.0f; 41 | multiplier = attackMultiplier; 42 | } 43 | 44 | void release() 45 | { 46 | target = 0.0f; 47 | multiplier = releaseMultiplier; 48 | } 49 | 50 | float attackMultiplier; 51 | float decayMultiplier; 52 | float sustainLevel; 53 | float releaseMultiplier; 54 | 55 | float level; 56 | 57 | private: 58 | float target; 59 | float multiplier; 60 | }; 61 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/NoiseGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class NoiseGenerator 4 | { 5 | public: 6 | void reset() 7 | { 8 | noiseSeed = 22222; 9 | } 10 | 11 | float nextValue() 12 | { 13 | // Generate the next integer pseudorandom number. 14 | noiseSeed = noiseSeed * 196314165 + 907633515; 15 | 16 | // Convert to a signed value. 17 | int temp = int(noiseSeed >> 7) - 16777216; 18 | 19 | // Convert to a floating-point number between -1.0 and 1.0. 20 | return float(temp) / 16777216.0f; 21 | } 22 | 23 | private: 24 | unsigned int noiseSeed; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/Oscillator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const float PI_OVER_4 = 0.7853981633974483f; 6 | const float PI = 3.1415926535897932f; 7 | const float TWO_PI = 6.2831853071795864f; 8 | 9 | class Oscillator 10 | { 11 | public: 12 | float period = 0.0f; 13 | float modulation = 1.0f; 14 | float amplitude = 1.0f; 15 | 16 | void reset() 17 | { 18 | inc = 0.0f; 19 | phase = 0.0f; 20 | sin0 = 0.0f; 21 | sin1 = 0.0f; 22 | dsin = 0.0f; 23 | dc = 0.0f; 24 | } 25 | 26 | float nextSample() 27 | { 28 | float output = 0.0f; 29 | 30 | phase += inc; 31 | if (phase <= PI_OVER_4) { 32 | float halfPeriod = (period / 2.0f) * modulation; 33 | phaseMax = std::floor(0.5f + halfPeriod) - 0.5f; 34 | 35 | dc = 0.5f * amplitude / phaseMax; 36 | phaseMax *= PI; 37 | 38 | inc = phaseMax / halfPeriod; 39 | phase = -phase; 40 | 41 | sin0 = amplitude * std::sin(phase); 42 | sin1 = amplitude * std::sin(phase - inc); 43 | dsin = 2.0f * std::cos(inc); 44 | 45 | if (phase*phase > 1e-9) { 46 | output = sin0 / phase; 47 | } else { 48 | output = amplitude; 49 | } 50 | } else { 51 | if (phase > phaseMax) { 52 | phase = phaseMax + phaseMax - phase; 53 | inc = -inc; 54 | } 55 | 56 | float sinp = dsin * sin0 - sin1; 57 | sin1 = sin0; 58 | sin0 = sinp; 59 | 60 | output = sinp / phase; 61 | } 62 | 63 | return output - dc; 64 | } 65 | 66 | void squareWave(Oscillator& other, float newPeriod) 67 | { 68 | reset(); 69 | 70 | if (other.inc > 0.0f) { 71 | phase = other.phaseMax + other.phaseMax - other.phase; 72 | inc = -other.inc; 73 | } else if (other.inc < 0.0f) { 74 | phase = other.phase; 75 | inc = other.inc; 76 | } else { 77 | phase = -PI; 78 | inc = PI; 79 | } 80 | 81 | phase += PI * newPeriod / 2.0f; 82 | phaseMax = phase; 83 | } 84 | 85 | private: 86 | float phase; 87 | float phaseMax; 88 | float inc; 89 | 90 | float sin0; 91 | float sin1; 92 | float dsin; 93 | 94 | float dc; 95 | }; 96 | -------------------------------------------------------------------------------- /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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /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 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/Preset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const int NUM_PARAMS = 26; 6 | 7 | struct Preset 8 | { 9 | Preset(const char* name, 10 | float p0, float p1, float p2, float p3, 11 | float p4, float p5, float p6, float p7, 12 | float p8, float p9, float p10, float p11, 13 | float p12, float p13, float p14, float p15, 14 | float p16, float p17, float p18, float p19, 15 | float p20, float p21, float p22, float p23, 16 | float p24, float p25) 17 | { 18 | strcpy(this->name, name); 19 | param[0] = p0; // Osc Mix 20 | param[1] = p1; // Osc Tune 21 | param[2] = p2; // Osc Fine 22 | param[3] = p3; // Glide Mode 23 | param[4] = p4; // Glide Rate 24 | param[5] = p5; // Glide Bend 25 | param[6] = p6; // Filter Freq 26 | param[7] = p7; // Filter Reso 27 | param[8] = p8; // Filter Env 28 | param[9] = p9; // Filter LFO 29 | param[10] = p10; // Velocity 30 | param[11] = p11; // Filter Attack 31 | param[12] = p12; // Filter Decay 32 | param[13] = p13; // Filter Sustain 33 | param[14] = p14; // Filter Release 34 | param[15] = p15; // Env Attack 35 | param[16] = p16; // Env Decay 36 | param[17] = p17; // Env Sustain 37 | param[18] = p18; // Env Release 38 | param[19] = p19; // LFO Rate 39 | param[20] = p20; // Vibrato 40 | param[21] = p21; // Noise 41 | param[22] = p22; // Octave 42 | param[23] = p23; // Tuning 43 | param[24] = p24; // Output Level 44 | param[25] = p25; // Polyphony 45 | } 46 | 47 | char name[40]; 48 | float param[NUM_PARAMS]; 49 | }; 50 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/Synth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Voice.h" 5 | #include "NoiseGenerator.h" 6 | 7 | class Synth 8 | { 9 | public: 10 | Synth(); 11 | 12 | void allocateResources(double sampleRate, int samplesPerBlock); 13 | void deallocateResources(); 14 | void reset(); 15 | void render(float** outputBuffers, int sampleCount); 16 | void midiMessage(uint8_t data0, uint8_t data1, uint8_t data2); 17 | 18 | float noiseMix; 19 | 20 | float envAttack, envDecay, envSustain, envRelease; 21 | 22 | float oscMix; 23 | float detune; 24 | float tune; 25 | 26 | static constexpr int MAX_VOICES = 8; 27 | int numVoices; 28 | 29 | float volumeTrim; 30 | 31 | juce::LinearSmoothedValue outputLevelSmoother; 32 | 33 | float velocitySensitivity; 34 | bool ignoreVelocity; 35 | 36 | const int LFO_MAX = 32; 37 | float lfoInc; 38 | 39 | float vibrato; 40 | float pwmDepth; 41 | 42 | int glideMode; 43 | float glideRate; 44 | float glideBend; 45 | 46 | private: 47 | void updateLFO(); 48 | 49 | void controlChange(uint8_t data1, uint8_t data2); 50 | 51 | void noteOn(int note, int velocity); 52 | void noteOff(int note); 53 | 54 | void startVoice(int v, int note, int velocity); 55 | void restartMonoVoice(int note, int velocity); 56 | 57 | float calcPeriod(int v, int note) const; 58 | int findFreeVoice() const; 59 | 60 | void shiftQueuedNotes(); 61 | int nextQueuedNote(); 62 | 63 | inline void updatePeriod(Voice& voice) 64 | { 65 | voice.osc1.period = voice.period * pitchBend; 66 | voice.osc2.period = voice.osc1.period * detune; 67 | } 68 | 69 | bool isPlayingLegatoStyle() const; 70 | 71 | float sampleRate; 72 | std::array voices; 73 | NoiseGenerator noiseGen; 74 | 75 | int lastNote; 76 | 77 | int lfoStep; 78 | float lfo; 79 | 80 | float pitchBend; 81 | bool sustainPedalPressed; 82 | float modWheel; 83 | }; 84 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | inline void protectYourEars(float* buffer, int sampleCount) 4 | { 5 | if (buffer == nullptr) { return; } 6 | bool firstWarning = true; 7 | for (int i = 0; i < sampleCount; ++i) { 8 | float x = buffer[i]; 9 | bool silence = false; 10 | if (std::isnan(x)) { 11 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 12 | silence = true; 13 | } else if (std::isinf(x)) { 14 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 15 | silence = true; 16 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 17 | DBG("!!! WARNING: sample out of range, silencing !!!"); 18 | silence = true; 19 | } else if (x < -1.0f) { 20 | if (firstWarning) { 21 | DBG("!!! WARNING: sample out of range, clamping !!!"); 22 | firstWarning = false; 23 | } 24 | buffer[i] = -1.0f; 25 | } else if (x > 1.0f) { 26 | if (firstWarning) { 27 | DBG("!!! WARNING: sample out of range, clamping !!!"); 28 | firstWarning = false; 29 | } 30 | buffer[i] = 1.0f; 31 | } 32 | if (silence) { 33 | memset(buffer, 0, sampleCount * sizeof(float)); 34 | return; 35 | } 36 | } 37 | } 38 | 39 | template 40 | inline static void castParameter(juce::AudioProcessorValueTreeState& apvts, const juce::ParameterID& id, T& destination) 41 | { 42 | destination = dynamic_cast(apvts.getParameter(id.getParamID())); 43 | jassert(destination); // parameter does not exist or wrong type 44 | } 45 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 11/Source/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Oscillator.h" 4 | #include "Envelope.h" 5 | 6 | struct Voice 7 | { 8 | int note; 9 | float saw; 10 | float period; 11 | float target; 12 | 13 | float glideRate; 14 | 15 | Oscillator osc1; 16 | Oscillator osc2; 17 | Envelope env; 18 | 19 | float panLeft, panRight; 20 | 21 | void reset() 22 | { 23 | note = 0; 24 | saw = 0.0f; 25 | 26 | osc1.reset(); 27 | osc2.reset(); 28 | env.reset(); 29 | 30 | panLeft = 0.707f; 31 | panRight = 0.707f; 32 | } 33 | 34 | float render(float input) 35 | { 36 | float sample1 = osc1.nextSample(); 37 | float sample2 = osc2.nextSample(); 38 | saw = saw * 0.997f + sample1 - sample2; 39 | 40 | float output = saw + input; 41 | float envelope = env.nextValue(); 42 | return output * envelope; 43 | } 44 | 45 | void updatePanning() 46 | { 47 | float panning = std::clamp((note - 60.0f) / 24.0f, -1.0f, 1.0f); 48 | panLeft = std::sin(PI_OVER_4 * (1.0f - panning)); 49 | panRight = std::sin(PI_OVER_4 * (1.0f + panning)); 50 | } 51 | 52 | void updateLFO() 53 | { 54 | period += glideRate * (target - period); 55 | } 56 | 57 | void release() 58 | { 59 | env.release(); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/Envelope.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const float SILENCE = 0.0001f; 4 | 5 | class Envelope 6 | { 7 | public: 8 | void reset() 9 | { 10 | level = 0.0f; 11 | target = 0.0f; 12 | multiplier = 0.0f; 13 | } 14 | 15 | float nextValue() 16 | { 17 | level = multiplier * (level - target) + target; 18 | 19 | if (level + target > 3.0f) { 20 | multiplier = decayMultiplier; 21 | target = sustainLevel; 22 | } 23 | 24 | return level; 25 | } 26 | 27 | inline bool isActive() const 28 | { 29 | return level > SILENCE; 30 | } 31 | 32 | inline bool isInAttack() const 33 | { 34 | return target >= 2.0f; 35 | } 36 | 37 | void attack() 38 | { 39 | level += SILENCE + SILENCE; 40 | target = 2.0f; 41 | multiplier = attackMultiplier; 42 | } 43 | 44 | void release() 45 | { 46 | target = 0.0f; 47 | multiplier = releaseMultiplier; 48 | } 49 | 50 | float attackMultiplier; 51 | float decayMultiplier; 52 | float sustainLevel; 53 | float releaseMultiplier; 54 | 55 | float level; 56 | 57 | private: 58 | float target; 59 | float multiplier; 60 | }; 61 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/Filter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if 1 4 | 5 | // Resonant low-pass filter based on Cytomic SVF. 6 | class Filter 7 | { 8 | public: 9 | float sampleRate; 10 | 11 | void updateCoefficients(float cutoff, float Q) 12 | { 13 | g = std::tan(PI * cutoff / sampleRate); 14 | k = 1.0f / Q; 15 | a1 = 1.0f / (1.0f + g * (g + k)); 16 | a2 = g * a1; 17 | a3 = g * a2; 18 | } 19 | 20 | void reset() 21 | { 22 | g = 0.0f; 23 | k = 0.0f; 24 | a1 = 0.0f; 25 | a2 = 0.0f; 26 | a3 = 0.0f; 27 | 28 | ic1eq = 0.0f; 29 | ic2eq = 0.0f; 30 | } 31 | 32 | float render(float x) 33 | { 34 | float v3 = x - ic2eq; 35 | float v1 = a1 * ic1eq + a2 * v3; 36 | float v2 = ic2eq + a2 * ic1eq + a3 * v3; 37 | ic1eq = 2.0f * v1 - ic1eq; 38 | ic2eq = 2.0f * v2 - ic2eq; 39 | return v2; 40 | } 41 | 42 | private: 43 | const float PI = 3.1415926535897932f; 44 | 45 | float g, k, a1, a2, a3; // filter coefficients 46 | float ic1eq, ic2eq; // internal state 47 | }; 48 | 49 | #else 50 | 51 | #include 52 | 53 | // This is the Moog Ladder Filter from the chapter 10 bonus section. 54 | class Filter : public juce::dsp::LadderFilter 55 | { 56 | public: 57 | void updateCoefficients(float cutoff, float Q) 58 | { 59 | setCutoffFrequencyHz(cutoff); 60 | setResonance(std::clamp(Q / 30.0f, 0.0f, 1.0f)); 61 | //setDrive(2.0f); 62 | } 63 | 64 | float render(float x) 65 | { 66 | updateSmoothers(); 67 | return processSample(x, 0); 68 | } 69 | }; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/NoiseGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class NoiseGenerator 4 | { 5 | public: 6 | void reset() 7 | { 8 | noiseSeed = 22222; 9 | } 10 | 11 | float nextValue() 12 | { 13 | // Generate the next integer pseudorandom number. 14 | noiseSeed = noiseSeed * 196314165 + 907633515; 15 | 16 | // Convert to a signed value. 17 | int temp = int(noiseSeed >> 7) - 16777216; 18 | 19 | // Convert to a floating-point number between -1.0 and 1.0. 20 | return float(temp) / 16777216.0f; 21 | } 22 | 23 | private: 24 | unsigned int noiseSeed; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/Oscillator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const float PI_OVER_4 = 0.7853981633974483f; 6 | const float PI = 3.1415926535897932f; 7 | const float TWO_PI = 6.2831853071795864f; 8 | 9 | class Oscillator 10 | { 11 | public: 12 | float period = 0.0f; 13 | float modulation = 1.0f; 14 | float amplitude = 1.0f; 15 | 16 | void reset() 17 | { 18 | inc = 0.0f; 19 | phase = 0.0f; 20 | sin0 = 0.0f; 21 | sin1 = 0.0f; 22 | dsin = 0.0f; 23 | dc = 0.0f; 24 | } 25 | 26 | float nextSample() 27 | { 28 | float output = 0.0f; 29 | 30 | phase += inc; 31 | if (phase <= PI_OVER_4) { 32 | float halfPeriod = (period / 2.0f) * modulation; 33 | phaseMax = std::floor(0.5f + halfPeriod) - 0.5f; 34 | 35 | dc = 0.5f * amplitude / phaseMax; 36 | phaseMax *= PI; 37 | 38 | inc = phaseMax / halfPeriod; 39 | phase = -phase; 40 | 41 | sin0 = amplitude * std::sin(phase); 42 | sin1 = amplitude * std::sin(phase - inc); 43 | dsin = 2.0f * std::cos(inc); 44 | 45 | if (phase*phase > 1e-9) { 46 | output = sin0 / phase; 47 | } else { 48 | output = amplitude; 49 | } 50 | } else { 51 | if (phase > phaseMax) { 52 | phase = phaseMax + phaseMax - phase; 53 | inc = -inc; 54 | } 55 | 56 | float sinp = dsin * sin0 - sin1; 57 | sin1 = sin0; 58 | sin0 = sinp; 59 | 60 | output = sinp / phase; 61 | } 62 | 63 | return output - dc; 64 | } 65 | 66 | void squareWave(Oscillator& other, float newPeriod) 67 | { 68 | reset(); 69 | 70 | if (other.inc > 0.0f) { 71 | phase = other.phaseMax + other.phaseMax - other.phase; 72 | inc = -other.inc; 73 | } else if (other.inc < 0.0f) { 74 | phase = other.phase; 75 | inc = other.inc; 76 | } else { 77 | phase = -PI; 78 | inc = PI; 79 | } 80 | 81 | phase += PI * newPeriod / 2.0f; 82 | phaseMax = phase; 83 | } 84 | 85 | private: 86 | float phase; 87 | float phaseMax; 88 | float inc; 89 | 90 | float sin0; 91 | float sin1; 92 | float dsin; 93 | 94 | float dc; 95 | }; 96 | -------------------------------------------------------------------------------- /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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /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 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/Preset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const int NUM_PARAMS = 26; 6 | 7 | struct Preset 8 | { 9 | Preset(const char* name, 10 | float p0, float p1, float p2, float p3, 11 | float p4, float p5, float p6, float p7, 12 | float p8, float p9, float p10, float p11, 13 | float p12, float p13, float p14, float p15, 14 | float p16, float p17, float p18, float p19, 15 | float p20, float p21, float p22, float p23, 16 | float p24, float p25) 17 | { 18 | strcpy(this->name, name); 19 | param[0] = p0; // Osc Mix 20 | param[1] = p1; // Osc Tune 21 | param[2] = p2; // Osc Fine 22 | param[3] = p3; // Glide Mode 23 | param[4] = p4; // Glide Rate 24 | param[5] = p5; // Glide Bend 25 | param[6] = p6; // Filter Freq 26 | param[7] = p7; // Filter Reso 27 | param[8] = p8; // Filter Env 28 | param[9] = p9; // Filter LFO 29 | param[10] = p10; // Velocity 30 | param[11] = p11; // Filter Attack 31 | param[12] = p12; // Filter Decay 32 | param[13] = p13; // Filter Sustain 33 | param[14] = p14; // Filter Release 34 | param[15] = p15; // Env Attack 35 | param[16] = p16; // Env Decay 36 | param[17] = p17; // Env Sustain 37 | param[18] = p18; // Env Release 38 | param[19] = p19; // LFO Rate 39 | param[20] = p20; // Vibrato 40 | param[21] = p21; // Noise 41 | param[22] = p22; // Octave 42 | param[23] = p23; // Tuning 43 | param[24] = p24; // Output Level 44 | param[25] = p25; // Polyphony 45 | } 46 | 47 | char name[40]; 48 | float param[NUM_PARAMS]; 49 | }; 50 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/Synth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Voice.h" 5 | #include "NoiseGenerator.h" 6 | 7 | class Synth 8 | { 9 | public: 10 | Synth(); 11 | 12 | void allocateResources(double sampleRate, int samplesPerBlock); 13 | void deallocateResources(); 14 | void reset(); 15 | void render(float** outputBuffers, int sampleCount); 16 | void midiMessage(uint8_t data0, uint8_t data1, uint8_t data2); 17 | 18 | float noiseMix; 19 | 20 | float envAttack, envDecay, envSustain, envRelease; 21 | 22 | float oscMix; 23 | float detune; 24 | float tune; 25 | 26 | static constexpr int MAX_VOICES = 8; 27 | int numVoices; 28 | 29 | float volumeTrim; 30 | 31 | juce::LinearSmoothedValue outputLevelSmoother; 32 | 33 | float velocitySensitivity; 34 | bool ignoreVelocity; 35 | 36 | const int LFO_MAX = 32; 37 | float lfoInc; 38 | 39 | float vibrato; 40 | float pwmDepth; 41 | 42 | int glideMode; 43 | float glideRate; 44 | float glideBend; 45 | 46 | float filterKeyTracking; 47 | float filterQ; 48 | float filterLFODepth; 49 | float filterAttack, filterDecay, filterSustain, filterRelease; 50 | float filterEnvDepth; 51 | 52 | private: 53 | void updateLFO(); 54 | 55 | void controlChange(uint8_t data1, uint8_t data2); 56 | 57 | void noteOn(int note, int velocity); 58 | void noteOff(int note); 59 | 60 | void startVoice(int v, int note, int velocity); 61 | void restartMonoVoice(int note, int velocity); 62 | 63 | float calcPeriod(int v, int note) const; 64 | int findFreeVoice() const; 65 | 66 | void shiftQueuedNotes(); 67 | int nextQueuedNote(); 68 | 69 | inline void updatePeriod(Voice& voice) 70 | { 71 | voice.osc1.period = voice.period * pitchBend; 72 | voice.osc2.period = voice.osc1.period * detune; 73 | } 74 | 75 | bool isPlayingLegatoStyle() const; 76 | 77 | float sampleRate; 78 | std::array voices; 79 | NoiseGenerator noiseGen; 80 | 81 | int lastNote; 82 | 83 | int lfoStep; 84 | float lfo; 85 | float filterZip; 86 | 87 | float pitchBend; 88 | bool sustainPedalPressed; 89 | float modWheel; 90 | float resonanceCtl; 91 | float pressure; 92 | float filterCtl; 93 | }; 94 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | inline void protectYourEars(float* buffer, int sampleCount) 4 | { 5 | if (buffer == nullptr) { return; } 6 | bool firstWarning = true; 7 | for (int i = 0; i < sampleCount; ++i) { 8 | float x = buffer[i]; 9 | bool silence = false; 10 | if (std::isnan(x)) { 11 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 12 | silence = true; 13 | } else if (std::isinf(x)) { 14 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 15 | silence = true; 16 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 17 | DBG("!!! WARNING: sample out of range, silencing !!!"); 18 | silence = true; 19 | } else if (x < -1.0f) { 20 | if (firstWarning) { 21 | DBG("!!! WARNING: sample out of range, clamping !!!"); 22 | firstWarning = false; 23 | } 24 | buffer[i] = -1.0f; 25 | } else if (x > 1.0f) { 26 | if (firstWarning) { 27 | DBG("!!! WARNING: sample out of range, clamping !!!"); 28 | firstWarning = false; 29 | } 30 | buffer[i] = 1.0f; 31 | } 32 | if (silence) { 33 | memset(buffer, 0, sampleCount * sizeof(float)); 34 | return; 35 | } 36 | } 37 | } 38 | 39 | template 40 | inline static void castParameter(juce::AudioProcessorValueTreeState& apvts, const juce::ParameterID& id, T& destination) 41 | { 42 | destination = dynamic_cast(apvts.getParameter(id.getParamID())); 43 | jassert(destination); // parameter does not exist or wrong type 44 | } 45 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 12/Source/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Oscillator.h" 4 | #include "Envelope.h" 5 | #include "Filter.h" 6 | 7 | struct Voice 8 | { 9 | int note; 10 | float saw; 11 | float period; 12 | float target; 13 | 14 | float glideRate; 15 | float cutoff; 16 | float filterMod; 17 | float filterQ; 18 | float pitchBend; 19 | float filterEnvDepth; 20 | 21 | Oscillator osc1; 22 | Oscillator osc2; 23 | Envelope env; 24 | Envelope filterEnv; 25 | Filter filter; 26 | 27 | float panLeft, panRight; 28 | 29 | void reset() 30 | { 31 | note = 0; 32 | saw = 0.0f; 33 | 34 | osc1.reset(); 35 | osc2.reset(); 36 | env.reset(); 37 | filterEnv.reset(); 38 | filter.reset(); 39 | 40 | panLeft = 0.707f; 41 | panRight = 0.707f; 42 | } 43 | 44 | float render(float input) 45 | { 46 | float sample1 = osc1.nextSample(); 47 | float sample2 = osc2.nextSample(); 48 | saw = saw * 0.997f + sample1 - sample2; 49 | 50 | float output = saw + input; 51 | output = filter.render(output); 52 | 53 | float envelope = env.nextValue(); 54 | return output * envelope; 55 | } 56 | 57 | void updatePanning() 58 | { 59 | float panning = std::clamp((note - 60.0f) / 24.0f, -1.0f, 1.0f); 60 | panLeft = std::sin(PI_OVER_4 * (1.0f - panning)); 61 | panRight = std::sin(PI_OVER_4 * (1.0f + panning)); 62 | } 63 | 64 | void updateLFO() 65 | { 66 | period += glideRate * (target - period); 67 | 68 | float fenv = filterEnv.nextValue(); 69 | float modulatedCutoff = cutoff * std::exp(filterMod + filterEnvDepth * fenv) / pitchBend; 70 | 71 | modulatedCutoff = std::clamp(modulatedCutoff, 30.0f, 20000.0f); 72 | filter.updateCoefficients(modulatedCutoff, filterQ); 73 | } 74 | 75 | void release() 76 | { 77 | env.release(); 78 | filterEnv.release(); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/Envelope.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const float SILENCE = 0.0001f; 4 | 5 | class Envelope 6 | { 7 | public: 8 | void reset() 9 | { 10 | level = 0.0f; 11 | target = 0.0f; 12 | multiplier = 0.0f; 13 | } 14 | 15 | float nextValue() 16 | { 17 | level = multiplier * (level - target) + target; 18 | 19 | if (level + target > 3.0f) { 20 | multiplier = decayMultiplier; 21 | target = sustainLevel; 22 | } 23 | 24 | return level; 25 | } 26 | 27 | inline bool isActive() const 28 | { 29 | return level > SILENCE; 30 | } 31 | 32 | inline bool isInAttack() const 33 | { 34 | return target >= 2.0f; 35 | } 36 | 37 | void attack() 38 | { 39 | level += SILENCE + SILENCE; 40 | target = 2.0f; 41 | multiplier = attackMultiplier; 42 | } 43 | 44 | void release() 45 | { 46 | target = 0.0f; 47 | multiplier = releaseMultiplier; 48 | } 49 | 50 | float attackMultiplier; 51 | float decayMultiplier; 52 | float sustainLevel; 53 | float releaseMultiplier; 54 | 55 | float level; 56 | 57 | private: 58 | float target; 59 | float multiplier; 60 | }; 61 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/Filter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if 1 4 | 5 | // Resonant low-pass filter based on Cytomic SVF. 6 | class Filter 7 | { 8 | public: 9 | float sampleRate; 10 | 11 | void updateCoefficients(float cutoff, float Q) 12 | { 13 | g = std::tan(PI * cutoff / sampleRate); 14 | k = 1.0f / Q; 15 | a1 = 1.0f / (1.0f + g * (g + k)); 16 | a2 = g * a1; 17 | a3 = g * a2; 18 | } 19 | 20 | void reset() 21 | { 22 | g = 0.0f; 23 | k = 0.0f; 24 | a1 = 0.0f; 25 | a2 = 0.0f; 26 | a3 = 0.0f; 27 | 28 | ic1eq = 0.0f; 29 | ic2eq = 0.0f; 30 | } 31 | 32 | float render(float x) 33 | { 34 | float v3 = x - ic2eq; 35 | float v1 = a1 * ic1eq + a2 * v3; 36 | float v2 = ic2eq + a2 * ic1eq + a3 * v3; 37 | ic1eq = 2.0f * v1 - ic1eq; 38 | ic2eq = 2.0f * v2 - ic2eq; 39 | return v2; 40 | } 41 | 42 | private: 43 | const float PI = 3.1415926535897932f; 44 | 45 | float g, k, a1, a2, a3; // filter coefficients 46 | float ic1eq, ic2eq; // internal state 47 | }; 48 | 49 | #else 50 | 51 | #include 52 | 53 | // This is the Moog Ladder Filter from the chapter 10 bonus section. 54 | class Filter : public juce::dsp::LadderFilter 55 | { 56 | public: 57 | void updateCoefficients(float cutoff, float Q) 58 | { 59 | setCutoffFrequencyHz(cutoff); 60 | setResonance(std::clamp(Q / 30.0f, 0.0f, 1.0f)); 61 | //setDrive(2.0f); 62 | } 63 | 64 | float render(float x) 65 | { 66 | updateSmoothers(); 67 | return processSample(x, 0); 68 | } 69 | }; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/LookAndFeel.cpp: -------------------------------------------------------------------------------- 1 | #include "LookAndFeel.h" 2 | 3 | LookAndFeel::LookAndFeel() 4 | { 5 | setColour(juce::ResizableWindow::backgroundColourId, juce::Colour(30, 60, 90)); 6 | 7 | setColour(juce::Slider::rotarySliderOutlineColourId, juce::Colour(0, 0, 0)); 8 | setColour(juce::Slider::rotarySliderFillColourId, juce::Colour(90, 180, 240)); 9 | setColour(juce::Slider::thumbColourId, juce::Colour(255, 255, 255)); 10 | 11 | setColour(juce::TextButton::buttonColourId, juce::Colour(15, 30, 45)); 12 | setColour(juce::TextButton::buttonOnColourId, juce::Colour(90, 180, 240)); 13 | setColour(juce::TextButton::textColourOffId, juce::Colour(180, 180, 180)); 14 | setColour(juce::TextButton::textColourOnId, juce::Colour(255, 255, 255)); 15 | setColour(juce::ComboBox::outlineColourId, juce::Colour(180, 180, 180)); 16 | 17 | juce::Typeface::Ptr typeface = juce::Typeface::createSystemTypefaceFor(BinaryData::LatoMedium_ttf, BinaryData::LatoMedium_ttfSize); 18 | setDefaultSansSerifTypeface(typeface); 19 | } 20 | 21 | void LookAndFeel::drawRotarySlider( 22 | juce::Graphics& g, int x, int y, int width, int /*height*/, float sliderPos, 23 | float rotaryStartAngle, float rotaryEndAngle, juce::Slider& slider) 24 | { 25 | auto outlineColor = slider.findColour(juce::Slider::rotarySliderOutlineColourId); 26 | auto fillColor = slider.findColour(juce::Slider::rotarySliderFillColourId); 27 | auto dialColor = slider.findColour(juce::Slider::thumbColourId); 28 | 29 | auto bounds = juce::Rectangle(x, y, width, width).toFloat() 30 | .withTrimmedLeft(16.0f).withTrimmedRight(16.0f) 31 | .withTrimmedTop(0.0f).withTrimmedBottom(8.0f); 32 | 33 | auto radius = bounds.getWidth() / 2.0f; 34 | auto toAngle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); 35 | auto lineW = 6.0f; 36 | auto arcRadius = radius - lineW / 2.0f; 37 | 38 | auto arg = toAngle - juce::MathConstants::halfPi; 39 | auto dialW = 3.0f; 40 | auto dialRadius = arcRadius - 6.0f; 41 | 42 | auto center = bounds.getCentre(); 43 | auto strokeType = juce::PathStrokeType(lineW, juce::PathStrokeType::curved, 44 | juce::PathStrokeType::butt); 45 | 46 | juce::Path backgroundArc; 47 | backgroundArc.addCentredArc(center.x, center.y, arcRadius, arcRadius, 0.0f, 48 | rotaryStartAngle, rotaryEndAngle, true); 49 | g.setColour(outlineColor); 50 | g.strokePath(backgroundArc, strokeType); 51 | 52 | if (slider.isEnabled()) { 53 | juce::Path valueArc; 54 | valueArc.addCentredArc(center.x, center.y, arcRadius, arcRadius, 0.0f, 55 | rotaryStartAngle, toAngle, true); 56 | g.setColour(fillColor); 57 | g.strokePath(valueArc, strokeType); 58 | } 59 | 60 | juce::Point thumbPoint(center.x + dialRadius * std::cos(arg), 61 | center.y + dialRadius * std::sin(arg)); 62 | g.setColour(dialColor); 63 | g.drawLine(center.x, center.y, thumbPoint.x, thumbPoint.y, dialW); 64 | g.fillEllipse(juce::Rectangle(dialW, dialW).withCentre(thumbPoint)); 65 | g.fillEllipse(juce::Rectangle(dialW, dialW).withCentre(center)); 66 | } 67 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/LookAndFeel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class LookAndFeel : public juce::LookAndFeel_V4 { 6 | public: 7 | LookAndFeel(); 8 | 9 | void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, 10 | float sliderPos, float rotaryStartAngle, 11 | float rotaryEndAngle, juce::Slider& slider) override; 12 | 13 | private: 14 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LookAndFeel) 15 | }; 16 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/NoiseGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class NoiseGenerator 4 | { 5 | public: 6 | void reset() 7 | { 8 | noiseSeed = 22222; 9 | } 10 | 11 | float nextValue() 12 | { 13 | // Generate the next integer pseudorandom number. 14 | noiseSeed = noiseSeed * 196314165 + 907633515; 15 | 16 | // Convert to a signed value. 17 | int temp = int(noiseSeed >> 7) - 16777216; 18 | 19 | // Convert to a floating-point number between -1.0 and 1.0. 20 | return float(temp) / 16777216.0f; 21 | } 22 | 23 | private: 24 | unsigned int noiseSeed; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/Oscillator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const float PI_OVER_4 = 0.7853981633974483f; 6 | const float PI = 3.1415926535897932f; 7 | const float TWO_PI = 6.2831853071795864f; 8 | 9 | class Oscillator 10 | { 11 | public: 12 | float period = 0.0f; 13 | float modulation = 1.0f; 14 | float amplitude = 1.0f; 15 | 16 | void reset() 17 | { 18 | inc = 0.0f; 19 | phase = 0.0f; 20 | sin0 = 0.0f; 21 | sin1 = 0.0f; 22 | dsin = 0.0f; 23 | dc = 0.0f; 24 | } 25 | 26 | float nextSample() 27 | { 28 | float output = 0.0f; 29 | 30 | phase += inc; 31 | if (phase <= PI_OVER_4) { 32 | float halfPeriod = (period / 2.0f) * modulation; 33 | phaseMax = std::floor(0.5f + halfPeriod) - 0.5f; 34 | 35 | dc = 0.5f * amplitude / phaseMax; 36 | phaseMax *= PI; 37 | 38 | inc = phaseMax / halfPeriod; 39 | phase = -phase; 40 | 41 | sin0 = amplitude * std::sin(phase); 42 | sin1 = amplitude * std::sin(phase - inc); 43 | dsin = 2.0f * std::cos(inc); 44 | 45 | if (phase*phase > 1e-9) { 46 | output = sin0 / phase; 47 | } else { 48 | output = amplitude; 49 | } 50 | } else { 51 | if (phase > phaseMax) { 52 | phase = phaseMax + phaseMax - phase; 53 | inc = -inc; 54 | } 55 | 56 | float sinp = dsin * sin0 - sin1; 57 | sin1 = sin0; 58 | sin0 = sinp; 59 | 60 | output = sinp / phase; 61 | } 62 | 63 | return output - dc; 64 | } 65 | 66 | void squareWave(Oscillator& other, float newPeriod) 67 | { 68 | reset(); 69 | 70 | if (other.inc > 0.0f) { 71 | phase = other.phaseMax + other.phaseMax - other.phase; 72 | inc = -other.inc; 73 | } else if (other.inc < 0.0f) { 74 | phase = other.phase; 75 | inc = other.inc; 76 | } else { 77 | phase = -PI; 78 | inc = PI; 79 | } 80 | 81 | phase += PI * newPeriod / 2.0f; 82 | phaseMax = phase; 83 | } 84 | 85 | private: 86 | float phase; 87 | float phaseMax; 88 | float inc; 89 | 90 | float sin0; 91 | float sin1; 92 | float dsin; 93 | 94 | float dc; 95 | }; 96 | -------------------------------------------------------------------------------- /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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& p) 14 | : AudioProcessorEditor (&p), audioProcessor (p) 15 | { 16 | juce::LookAndFeel::setDefaultLookAndFeel(&globalLNF); 17 | 18 | outputLevelKnob.label = "Level"; 19 | addAndMakeVisible(outputLevelKnob); 20 | 21 | filterResoKnob.label = "Reso"; 22 | addAndMakeVisible(filterResoKnob); 23 | 24 | polyModeButton.setButtonText("Poly"); 25 | polyModeButton.setClickingTogglesState(true); 26 | addAndMakeVisible(polyModeButton); 27 | 28 | setSize(600, 400); 29 | 30 | midiLearnButton.setButtonText("MIDI Learn"); 31 | midiLearnButton.addListener(this); 32 | addAndMakeVisible(midiLearnButton); 33 | } 34 | 35 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 36 | { 37 | midiLearnButton.removeListener(this); 38 | audioProcessor.midiLearn = false; 39 | } 40 | 41 | //============================================================================== 42 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 43 | { 44 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 45 | } 46 | 47 | void JX11AudioProcessorEditor::resized() 48 | { 49 | juce::Rectangle r(20, 20, 100, 120); 50 | outputLevelKnob.setBounds(r); 51 | 52 | r = r.withX(r.getRight() + 20); 53 | filterResoKnob.setBounds(r); 54 | 55 | polyModeButton.setSize(80, 30); 56 | polyModeButton.setCentrePosition(r.withX(r.getRight()).getCentre()); 57 | 58 | midiLearnButton.setBounds(400, 20, 100, 30); 59 | } 60 | 61 | void JX11AudioProcessorEditor::buttonClicked(juce::Button* button) 62 | { 63 | button->setButtonText("Waiting..."); 64 | button->setEnabled(false); 65 | audioProcessor.midiLearn = true; 66 | 67 | startTimerHz(10); 68 | } 69 | 70 | void JX11AudioProcessorEditor::timerCallback() 71 | { 72 | if (!audioProcessor.midiLearn) { 73 | stopTimer(); 74 | midiLearnButton.setButtonText("MIDI Learn"); 75 | midiLearnButton.setEnabled(true); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /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 "RotaryKnob.h" 14 | #include "LookAndFeel.h" 15 | 16 | //============================================================================== 17 | /** 18 | */ 19 | class JX11AudioProcessorEditor : public juce::AudioProcessorEditor, 20 | private juce::Button::Listener, juce::Timer 21 | { 22 | public: 23 | JX11AudioProcessorEditor (JX11AudioProcessor&); 24 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 34 | 35 | LookAndFeel globalLNF; 36 | 37 | using APVTS = juce::AudioProcessorValueTreeState; 38 | using SliderAttachment = APVTS::SliderAttachment; 39 | using ButtonAttachment = APVTS::ButtonAttachment; 40 | 41 | RotaryKnob outputLevelKnob; 42 | SliderAttachment outputLevelAttachment { audioProcessor.apvts, ParameterID::outputLevel.getParamID(), outputLevelKnob.slider }; 43 | 44 | RotaryKnob filterResoKnob; 45 | SliderAttachment filterResoAttachment { audioProcessor.apvts, ParameterID::filterReso.getParamID(), filterResoKnob.slider }; 46 | 47 | juce::TextButton polyModeButton; 48 | ButtonAttachment polyModeAttachment { audioProcessor.apvts, ParameterID::polyMode.getParamID(), polyModeButton }; 49 | 50 | juce::TextButton midiLearnButton; 51 | void buttonClicked(juce::Button* button) override; 52 | void timerCallback() override; 53 | 54 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 55 | }; 56 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/Preset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const int NUM_PARAMS = 26; 6 | 7 | struct Preset 8 | { 9 | Preset(const char* name, 10 | float p0, float p1, float p2, float p3, 11 | float p4, float p5, float p6, float p7, 12 | float p8, float p9, float p10, float p11, 13 | float p12, float p13, float p14, float p15, 14 | float p16, float p17, float p18, float p19, 15 | float p20, float p21, float p22, float p23, 16 | float p24, float p25) 17 | { 18 | strcpy(this->name, name); 19 | param[0] = p0; // Osc Mix 20 | param[1] = p1; // Osc Tune 21 | param[2] = p2; // Osc Fine 22 | param[3] = p3; // Glide Mode 23 | param[4] = p4; // Glide Rate 24 | param[5] = p5; // Glide Bend 25 | param[6] = p6; // Filter Freq 26 | param[7] = p7; // Filter Reso 27 | param[8] = p8; // Filter Env 28 | param[9] = p9; // Filter LFO 29 | param[10] = p10; // Velocity 30 | param[11] = p11; // Filter Attack 31 | param[12] = p12; // Filter Decay 32 | param[13] = p13; // Filter Sustain 33 | param[14] = p14; // Filter Release 34 | param[15] = p15; // Env Attack 35 | param[16] = p16; // Env Decay 36 | param[17] = p17; // Env Sustain 37 | param[18] = p18; // Env Release 38 | param[19] = p19; // LFO Rate 39 | param[20] = p20; // Vibrato 40 | param[21] = p21; // Noise 41 | param[22] = p22; // Octave 42 | param[23] = p23; // Tuning 43 | param[24] = p24; // Output Level 44 | param[25] = p25; // Polyphony 45 | } 46 | 47 | char name[40]; 48 | float param[NUM_PARAMS]; 49 | }; 50 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/RotaryKnob.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "RotaryKnob.h" 3 | 4 | static constexpr int labelHeight = 15; 5 | static constexpr int textBoxHeight = 20; 6 | 7 | RotaryKnob::RotaryKnob() 8 | { 9 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); 10 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 100, textBoxHeight); 11 | slider.setRotaryParameters(juce::degreesToRadians(225.0f), juce::degreesToRadians(495.0f), true); 12 | addAndMakeVisible(slider); 13 | 14 | setBounds(0, 0, 100, 120); 15 | } 16 | 17 | RotaryKnob::~RotaryKnob() 18 | { 19 | } 20 | 21 | void RotaryKnob::paint (juce::Graphics& g) 22 | { 23 | g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId)); 24 | 25 | g.setFont(15.0f); 26 | g.setColour(juce::Colours::white); 27 | 28 | auto bounds = getLocalBounds(); 29 | g.drawText(label, juce::Rectangle{ 0, 0, bounds.getWidth(), labelHeight }, juce::Justification::centred); 30 | 31 | /* 32 | g.setColour(juce::Colours::red); 33 | g.drawRect(getLocalBounds(), 1); 34 | 35 | g.setColour(juce::Colours::yellow); 36 | g.drawRect(0, labelHeight, bounds.getWidth(), bounds.getHeight() - labelHeight - textBoxHeight, 1); 37 | 38 | g.setColour(juce::Colours::green); 39 | g.drawRect(0, 0, bounds.getWidth(), labelHeight, 1); 40 | */ 41 | } 42 | 43 | void RotaryKnob::resized() 44 | { 45 | auto bounds = getLocalBounds(); 46 | slider.setBounds(0, labelHeight, bounds.getWidth(), bounds.getHeight() - labelHeight); 47 | } 48 | -------------------------------------------------------------------------------- /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(); 9 | ~RotaryKnob() override; 10 | 11 | void paint (juce::Graphics&) override; 12 | void resized() override; 13 | 14 | juce::Slider slider; 15 | juce::String label; 16 | 17 | private: 18 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RotaryKnob) 19 | }; 20 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/Synth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Voice.h" 5 | #include "NoiseGenerator.h" 6 | 7 | class Synth 8 | { 9 | public: 10 | Synth(); 11 | 12 | void allocateResources(double sampleRate, int samplesPerBlock); 13 | void deallocateResources(); 14 | void reset(); 15 | void render(float** outputBuffers, int sampleCount); 16 | void midiMessage(uint8_t data0, uint8_t data1, uint8_t data2); 17 | 18 | float noiseMix; 19 | 20 | float envAttack, envDecay, envSustain, envRelease; 21 | 22 | float oscMix; 23 | float detune; 24 | float tune; 25 | 26 | static constexpr int MAX_VOICES = 8; 27 | int numVoices; 28 | 29 | float volumeTrim; 30 | 31 | juce::LinearSmoothedValue outputLevelSmoother; 32 | 33 | float velocitySensitivity; 34 | bool ignoreVelocity; 35 | 36 | const int LFO_MAX = 32; 37 | float lfoInc; 38 | 39 | float vibrato; 40 | float pwmDepth; 41 | 42 | int glideMode; 43 | float glideRate; 44 | float glideBend; 45 | 46 | float filterKeyTracking; 47 | float filterQ; 48 | float filterLFODepth; 49 | float filterAttack, filterDecay, filterSustain, filterRelease; 50 | float filterEnvDepth; 51 | 52 | uint8_t resoCC = 0x47; 53 | 54 | private: 55 | void updateLFO(); 56 | 57 | void controlChange(uint8_t data1, uint8_t data2); 58 | 59 | void noteOn(int note, int velocity); 60 | void noteOff(int note); 61 | 62 | void startVoice(int v, int note, int velocity); 63 | void restartMonoVoice(int note, int velocity); 64 | 65 | float calcPeriod(int v, int note) const; 66 | int findFreeVoice() const; 67 | 68 | void shiftQueuedNotes(); 69 | int nextQueuedNote(); 70 | 71 | inline void updatePeriod(Voice& voice) 72 | { 73 | voice.osc1.period = voice.period * pitchBend; 74 | voice.osc2.period = voice.osc1.period * detune; 75 | } 76 | 77 | bool isPlayingLegatoStyle() const; 78 | 79 | float sampleRate; 80 | std::array voices; 81 | NoiseGenerator noiseGen; 82 | 83 | int lastNote; 84 | 85 | int lfoStep; 86 | float lfo; 87 | float filterZip; 88 | 89 | float pitchBend; 90 | bool sustainPedalPressed; 91 | float modWheel; 92 | float resonanceCtl; 93 | float pressure; 94 | float filterCtl; 95 | }; 96 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | inline void protectYourEars(float* buffer, int sampleCount) 4 | { 5 | if (buffer == nullptr) { return; } 6 | bool firstWarning = true; 7 | for (int i = 0; i < sampleCount; ++i) { 8 | float x = buffer[i]; 9 | bool silence = false; 10 | if (std::isnan(x)) { 11 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 12 | silence = true; 13 | } else if (std::isinf(x)) { 14 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 15 | silence = true; 16 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 17 | DBG("!!! WARNING: sample out of range, silencing !!!"); 18 | silence = true; 19 | } else if (x < -1.0f) { 20 | if (firstWarning) { 21 | DBG("!!! WARNING: sample out of range, clamping !!!"); 22 | firstWarning = false; 23 | } 24 | buffer[i] = -1.0f; 25 | } else if (x > 1.0f) { 26 | if (firstWarning) { 27 | DBG("!!! WARNING: sample out of range, clamping !!!"); 28 | firstWarning = false; 29 | } 30 | buffer[i] = 1.0f; 31 | } 32 | if (silence) { 33 | memset(buffer, 0, sampleCount * sizeof(float)); 34 | return; 35 | } 36 | } 37 | } 38 | 39 | template 40 | inline static void castParameter(juce::AudioProcessorValueTreeState& apvts, const juce::ParameterID& id, T& destination) 41 | { 42 | destination = dynamic_cast(apvts.getParameter(id.getParamID())); 43 | jassert(destination); // parameter does not exist or wrong type 44 | } 45 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 13/Source/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Oscillator.h" 4 | #include "Envelope.h" 5 | #include "Filter.h" 6 | 7 | struct Voice 8 | { 9 | int note; 10 | float saw; 11 | float period; 12 | float target; 13 | 14 | float glideRate; 15 | float cutoff; 16 | float filterMod; 17 | float filterQ; 18 | float pitchBend; 19 | float filterEnvDepth; 20 | 21 | Oscillator osc1; 22 | Oscillator osc2; 23 | Envelope env; 24 | Envelope filterEnv; 25 | Filter filter; 26 | 27 | float panLeft, panRight; 28 | 29 | void reset() 30 | { 31 | note = 0; 32 | saw = 0.0f; 33 | 34 | osc1.reset(); 35 | osc2.reset(); 36 | env.reset(); 37 | filterEnv.reset(); 38 | filter.reset(); 39 | 40 | panLeft = 0.707f; 41 | panRight = 0.707f; 42 | } 43 | 44 | float render(float input) 45 | { 46 | float sample1 = osc1.nextSample(); 47 | float sample2 = osc2.nextSample(); 48 | saw = saw * 0.997f + sample1 - sample2; 49 | 50 | float output = saw + input; 51 | output = filter.render(output); 52 | 53 | float envelope = env.nextValue(); 54 | return output * envelope; 55 | } 56 | 57 | void updatePanning() 58 | { 59 | float panning = std::clamp((note - 60.0f) / 24.0f, -1.0f, 1.0f); 60 | panLeft = std::sin(PI_OVER_4 * (1.0f - panning)); 61 | panRight = std::sin(PI_OVER_4 * (1.0f + panning)); 62 | } 63 | 64 | void updateLFO() 65 | { 66 | period += glideRate * (target - period); 67 | 68 | float fenv = filterEnv.nextValue(); 69 | float modulatedCutoff = cutoff * std::exp(filterMod + filterEnvDepth * fenv) / pitchBend; 70 | 71 | modulatedCutoff = std::clamp(modulatedCutoff, 30.0f, 20000.0f); 72 | filter.updateCoefficients(modulatedCutoff, filterQ); 73 | } 74 | 75 | void release() 76 | { 77 | env.release(); 78 | filterEnv.release(); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 2/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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 2/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 JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 2/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 JX11AudioProcessor : public juce::AudioProcessor 17 | { 18 | public: 19 | //============================================================================== 20 | JX11AudioProcessor(); 21 | ~JX11AudioProcessor() 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 (JX11AudioProcessor) 59 | }; 60 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 3/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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 3/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 JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 3/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 JX11AudioProcessor : public juce::AudioProcessor 17 | { 18 | public: 19 | //============================================================================== 20 | JX11AudioProcessor(); 21 | ~JX11AudioProcessor() 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 (JX11AudioProcessor) 59 | }; 60 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 4/Source/NoiseGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class NoiseGenerator 4 | { 5 | public: 6 | void reset() 7 | { 8 | noiseSeed = 22222; 9 | } 10 | 11 | float nextValue() 12 | { 13 | // Generate the next integer pseudorandom number. 14 | noiseSeed = noiseSeed * 196314165 + 907633515; 15 | 16 | // Convert to a signed value. 17 | int temp = int(noiseSeed >> 7) - 16777216; 18 | 19 | // Convert to a floating-point number between -1.0 and 1.0. 20 | return float(temp) / 16777216.0f; 21 | } 22 | 23 | private: 24 | unsigned int noiseSeed; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 4/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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 4/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 JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 4/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 "Synth.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class JX11AudioProcessor : public juce::AudioProcessor 18 | { 19 | public: 20 | //============================================================================== 21 | JX11AudioProcessor(); 22 | ~JX11AudioProcessor() override; 23 | 24 | //============================================================================== 25 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 26 | void releaseResources() override; 27 | void reset() 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 | private: 59 | void splitBufferByEvents(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages); 60 | void handleMIDI(uint8_t data0, uint8_t data1, uint8_t data2); 61 | void render(juce::AudioBuffer& buffer, int sampleCount, int bufferOffset); 62 | 63 | private: 64 | Synth synth; 65 | 66 | //============================================================================== 67 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessor) 68 | }; 69 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 4/Source/Synth.cpp: -------------------------------------------------------------------------------- 1 | #include "Synth.h" 2 | #include "Utils.h" 3 | 4 | Synth::Synth() 5 | { 6 | sampleRate = 44100.0f; 7 | } 8 | 9 | void Synth::allocateResources(double sampleRate_, int /*samplesPerBlock*/) 10 | { 11 | sampleRate = static_cast(sampleRate_); 12 | } 13 | 14 | void Synth::deallocateResources() 15 | { 16 | // do nothing 17 | } 18 | 19 | void Synth::reset() 20 | { 21 | voice.reset(); 22 | noiseGen.reset(); 23 | } 24 | 25 | void Synth::render(float** outputBuffers, int sampleCount) 26 | { 27 | float* outputBufferLeft = outputBuffers[0]; 28 | float* outputBufferRight = outputBuffers[1]; 29 | 30 | for (int sample = 0; sample < sampleCount; ++sample) { 31 | float noise = noiseGen.nextValue(); 32 | 33 | float output = 0.0f; 34 | 35 | if (voice.note > 0) { 36 | output = noise * (voice.velocity / 127.0f) * 0.5f; 37 | } 38 | 39 | outputBufferLeft[sample] = output; 40 | if (outputBufferRight != nullptr) { 41 | outputBufferRight[sample] = output; 42 | } 43 | } 44 | 45 | protectYourEars(outputBufferLeft, sampleCount); 46 | protectYourEars(outputBufferRight, sampleCount); 47 | } 48 | 49 | void Synth::midiMessage(uint8_t data0, uint8_t data1, uint8_t data2) 50 | { 51 | switch (data0 & 0xF0) { 52 | // Note off 53 | case 0x80: 54 | noteOff(data1 & 0x7F); 55 | break; 56 | 57 | // Note on 58 | case 0x90: { 59 | uint8_t note = data1 & 0x7F; 60 | uint8_t velo = data2 & 0x7F; 61 | if (velo > 0) { 62 | noteOn(note, velo); 63 | } else { 64 | noteOff(note); 65 | } 66 | break; 67 | } 68 | } 69 | } 70 | 71 | void Synth::noteOn(int note, int velocity) 72 | { 73 | voice.note = note; 74 | voice.velocity = velocity; 75 | } 76 | 77 | void Synth::noteOff(int note) 78 | { 79 | if (voice.note == note) { 80 | voice.note = 0; 81 | voice.velocity = 0; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 4/Source/Synth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Voice.h" 5 | #include "NoiseGenerator.h" 6 | 7 | class Synth 8 | { 9 | public: 10 | Synth(); 11 | 12 | void allocateResources(double sampleRate, int samplesPerBlock); 13 | void deallocateResources(); 14 | void reset(); 15 | void render(float** outputBuffers, int sampleCount); 16 | void midiMessage(uint8_t data0, uint8_t data1, uint8_t data2); 17 | 18 | private: 19 | void noteOn(int note, int velocity); 20 | void noteOff(int note); 21 | 22 | float sampleRate; 23 | Voice voice; 24 | NoiseGenerator noiseGen; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 4/Source/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | inline void protectYourEars(float* buffer, int sampleCount) 4 | { 5 | if (buffer == nullptr) { return; } 6 | bool firstWarning = true; 7 | for (int i = 0; i < sampleCount; ++i) { 8 | float x = buffer[i]; 9 | bool silence = false; 10 | if (std::isnan(x)) { 11 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 12 | silence = true; 13 | } else if (std::isinf(x)) { 14 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 15 | silence = true; 16 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 17 | DBG("!!! WARNING: sample out of range, silencing !!!"); 18 | silence = true; 19 | } else if (x < -1.0f) { 20 | if (firstWarning) { 21 | DBG("!!! WARNING: sample out of range, clamping !!!"); 22 | firstWarning = false; 23 | } 24 | buffer[i] = -1.0f; 25 | } else if (x > 1.0f) { 26 | if (firstWarning) { 27 | DBG("!!! WARNING: sample out of range, clamping !!!"); 28 | firstWarning = false; 29 | } 30 | buffer[i] = 1.0f; 31 | } 32 | if (silence) { 33 | memset(buffer, 0, sampleCount * sizeof(float)); 34 | return; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 4/Source/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Voice 4 | { 5 | int note; 6 | int velocity; 7 | 8 | void reset() 9 | { 10 | note = 0; 11 | velocity = 0; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 5/Source/NoiseGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class NoiseGenerator 4 | { 5 | public: 6 | void reset() 7 | { 8 | noiseSeed = 22222; 9 | } 10 | 11 | float nextValue() 12 | { 13 | // Generate the next integer pseudorandom number. 14 | noiseSeed = noiseSeed * 196314165 + 907633515; 15 | 16 | // Convert to a signed value. 17 | int temp = int(noiseSeed >> 7) - 16777216; 18 | 19 | // Convert to a floating-point number between -1.0 and 1.0. 20 | return float(temp) / 16777216.0f; 21 | } 22 | 23 | private: 24 | unsigned int noiseSeed; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 5/Source/Oscillator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const float TWO_PI = 6.2831853071795864f; 4 | 5 | class Oscillator 6 | { 7 | public: 8 | float amplitude; 9 | float inc; 10 | float phase; 11 | 12 | void reset() 13 | { 14 | phase = 0.0f; 15 | sin0 = amplitude * std::sin(phase * TWO_PI); 16 | sin1 = amplitude * std::sin((phase - inc) * TWO_PI); 17 | dsin = 2.0f * std::cos(inc * TWO_PI); 18 | } 19 | 20 | float nextSample() 21 | { 22 | float sinx = dsin * sin0 - sin1; 23 | sin1 = sin0; 24 | sin0 = sinx; 25 | return sinx; 26 | } 27 | 28 | private: 29 | float sin0; 30 | float sin1; 31 | float dsin; 32 | }; 33 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 5/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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 5/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 JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 5/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 "Synth.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class JX11AudioProcessor : public juce::AudioProcessor 18 | { 19 | public: 20 | //============================================================================== 21 | JX11AudioProcessor(); 22 | ~JX11AudioProcessor() override; 23 | 24 | //============================================================================== 25 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 26 | void releaseResources() override; 27 | void reset() 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 | private: 59 | void splitBufferByEvents(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages); 60 | void handleMIDI(uint8_t data0, uint8_t data1, uint8_t data2); 61 | void render(juce::AudioBuffer& buffer, int sampleCount, int bufferOffset); 62 | 63 | private: 64 | Synth synth; 65 | 66 | //============================================================================== 67 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessor) 68 | }; 69 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 5/Source/Synth.cpp: -------------------------------------------------------------------------------- 1 | #include "Synth.h" 2 | #include "Utils.h" 3 | 4 | Synth::Synth() 5 | { 6 | sampleRate = 44100.0f; 7 | } 8 | 9 | void Synth::allocateResources(double sampleRate_, int /*samplesPerBlock*/) 10 | { 11 | sampleRate = static_cast(sampleRate_); 12 | } 13 | 14 | void Synth::deallocateResources() 15 | { 16 | // do nothing 17 | } 18 | 19 | void Synth::reset() 20 | { 21 | voice.reset(); 22 | noiseGen.reset(); 23 | } 24 | 25 | void Synth::render(float** outputBuffers, int sampleCount) 26 | { 27 | float* outputBufferLeft = outputBuffers[0]; 28 | float* outputBufferRight = outputBuffers[1]; 29 | 30 | for (int sample = 0; sample < sampleCount; ++sample) { 31 | float noise = noiseGen.nextValue(); 32 | 33 | float output = 0.0f; 34 | 35 | if (voice.note > 0) { 36 | output = voice.render(); 37 | } 38 | 39 | outputBufferLeft[sample] = output; 40 | if (outputBufferRight != nullptr) { 41 | outputBufferRight[sample] = output; 42 | } 43 | } 44 | 45 | protectYourEars(outputBufferLeft, sampleCount); 46 | protectYourEars(outputBufferRight, sampleCount); 47 | } 48 | 49 | void Synth::midiMessage(uint8_t data0, uint8_t data1, uint8_t data2) 50 | { 51 | switch (data0 & 0xF0) { 52 | // Note off 53 | case 0x80: 54 | noteOff(data1 & 0x7F); 55 | break; 56 | 57 | // Note on 58 | case 0x90: { 59 | uint8_t note = data1 & 0x7F; 60 | uint8_t velo = data2 & 0x7F; 61 | if (velo > 0) { 62 | noteOn(note, velo); 63 | } else { 64 | noteOff(note); 65 | } 66 | break; 67 | } 68 | } 69 | } 70 | 71 | void Synth::noteOn(int note, int velocity) 72 | { 73 | voice.note = note; 74 | 75 | float freq = 440.0f * std::exp2(float(note - 69) / 12.0f); 76 | 77 | voice.osc.amplitude = (velocity / 127.0f) * 0.5f; 78 | voice.osc.inc = freq / sampleRate; 79 | voice.osc.reset(); 80 | } 81 | 82 | void Synth::noteOff(int note) 83 | { 84 | if (voice.note == note) { 85 | voice.note = 0; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 5/Source/Synth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Voice.h" 5 | #include "NoiseGenerator.h" 6 | 7 | class Synth 8 | { 9 | public: 10 | Synth(); 11 | 12 | void allocateResources(double sampleRate, int samplesPerBlock); 13 | void deallocateResources(); 14 | void reset(); 15 | void render(float** outputBuffers, int sampleCount); 16 | void midiMessage(uint8_t data0, uint8_t data1, uint8_t data2); 17 | 18 | private: 19 | void noteOn(int note, int velocity); 20 | void noteOff(int note); 21 | 22 | float sampleRate; 23 | Voice voice; 24 | NoiseGenerator noiseGen; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 5/Source/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | inline void protectYourEars(float* buffer, int sampleCount) 4 | { 5 | if (buffer == nullptr) { return; } 6 | bool firstWarning = true; 7 | for (int i = 0; i < sampleCount; ++i) { 8 | float x = buffer[i]; 9 | bool silence = false; 10 | if (std::isnan(x)) { 11 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 12 | silence = true; 13 | } else if (std::isinf(x)) { 14 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 15 | silence = true; 16 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 17 | DBG("!!! WARNING: sample out of range, silencing !!!"); 18 | silence = true; 19 | } else if (x < -1.0f) { 20 | if (firstWarning) { 21 | DBG("!!! WARNING: sample out of range, clamping !!!"); 22 | firstWarning = false; 23 | } 24 | buffer[i] = -1.0f; 25 | } else if (x > 1.0f) { 26 | if (firstWarning) { 27 | DBG("!!! WARNING: sample out of range, clamping !!!"); 28 | firstWarning = false; 29 | } 30 | buffer[i] = 1.0f; 31 | } 32 | if (silence) { 33 | memset(buffer, 0, sampleCount * sizeof(float)); 34 | return; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 5/Source/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Oscillator.h" 4 | 5 | struct Voice 6 | { 7 | int note; 8 | Oscillator osc; 9 | 10 | void reset() 11 | { 12 | note = 0; 13 | osc.reset(); 14 | } 15 | 16 | float render() 17 | { 18 | return osc.nextSample(); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 6/Source/NoiseGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class NoiseGenerator 4 | { 5 | public: 6 | void reset() 7 | { 8 | noiseSeed = 22222; 9 | } 10 | 11 | float nextValue() 12 | { 13 | // Generate the next integer pseudorandom number. 14 | noiseSeed = noiseSeed * 196314165 + 907633515; 15 | 16 | // Convert to a signed value. 17 | int temp = int(noiseSeed >> 7) - 16777216; 18 | 19 | // Convert to a floating-point number between -1.0 and 1.0. 20 | return float(temp) / 16777216.0f; 21 | } 22 | 23 | private: 24 | unsigned int noiseSeed; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 6/Source/Oscillator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const float PI_OVER_4 = 0.7853981633974483f; 6 | const float PI = 3.1415926535897932f; 7 | const float TWO_PI = 6.2831853071795864f; 8 | 9 | class Oscillator 10 | { 11 | public: 12 | float period = 0.0f; 13 | float amplitude = 1.0f; 14 | 15 | void reset() 16 | { 17 | inc = 0.0f; 18 | phase = 0.0f; 19 | sin0 = 0.0f; 20 | sin1 = 0.0f; 21 | dsin = 0.0f; 22 | dc = 0.0f; 23 | } 24 | 25 | float nextSample() 26 | { 27 | float output = 0.0f; 28 | 29 | phase += inc; 30 | if (phase <= PI_OVER_4) { 31 | float halfPeriod = period / 2.0f; 32 | phaseMax = std::floor(0.5f + halfPeriod) - 0.5f; 33 | 34 | dc = 0.5f * amplitude / phaseMax; 35 | phaseMax *= PI; 36 | 37 | inc = phaseMax / halfPeriod; 38 | phase = -phase; 39 | 40 | sin0 = amplitude * std::sin(phase); 41 | sin1 = amplitude * std::sin(phase - inc); 42 | dsin = 2.0f * std::cos(inc); 43 | 44 | if (phase*phase > 1e-9) { 45 | output = sin0 / phase; 46 | } else { 47 | output = amplitude; 48 | } 49 | } else { 50 | if (phase > phaseMax) { 51 | phase = phaseMax + phaseMax - phase; 52 | inc = -inc; 53 | } 54 | 55 | float sinp = dsin * sin0 - sin1; 56 | sin1 = sin0; 57 | sin0 = sinp; 58 | 59 | output = sinp / phase; 60 | } 61 | 62 | return output - dc; 63 | } 64 | 65 | private: 66 | float phase; 67 | float phaseMax; 68 | float inc; 69 | 70 | float sin0; 71 | float sin1; 72 | float dsin; 73 | 74 | float dc; 75 | }; 76 | -------------------------------------------------------------------------------- /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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /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 JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 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 | #include "Synth.h" 13 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class JX11AudioProcessor : public juce::AudioProcessor 18 | { 19 | public: 20 | //============================================================================== 21 | JX11AudioProcessor(); 22 | ~JX11AudioProcessor() override; 23 | 24 | //============================================================================== 25 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 26 | void releaseResources() override; 27 | void reset() 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 | private: 59 | void splitBufferByEvents(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages); 60 | void handleMIDI(uint8_t data0, uint8_t data1, uint8_t data2); 61 | void render(juce::AudioBuffer& buffer, int sampleCount, int bufferOffset); 62 | 63 | private: 64 | Synth synth; 65 | 66 | //============================================================================== 67 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessor) 68 | }; 69 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 6/Source/Synth.cpp: -------------------------------------------------------------------------------- 1 | #include "Synth.h" 2 | #include "Utils.h" 3 | 4 | Synth::Synth() 5 | { 6 | sampleRate = 44100.0f; 7 | } 8 | 9 | void Synth::allocateResources(double sampleRate_, int /*samplesPerBlock*/) 10 | { 11 | sampleRate = static_cast(sampleRate_); 12 | } 13 | 14 | void Synth::deallocateResources() 15 | { 16 | // do nothing 17 | } 18 | 19 | void Synth::reset() 20 | { 21 | voice.reset(); 22 | noiseGen.reset(); 23 | } 24 | 25 | void Synth::render(float** outputBuffers, int sampleCount) 26 | { 27 | float* outputBufferLeft = outputBuffers[0]; 28 | float* outputBufferRight = outputBuffers[1]; 29 | 30 | for (int sample = 0; sample < sampleCount; ++sample) { 31 | float noise = noiseGen.nextValue(); 32 | 33 | float output = 0.0f; 34 | 35 | if (voice.note > 0) { 36 | output = voice.render(); 37 | } 38 | 39 | outputBufferLeft[sample] = output; 40 | if (outputBufferRight != nullptr) { 41 | outputBufferRight[sample] = output; 42 | } 43 | } 44 | 45 | protectYourEars(outputBufferLeft, sampleCount); 46 | protectYourEars(outputBufferRight, sampleCount); 47 | } 48 | 49 | void Synth::midiMessage(uint8_t data0, uint8_t data1, uint8_t data2) 50 | { 51 | switch (data0 & 0xF0) { 52 | // Note off 53 | case 0x80: 54 | noteOff(data1 & 0x7F); 55 | break; 56 | 57 | // Note on 58 | case 0x90: { 59 | uint8_t note = data1 & 0x7F; 60 | uint8_t velo = data2 & 0x7F; 61 | if (velo > 0) { 62 | noteOn(note, velo); 63 | } else { 64 | noteOff(note); 65 | } 66 | break; 67 | } 68 | } 69 | } 70 | 71 | void Synth::noteOn(int note, int velocity) 72 | { 73 | voice.note = note; 74 | 75 | float freq = 440.0f * std::exp2(float(note - 69) / 12.0f); 76 | 77 | voice.osc.amplitude = (velocity / 127.0f) * 0.5f; 78 | voice.osc.period = sampleRate / freq; 79 | voice.osc.reset(); 80 | } 81 | 82 | void Synth::noteOff(int note) 83 | { 84 | if (voice.note == note) { 85 | voice.note = 0; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 6/Source/Synth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Voice.h" 5 | #include "NoiseGenerator.h" 6 | 7 | class Synth 8 | { 9 | public: 10 | Synth(); 11 | 12 | void allocateResources(double sampleRate, int samplesPerBlock); 13 | void deallocateResources(); 14 | void reset(); 15 | void render(float** outputBuffers, int sampleCount); 16 | void midiMessage(uint8_t data0, uint8_t data1, uint8_t data2); 17 | 18 | private: 19 | void noteOn(int note, int velocity); 20 | void noteOff(int note); 21 | 22 | float sampleRate; 23 | Voice voice; 24 | NoiseGenerator noiseGen; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 6/Source/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | inline void protectYourEars(float* buffer, int sampleCount) 4 | { 5 | if (buffer == nullptr) { return; } 6 | bool firstWarning = true; 7 | for (int i = 0; i < sampleCount; ++i) { 8 | float x = buffer[i]; 9 | bool silence = false; 10 | if (std::isnan(x)) { 11 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 12 | silence = true; 13 | } else if (std::isinf(x)) { 14 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 15 | silence = true; 16 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 17 | DBG("!!! WARNING: sample out of range, silencing !!!"); 18 | silence = true; 19 | } else if (x < -1.0f) { 20 | if (firstWarning) { 21 | DBG("!!! WARNING: sample out of range, clamping !!!"); 22 | firstWarning = false; 23 | } 24 | buffer[i] = -1.0f; 25 | } else if (x > 1.0f) { 26 | if (firstWarning) { 27 | DBG("!!! WARNING: sample out of range, clamping !!!"); 28 | firstWarning = false; 29 | } 30 | buffer[i] = 1.0f; 31 | } 32 | if (silence) { 33 | memset(buffer, 0, sampleCount * sizeof(float)); 34 | return; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 6/Source/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Oscillator.h" 4 | 5 | struct Voice 6 | { 7 | int note; 8 | float saw; 9 | Oscillator osc; 10 | 11 | void reset() 12 | { 13 | note = 0; 14 | saw = 0.0f; 15 | osc.reset(); 16 | } 17 | 18 | float render() 19 | { 20 | float sample = osc.nextSample(); 21 | saw = saw * 0.997f + sample; 22 | return saw; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 7/Source/NoiseGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class NoiseGenerator 4 | { 5 | public: 6 | void reset() 7 | { 8 | noiseSeed = 22222; 9 | } 10 | 11 | float nextValue() 12 | { 13 | // Generate the next integer pseudorandom number. 14 | noiseSeed = noiseSeed * 196314165 + 907633515; 15 | 16 | // Convert to a signed value. 17 | int temp = int(noiseSeed >> 7) - 16777216; 18 | 19 | // Convert to a floating-point number between -1.0 and 1.0. 20 | return float(temp) / 16777216.0f; 21 | } 22 | 23 | private: 24 | unsigned int noiseSeed; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 7/Source/Oscillator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const float PI_OVER_4 = 0.7853981633974483f; 6 | const float PI = 3.1415926535897932f; 7 | const float TWO_PI = 6.2831853071795864f; 8 | 9 | class Oscillator 10 | { 11 | public: 12 | float period = 0.0f; 13 | float amplitude = 1.0f; 14 | 15 | void reset() 16 | { 17 | inc = 0.0f; 18 | phase = 0.0f; 19 | sin0 = 0.0f; 20 | sin1 = 0.0f; 21 | dsin = 0.0f; 22 | dc = 0.0f; 23 | } 24 | 25 | float nextSample() 26 | { 27 | float output = 0.0f; 28 | 29 | phase += inc; 30 | if (phase <= PI_OVER_4) { 31 | float halfPeriod = period / 2.0f; 32 | phaseMax = std::floor(0.5f + halfPeriod) - 0.5f; 33 | 34 | dc = 0.5f * amplitude / phaseMax; 35 | phaseMax *= PI; 36 | 37 | inc = phaseMax / halfPeriod; 38 | phase = -phase; 39 | 40 | sin0 = amplitude * std::sin(phase); 41 | sin1 = amplitude * std::sin(phase - inc); 42 | dsin = 2.0f * std::cos(inc); 43 | 44 | if (phase*phase > 1e-9) { 45 | output = sin0 / phase; 46 | } else { 47 | output = amplitude; 48 | } 49 | } else { 50 | if (phase > phaseMax) { 51 | phase = phaseMax + phaseMax - phase; 52 | inc = -inc; 53 | } 54 | 55 | float sinp = dsin * sin0 - sin1; 56 | sin1 = sin0; 57 | sin0 = sinp; 58 | 59 | output = sinp / phase; 60 | } 61 | 62 | return output - dc; 63 | } 64 | 65 | private: 66 | float phase; 67 | float phaseMax; 68 | float inc; 69 | 70 | float sin0; 71 | float sin1; 72 | float dsin; 73 | 74 | float dc; 75 | }; 76 | -------------------------------------------------------------------------------- /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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /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 JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 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 | #include "Synth.h" 13 | #include "Preset.h" 14 | 15 | namespace ParameterID 16 | { 17 | #define PARAMETER_ID(str) const juce::ParameterID str(#str, 1); 18 | 19 | PARAMETER_ID(oscMix) 20 | PARAMETER_ID(oscTune) 21 | PARAMETER_ID(oscFine) 22 | PARAMETER_ID(glideMode) 23 | PARAMETER_ID(glideRate) 24 | PARAMETER_ID(glideBend) 25 | PARAMETER_ID(filterFreq) 26 | PARAMETER_ID(filterReso) 27 | PARAMETER_ID(filterEnv) 28 | PARAMETER_ID(filterLFO) 29 | PARAMETER_ID(filterVelocity) 30 | PARAMETER_ID(filterAttack) 31 | PARAMETER_ID(filterDecay) 32 | PARAMETER_ID(filterSustain) 33 | PARAMETER_ID(filterRelease) 34 | PARAMETER_ID(envAttack) 35 | PARAMETER_ID(envDecay) 36 | PARAMETER_ID(envSustain) 37 | PARAMETER_ID(envRelease) 38 | PARAMETER_ID(lfoRate) 39 | PARAMETER_ID(vibrato) 40 | PARAMETER_ID(noise) 41 | PARAMETER_ID(octave) 42 | PARAMETER_ID(tuning) 43 | PARAMETER_ID(outputLevel) 44 | PARAMETER_ID(polyMode) 45 | 46 | #undef PARAMETER_ID 47 | } 48 | 49 | //============================================================================== 50 | /** 51 | */ 52 | class JX11AudioProcessor : public juce::AudioProcessor, 53 | private juce::ValueTree::Listener 54 | { 55 | public: 56 | //============================================================================== 57 | JX11AudioProcessor(); 58 | ~JX11AudioProcessor() override; 59 | 60 | //============================================================================== 61 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 62 | void releaseResources() override; 63 | void reset() override; 64 | 65 | #ifndef JucePlugin_PreferredChannelConfigurations 66 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 67 | #endif 68 | 69 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 70 | 71 | //============================================================================== 72 | juce::AudioProcessorEditor* createEditor() override; 73 | bool hasEditor() const override; 74 | 75 | //============================================================================== 76 | const juce::String getName() const override; 77 | 78 | bool acceptsMidi() const override; 79 | bool producesMidi() const override; 80 | bool isMidiEffect() const override; 81 | double getTailLengthSeconds() const override; 82 | 83 | //============================================================================== 84 | int getNumPrograms() override; 85 | int getCurrentProgram() override; 86 | void setCurrentProgram (int index) override; 87 | const juce::String getProgramName (int index) override; 88 | void changeProgramName (int index, const juce::String& newName) override; 89 | 90 | //============================================================================== 91 | void getStateInformation (juce::MemoryBlock& destData) override; 92 | void setStateInformation (const void* data, int sizeInBytes) override; 93 | 94 | juce::AudioProcessorValueTreeState apvts { *this, nullptr, "Parameters", createParameterLayout() }; 95 | 96 | private: 97 | juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 98 | 99 | void valueTreePropertyChanged(juce::ValueTree&, const juce::Identifier&) override 100 | { 101 | parametersChanged.store(true); 102 | } 103 | 104 | std::atomic parametersChanged { false }; 105 | 106 | void update(); 107 | void createPrograms(); 108 | 109 | void splitBufferByEvents(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages); 110 | void handleMIDI(uint8_t data0, uint8_t data1, uint8_t data2); 111 | void render(juce::AudioBuffer& buffer, int sampleCount, int bufferOffset); 112 | 113 | std::vector presets; 114 | int currentProgram; 115 | 116 | Synth synth; 117 | 118 | juce::AudioParameterFloat* oscMixParam; 119 | juce::AudioParameterFloat* oscTuneParam; 120 | juce::AudioParameterFloat* oscFineParam; 121 | juce::AudioParameterChoice* glideModeParam; 122 | juce::AudioParameterFloat* glideRateParam; 123 | juce::AudioParameterFloat* glideBendParam; 124 | juce::AudioParameterFloat* filterFreqParam; 125 | juce::AudioParameterFloat* filterResoParam; 126 | juce::AudioParameterFloat* filterEnvParam; 127 | juce::AudioParameterFloat* filterLFOParam; 128 | juce::AudioParameterFloat* filterVelocityParam; 129 | juce::AudioParameterFloat* filterAttackParam; 130 | juce::AudioParameterFloat* filterDecayParam; 131 | juce::AudioParameterFloat* filterSustainParam; 132 | juce::AudioParameterFloat* filterReleaseParam; 133 | juce::AudioParameterFloat* envAttackParam; 134 | juce::AudioParameterFloat* envDecayParam; 135 | juce::AudioParameterFloat* envSustainParam; 136 | juce::AudioParameterFloat* envReleaseParam; 137 | juce::AudioParameterFloat* lfoRateParam; 138 | juce::AudioParameterFloat* vibratoParam; 139 | juce::AudioParameterFloat* noiseParam; 140 | juce::AudioParameterFloat* octaveParam; 141 | juce::AudioParameterFloat* tuningParam; 142 | juce::AudioParameterFloat* outputLevelParam; 143 | juce::AudioParameterChoice* polyModeParam; 144 | 145 | //============================================================================== 146 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessor) 147 | }; 148 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 7/Source/Preset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const int NUM_PARAMS = 26; 6 | 7 | struct Preset 8 | { 9 | Preset(const char* name, 10 | float p0, float p1, float p2, float p3, 11 | float p4, float p5, float p6, float p7, 12 | float p8, float p9, float p10, float p11, 13 | float p12, float p13, float p14, float p15, 14 | float p16, float p17, float p18, float p19, 15 | float p20, float p21, float p22, float p23, 16 | float p24, float p25) 17 | { 18 | strcpy(this->name, name); 19 | param[0] = p0; // Osc Mix 20 | param[1] = p1; // Osc Tune 21 | param[2] = p2; // Osc Fine 22 | param[3] = p3; // Glide Mode 23 | param[4] = p4; // Glide Rate 24 | param[5] = p5; // Glide Bend 25 | param[6] = p6; // Filter Freq 26 | param[7] = p7; // Filter Reso 27 | param[8] = p8; // Filter Env 28 | param[9] = p9; // Filter LFO 29 | param[10] = p10; // Velocity 30 | param[11] = p11; // Filter Attack 31 | param[12] = p12; // Filter Decay 32 | param[13] = p13; // Filter Sustain 33 | param[14] = p14; // Filter Release 34 | param[15] = p15; // Env Attack 35 | param[16] = p16; // Env Decay 36 | param[17] = p17; // Env Sustain 37 | param[18] = p18; // Env Release 38 | param[19] = p19; // LFO Rate 39 | param[20] = p20; // Vibrato 40 | param[21] = p21; // Noise 41 | param[22] = p22; // Octave 42 | param[23] = p23; // Tuning 43 | param[24] = p24; // Output Level 44 | param[25] = p25; // Polyphony 45 | } 46 | 47 | char name[40]; 48 | float param[NUM_PARAMS]; 49 | }; 50 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 7/Source/Synth.cpp: -------------------------------------------------------------------------------- 1 | #include "Synth.h" 2 | #include "Utils.h" 3 | 4 | Synth::Synth() 5 | { 6 | sampleRate = 44100.0f; 7 | } 8 | 9 | void Synth::allocateResources(double sampleRate_, int /*samplesPerBlock*/) 10 | { 11 | sampleRate = static_cast(sampleRate_); 12 | } 13 | 14 | void Synth::deallocateResources() 15 | { 16 | // do nothing 17 | } 18 | 19 | void Synth::reset() 20 | { 21 | voice.reset(); 22 | noiseGen.reset(); 23 | } 24 | 25 | void Synth::render(float** outputBuffers, int sampleCount) 26 | { 27 | float* outputBufferLeft = outputBuffers[0]; 28 | float* outputBufferRight = outputBuffers[1]; 29 | 30 | for (int sample = 0; sample < sampleCount; ++sample) { 31 | float noise = noiseGen.nextValue() * noiseMix; 32 | 33 | float output = 0.0f; 34 | 35 | if (voice.note > 0) { 36 | output = voice.render() + noise; 37 | } 38 | 39 | outputBufferLeft[sample] = output; 40 | if (outputBufferRight != nullptr) { 41 | outputBufferRight[sample] = output; 42 | } 43 | } 44 | 45 | protectYourEars(outputBufferLeft, sampleCount); 46 | protectYourEars(outputBufferRight, sampleCount); 47 | } 48 | 49 | void Synth::midiMessage(uint8_t data0, uint8_t data1, uint8_t data2) 50 | { 51 | switch (data0 & 0xF0) { 52 | // Note off 53 | case 0x80: 54 | noteOff(data1 & 0x7F); 55 | break; 56 | 57 | // Note on 58 | case 0x90: { 59 | uint8_t note = data1 & 0x7F; 60 | uint8_t velo = data2 & 0x7F; 61 | if (velo > 0) { 62 | noteOn(note, velo); 63 | } else { 64 | noteOff(note); 65 | } 66 | break; 67 | } 68 | } 69 | } 70 | 71 | void Synth::noteOn(int note, int velocity) 72 | { 73 | voice.note = note; 74 | 75 | float freq = 440.0f * std::exp2(float(note - 69) / 12.0f); 76 | 77 | voice.osc.amplitude = (velocity / 127.0f) * 0.5f; 78 | voice.osc.period = sampleRate / freq; 79 | voice.osc.reset(); 80 | } 81 | 82 | void Synth::noteOff(int note) 83 | { 84 | if (voice.note == note) { 85 | voice.note = 0; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 7/Source/Synth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Voice.h" 5 | #include "NoiseGenerator.h" 6 | 7 | class Synth 8 | { 9 | public: 10 | Synth(); 11 | 12 | void allocateResources(double sampleRate, int samplesPerBlock); 13 | void deallocateResources(); 14 | void reset(); 15 | void render(float** outputBuffers, int sampleCount); 16 | void midiMessage(uint8_t data0, uint8_t data1, uint8_t data2); 17 | 18 | float noiseMix; 19 | 20 | private: 21 | void noteOn(int note, int velocity); 22 | void noteOff(int note); 23 | 24 | float sampleRate; 25 | Voice voice; 26 | NoiseGenerator noiseGen; 27 | }; 28 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 7/Source/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | inline void protectYourEars(float* buffer, int sampleCount) 4 | { 5 | if (buffer == nullptr) { return; } 6 | bool firstWarning = true; 7 | for (int i = 0; i < sampleCount; ++i) { 8 | float x = buffer[i]; 9 | bool silence = false; 10 | if (std::isnan(x)) { 11 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 12 | silence = true; 13 | } else if (std::isinf(x)) { 14 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 15 | silence = true; 16 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 17 | DBG("!!! WARNING: sample out of range, silencing !!!"); 18 | silence = true; 19 | } else if (x < -1.0f) { 20 | if (firstWarning) { 21 | DBG("!!! WARNING: sample out of range, clamping !!!"); 22 | firstWarning = false; 23 | } 24 | buffer[i] = -1.0f; 25 | } else if (x > 1.0f) { 26 | if (firstWarning) { 27 | DBG("!!! WARNING: sample out of range, clamping !!!"); 28 | firstWarning = false; 29 | } 30 | buffer[i] = 1.0f; 31 | } 32 | if (silence) { 33 | memset(buffer, 0, sampleCount * sizeof(float)); 34 | return; 35 | } 36 | } 37 | } 38 | 39 | template 40 | inline static void castParameter(juce::AudioProcessorValueTreeState& apvts, const juce::ParameterID& id, T& destination) 41 | { 42 | destination = dynamic_cast(apvts.getParameter(id.getParamID())); 43 | jassert(destination); // parameter does not exist or wrong type 44 | } 45 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 7/Source/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Oscillator.h" 4 | 5 | struct Voice 6 | { 7 | int note; 8 | float saw; 9 | Oscillator osc; 10 | 11 | void reset() 12 | { 13 | note = 0; 14 | saw = 0.0f; 15 | osc.reset(); 16 | } 17 | 18 | float render() 19 | { 20 | float sample = osc.nextSample(); 21 | saw = saw * 0.997f + sample; 22 | return saw; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/Envelope.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const float SILENCE = 0.0001f; 4 | 5 | class Envelope 6 | { 7 | public: 8 | void reset() 9 | { 10 | level = 0.0f; 11 | target = 0.0f; 12 | multiplier = 0.0f; 13 | } 14 | 15 | float nextValue() 16 | { 17 | level = multiplier * (level - target) + target; 18 | 19 | if (level + target > 3.0f) { 20 | multiplier = decayMultiplier; 21 | target = sustainLevel; 22 | } 23 | 24 | return level; 25 | } 26 | 27 | inline bool isActive() const 28 | { 29 | return level > SILENCE; 30 | } 31 | 32 | inline bool isInAttack() const 33 | { 34 | return target >= 2.0f; 35 | } 36 | 37 | void attack() 38 | { 39 | level += SILENCE + SILENCE; 40 | target = 2.0f; 41 | multiplier = attackMultiplier; 42 | } 43 | 44 | void release() 45 | { 46 | target = 0.0f; 47 | multiplier = releaseMultiplier; 48 | } 49 | 50 | float attackMultiplier; 51 | float decayMultiplier; 52 | float sustainLevel; 53 | float releaseMultiplier; 54 | 55 | float level; 56 | 57 | private: 58 | float target; 59 | float multiplier; 60 | }; 61 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/NoiseGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class NoiseGenerator 4 | { 5 | public: 6 | void reset() 7 | { 8 | noiseSeed = 22222; 9 | } 10 | 11 | float nextValue() 12 | { 13 | // Generate the next integer pseudorandom number. 14 | noiseSeed = noiseSeed * 196314165 + 907633515; 15 | 16 | // Convert to a signed value. 17 | int temp = int(noiseSeed >> 7) - 16777216; 18 | 19 | // Convert to a floating-point number between -1.0 and 1.0. 20 | return float(temp) / 16777216.0f; 21 | } 22 | 23 | private: 24 | unsigned int noiseSeed; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/Oscillator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const float PI_OVER_4 = 0.7853981633974483f; 6 | const float PI = 3.1415926535897932f; 7 | const float TWO_PI = 6.2831853071795864f; 8 | 9 | class Oscillator 10 | { 11 | public: 12 | float period = 0.0f; 13 | float amplitude = 1.0f; 14 | 15 | void reset() 16 | { 17 | inc = 0.0f; 18 | phase = 0.0f; 19 | sin0 = 0.0f; 20 | sin1 = 0.0f; 21 | dsin = 0.0f; 22 | dc = 0.0f; 23 | } 24 | 25 | float nextSample() 26 | { 27 | float output = 0.0f; 28 | 29 | phase += inc; 30 | if (phase <= PI_OVER_4) { 31 | float halfPeriod = period / 2.0f; 32 | phaseMax = std::floor(0.5f + halfPeriod) - 0.5f; 33 | 34 | dc = 0.5f * amplitude / phaseMax; 35 | phaseMax *= PI; 36 | 37 | inc = phaseMax / halfPeriod; 38 | phase = -phase; 39 | 40 | sin0 = amplitude * std::sin(phase); 41 | sin1 = amplitude * std::sin(phase - inc); 42 | dsin = 2.0f * std::cos(inc); 43 | 44 | if (phase*phase > 1e-9) { 45 | output = sin0 / phase; 46 | } else { 47 | output = amplitude; 48 | } 49 | } else { 50 | if (phase > phaseMax) { 51 | phase = phaseMax + phaseMax - phase; 52 | inc = -inc; 53 | } 54 | 55 | float sinp = dsin * sin0 - sin1; 56 | sin1 = sin0; 57 | sin0 = sinp; 58 | 59 | output = sinp / phase; 60 | } 61 | 62 | return output - dc; 63 | } 64 | 65 | private: 66 | float phase; 67 | float phaseMax; 68 | float inc; 69 | 70 | float sin0; 71 | float sin1; 72 | float dsin; 73 | 74 | float dc; 75 | }; 76 | -------------------------------------------------------------------------------- /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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /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 JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 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 "Synth.h" 13 | #include "Preset.h" 14 | 15 | namespace ParameterID 16 | { 17 | #define PARAMETER_ID(str) const juce::ParameterID str(#str, 1); 18 | 19 | PARAMETER_ID(oscMix) 20 | PARAMETER_ID(oscTune) 21 | PARAMETER_ID(oscFine) 22 | PARAMETER_ID(glideMode) 23 | PARAMETER_ID(glideRate) 24 | PARAMETER_ID(glideBend) 25 | PARAMETER_ID(filterFreq) 26 | PARAMETER_ID(filterReso) 27 | PARAMETER_ID(filterEnv) 28 | PARAMETER_ID(filterLFO) 29 | PARAMETER_ID(filterVelocity) 30 | PARAMETER_ID(filterAttack) 31 | PARAMETER_ID(filterDecay) 32 | PARAMETER_ID(filterSustain) 33 | PARAMETER_ID(filterRelease) 34 | PARAMETER_ID(envAttack) 35 | PARAMETER_ID(envDecay) 36 | PARAMETER_ID(envSustain) 37 | PARAMETER_ID(envRelease) 38 | PARAMETER_ID(lfoRate) 39 | PARAMETER_ID(vibrato) 40 | PARAMETER_ID(noise) 41 | PARAMETER_ID(octave) 42 | PARAMETER_ID(tuning) 43 | PARAMETER_ID(outputLevel) 44 | PARAMETER_ID(polyMode) 45 | 46 | #undef PARAMETER_ID 47 | } 48 | 49 | //============================================================================== 50 | /** 51 | */ 52 | class JX11AudioProcessor : public juce::AudioProcessor, 53 | private juce::ValueTree::Listener 54 | { 55 | public: 56 | //============================================================================== 57 | JX11AudioProcessor(); 58 | ~JX11AudioProcessor() override; 59 | 60 | //============================================================================== 61 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 62 | void releaseResources() override; 63 | void reset() override; 64 | 65 | #ifndef JucePlugin_PreferredChannelConfigurations 66 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 67 | #endif 68 | 69 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 70 | 71 | //============================================================================== 72 | juce::AudioProcessorEditor* createEditor() override; 73 | bool hasEditor() const override; 74 | 75 | //============================================================================== 76 | const juce::String getName() const override; 77 | 78 | bool acceptsMidi() const override; 79 | bool producesMidi() const override; 80 | bool isMidiEffect() const override; 81 | double getTailLengthSeconds() const override; 82 | 83 | //============================================================================== 84 | int getNumPrograms() override; 85 | int getCurrentProgram() override; 86 | void setCurrentProgram (int index) override; 87 | const juce::String getProgramName (int index) override; 88 | void changeProgramName (int index, const juce::String& newName) override; 89 | 90 | //============================================================================== 91 | void getStateInformation (juce::MemoryBlock& destData) override; 92 | void setStateInformation (const void* data, int sizeInBytes) override; 93 | 94 | juce::AudioProcessorValueTreeState apvts { *this, nullptr, "Parameters", createParameterLayout() }; 95 | 96 | private: 97 | juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 98 | 99 | void valueTreePropertyChanged(juce::ValueTree&, const juce::Identifier&) override 100 | { 101 | parametersChanged.store(true); 102 | } 103 | 104 | std::atomic parametersChanged { false }; 105 | 106 | void update(); 107 | void createPrograms(); 108 | 109 | void splitBufferByEvents(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages); 110 | void handleMIDI(uint8_t data0, uint8_t data1, uint8_t data2); 111 | void render(juce::AudioBuffer& buffer, int sampleCount, int bufferOffset); 112 | 113 | std::vector presets; 114 | int currentProgram; 115 | 116 | Synth synth; 117 | 118 | juce::AudioParameterFloat* oscMixParam; 119 | juce::AudioParameterFloat* oscTuneParam; 120 | juce::AudioParameterFloat* oscFineParam; 121 | juce::AudioParameterChoice* glideModeParam; 122 | juce::AudioParameterFloat* glideRateParam; 123 | juce::AudioParameterFloat* glideBendParam; 124 | juce::AudioParameterFloat* filterFreqParam; 125 | juce::AudioParameterFloat* filterResoParam; 126 | juce::AudioParameterFloat* filterEnvParam; 127 | juce::AudioParameterFloat* filterLFOParam; 128 | juce::AudioParameterFloat* filterVelocityParam; 129 | juce::AudioParameterFloat* filterAttackParam; 130 | juce::AudioParameterFloat* filterDecayParam; 131 | juce::AudioParameterFloat* filterSustainParam; 132 | juce::AudioParameterFloat* filterReleaseParam; 133 | juce::AudioParameterFloat* envAttackParam; 134 | juce::AudioParameterFloat* envDecayParam; 135 | juce::AudioParameterFloat* envSustainParam; 136 | juce::AudioParameterFloat* envReleaseParam; 137 | juce::AudioParameterFloat* lfoRateParam; 138 | juce::AudioParameterFloat* vibratoParam; 139 | juce::AudioParameterFloat* noiseParam; 140 | juce::AudioParameterFloat* octaveParam; 141 | juce::AudioParameterFloat* tuningParam; 142 | juce::AudioParameterFloat* outputLevelParam; 143 | juce::AudioParameterChoice* polyModeParam; 144 | 145 | //============================================================================== 146 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessor) 147 | }; 148 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/Preset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const int NUM_PARAMS = 26; 6 | 7 | struct Preset 8 | { 9 | Preset(const char* name, 10 | float p0, float p1, float p2, float p3, 11 | float p4, float p5, float p6, float p7, 12 | float p8, float p9, float p10, float p11, 13 | float p12, float p13, float p14, float p15, 14 | float p16, float p17, float p18, float p19, 15 | float p20, float p21, float p22, float p23, 16 | float p24, float p25) 17 | { 18 | strcpy(this->name, name); 19 | param[0] = p0; // Osc Mix 20 | param[1] = p1; // Osc Tune 21 | param[2] = p2; // Osc Fine 22 | param[3] = p3; // Glide Mode 23 | param[4] = p4; // Glide Rate 24 | param[5] = p5; // Glide Bend 25 | param[6] = p6; // Filter Freq 26 | param[7] = p7; // Filter Reso 27 | param[8] = p8; // Filter Env 28 | param[9] = p9; // Filter LFO 29 | param[10] = p10; // Velocity 30 | param[11] = p11; // Filter Attack 31 | param[12] = p12; // Filter Decay 32 | param[13] = p13; // Filter Sustain 33 | param[14] = p14; // Filter Release 34 | param[15] = p15; // Env Attack 35 | param[16] = p16; // Env Decay 36 | param[17] = p17; // Env Sustain 37 | param[18] = p18; // Env Release 38 | param[19] = p19; // LFO Rate 39 | param[20] = p20; // Vibrato 40 | param[21] = p21; // Noise 41 | param[22] = p22; // Octave 42 | param[23] = p23; // Tuning 43 | param[24] = p24; // Output Level 44 | param[25] = p25; // Polyphony 45 | } 46 | 47 | char name[40]; 48 | float param[NUM_PARAMS]; 49 | }; 50 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/Synth.cpp: -------------------------------------------------------------------------------- 1 | #include "Synth.h" 2 | #include "Utils.h" 3 | 4 | Synth::Synth() 5 | { 6 | sampleRate = 44100.0f; 7 | } 8 | 9 | void Synth::allocateResources(double sampleRate_, int /*samplesPerBlock*/) 10 | { 11 | sampleRate = static_cast(sampleRate_); 12 | } 13 | 14 | void Synth::deallocateResources() 15 | { 16 | // do nothing 17 | } 18 | 19 | void Synth::reset() 20 | { 21 | voice.reset(); 22 | noiseGen.reset(); 23 | } 24 | 25 | void Synth::render(float** outputBuffers, int sampleCount) 26 | { 27 | float* outputBufferLeft = outputBuffers[0]; 28 | float* outputBufferRight = outputBuffers[1]; 29 | 30 | for (int sample = 0; sample < sampleCount; ++sample) { 31 | float noise = noiseGen.nextValue() * noiseMix; 32 | 33 | float output = 0.0f; 34 | 35 | if (voice.env.isActive()) { 36 | output = voice.render(noise); 37 | } 38 | 39 | outputBufferLeft[sample] = output; 40 | if (outputBufferRight != nullptr) { 41 | outputBufferRight[sample] = output; 42 | } 43 | } 44 | 45 | if (!voice.env.isActive()) { 46 | voice.env.reset(); 47 | } 48 | 49 | protectYourEars(outputBufferLeft, sampleCount); 50 | protectYourEars(outputBufferRight, sampleCount); 51 | } 52 | 53 | void Synth::midiMessage(uint8_t data0, uint8_t data1, uint8_t data2) 54 | { 55 | switch (data0 & 0xF0) { 56 | // Note off 57 | case 0x80: 58 | noteOff(data1 & 0x7F); 59 | break; 60 | 61 | // Note on 62 | case 0x90: { 63 | uint8_t note = data1 & 0x7F; 64 | uint8_t velo = data2 & 0x7F; 65 | if (velo > 0) { 66 | noteOn(note, velo); 67 | } else { 68 | noteOff(note); 69 | } 70 | break; 71 | } 72 | } 73 | } 74 | 75 | void Synth::noteOn(int note, int velocity) 76 | { 77 | voice.note = note; 78 | 79 | float freq = 440.0f * std::exp2(float(note - 69) / 12.0f); 80 | 81 | voice.osc.amplitude = (velocity / 127.0f) * 0.5f; 82 | voice.osc.period = sampleRate / freq; 83 | voice.osc.reset(); 84 | 85 | Envelope& env = voice.env; 86 | env.attackMultiplier = envAttack; 87 | env.decayMultiplier = envDecay; 88 | env.sustainLevel = envSustain; 89 | env.releaseMultiplier = envRelease; 90 | env.attack(); 91 | } 92 | 93 | void Synth::noteOff(int note) 94 | { 95 | if (voice.note == note) { 96 | voice.release(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/Synth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Voice.h" 5 | #include "NoiseGenerator.h" 6 | 7 | class Synth 8 | { 9 | public: 10 | Synth(); 11 | 12 | void allocateResources(double sampleRate, int samplesPerBlock); 13 | void deallocateResources(); 14 | void reset(); 15 | void render(float** outputBuffers, int sampleCount); 16 | void midiMessage(uint8_t data0, uint8_t data1, uint8_t data2); 17 | 18 | float noiseMix; 19 | 20 | float envAttack, envDecay, envSustain, envRelease; 21 | 22 | private: 23 | void noteOn(int note, int velocity); 24 | void noteOff(int note); 25 | 26 | float sampleRate; 27 | Voice voice; 28 | NoiseGenerator noiseGen; 29 | }; 30 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | inline void protectYourEars(float* buffer, int sampleCount) 4 | { 5 | if (buffer == nullptr) { return; } 6 | bool firstWarning = true; 7 | for (int i = 0; i < sampleCount; ++i) { 8 | float x = buffer[i]; 9 | bool silence = false; 10 | if (std::isnan(x)) { 11 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 12 | silence = true; 13 | } else if (std::isinf(x)) { 14 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 15 | silence = true; 16 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 17 | DBG("!!! WARNING: sample out of range, silencing !!!"); 18 | silence = true; 19 | } else if (x < -1.0f) { 20 | if (firstWarning) { 21 | DBG("!!! WARNING: sample out of range, clamping !!!"); 22 | firstWarning = false; 23 | } 24 | buffer[i] = -1.0f; 25 | } else if (x > 1.0f) { 26 | if (firstWarning) { 27 | DBG("!!! WARNING: sample out of range, clamping !!!"); 28 | firstWarning = false; 29 | } 30 | buffer[i] = 1.0f; 31 | } 32 | if (silence) { 33 | memset(buffer, 0, sampleCount * sizeof(float)); 34 | return; 35 | } 36 | } 37 | } 38 | 39 | template 40 | inline static void castParameter(juce::AudioProcessorValueTreeState& apvts, const juce::ParameterID& id, T& destination) 41 | { 42 | destination = dynamic_cast(apvts.getParameter(id.getParamID())); 43 | jassert(destination); // parameter does not exist or wrong type 44 | } 45 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 8/Source/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Oscillator.h" 4 | #include "Envelope.h" 5 | 6 | struct Voice 7 | { 8 | int note; 9 | float saw; 10 | 11 | Oscillator osc; 12 | Envelope env; 13 | 14 | void reset() 15 | { 16 | note = 0; 17 | saw = 0.0f; 18 | osc.reset(); 19 | env.reset(); 20 | } 21 | 22 | float render(float input) 23 | { 24 | float sample = osc.nextSample(); 25 | saw = saw * 0.997f + sample; 26 | 27 | float output = saw + input; 28 | 29 | float envelope = env.nextValue(); 30 | return output * envelope; 31 | } 32 | 33 | void release() 34 | { 35 | env.release(); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/Envelope.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const float SILENCE = 0.0001f; 4 | 5 | class Envelope 6 | { 7 | public: 8 | void reset() 9 | { 10 | level = 0.0f; 11 | target = 0.0f; 12 | multiplier = 0.0f; 13 | } 14 | 15 | float nextValue() 16 | { 17 | level = multiplier * (level - target) + target; 18 | 19 | if (level + target > 3.0f) { 20 | multiplier = decayMultiplier; 21 | target = sustainLevel; 22 | } 23 | 24 | return level; 25 | } 26 | 27 | inline bool isActive() const 28 | { 29 | return level > SILENCE; 30 | } 31 | 32 | inline bool isInAttack() const 33 | { 34 | return target >= 2.0f; 35 | } 36 | 37 | void attack() 38 | { 39 | level += SILENCE + SILENCE; 40 | target = 2.0f; 41 | multiplier = attackMultiplier; 42 | } 43 | 44 | void release() 45 | { 46 | target = 0.0f; 47 | multiplier = releaseMultiplier; 48 | } 49 | 50 | float attackMultiplier; 51 | float decayMultiplier; 52 | float sustainLevel; 53 | float releaseMultiplier; 54 | 55 | float level; 56 | 57 | private: 58 | float target; 59 | float multiplier; 60 | }; 61 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/NoiseGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class NoiseGenerator 4 | { 5 | public: 6 | void reset() 7 | { 8 | noiseSeed = 22222; 9 | } 10 | 11 | float nextValue() 12 | { 13 | // Generate the next integer pseudorandom number. 14 | noiseSeed = noiseSeed * 196314165 + 907633515; 15 | 16 | // Convert to a signed value. 17 | int temp = int(noiseSeed >> 7) - 16777216; 18 | 19 | // Convert to a floating-point number between -1.0 and 1.0. 20 | return float(temp) / 16777216.0f; 21 | } 22 | 23 | private: 24 | unsigned int noiseSeed; 25 | }; 26 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/Oscillator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const float PI_OVER_4 = 0.7853981633974483f; 6 | const float PI = 3.1415926535897932f; 7 | const float TWO_PI = 6.2831853071795864f; 8 | 9 | class Oscillator 10 | { 11 | public: 12 | float period = 0.0f; 13 | float amplitude = 1.0f; 14 | 15 | void reset() 16 | { 17 | inc = 0.0f; 18 | phase = 0.0f; 19 | sin0 = 0.0f; 20 | sin1 = 0.0f; 21 | dsin = 0.0f; 22 | dc = 0.0f; 23 | } 24 | 25 | float nextSample() 26 | { 27 | float output = 0.0f; 28 | 29 | phase += inc; 30 | if (phase <= PI_OVER_4) { 31 | float halfPeriod = period / 2.0f; 32 | phaseMax = std::floor(0.5f + halfPeriod) - 0.5f; 33 | 34 | dc = 0.5f * amplitude / phaseMax; 35 | phaseMax *= PI; 36 | 37 | inc = phaseMax / halfPeriod; 38 | phase = -phase; 39 | 40 | sin0 = amplitude * std::sin(phase); 41 | sin1 = amplitude * std::sin(phase - inc); 42 | dsin = 2.0f * std::cos(inc); 43 | 44 | if (phase*phase > 1e-9) { 45 | output = sin0 / phase; 46 | } else { 47 | output = amplitude; 48 | } 49 | } else { 50 | if (phase > phaseMax) { 51 | phase = phaseMax + phaseMax - phase; 52 | inc = -inc; 53 | } 54 | 55 | float sinp = dsin * sin0 - sin1; 56 | sin1 = sin0; 57 | sin0 = sinp; 58 | 59 | output = sinp / phase; 60 | } 61 | 62 | return output - dc; 63 | } 64 | 65 | private: 66 | float phase; 67 | float phaseMax; 68 | float inc; 69 | 70 | float sin0; 71 | float sin1; 72 | float dsin; 73 | 74 | float dc; 75 | }; 76 | -------------------------------------------------------------------------------- /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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /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 JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/Preset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const int NUM_PARAMS = 26; 6 | 7 | struct Preset 8 | { 9 | Preset(const char* name, 10 | float p0, float p1, float p2, float p3, 11 | float p4, float p5, float p6, float p7, 12 | float p8, float p9, float p10, float p11, 13 | float p12, float p13, float p14, float p15, 14 | float p16, float p17, float p18, float p19, 15 | float p20, float p21, float p22, float p23, 16 | float p24, float p25) 17 | { 18 | strcpy(this->name, name); 19 | param[0] = p0; // Osc Mix 20 | param[1] = p1; // Osc Tune 21 | param[2] = p2; // Osc Fine 22 | param[3] = p3; // Glide Mode 23 | param[4] = p4; // Glide Rate 24 | param[5] = p5; // Glide Bend 25 | param[6] = p6; // Filter Freq 26 | param[7] = p7; // Filter Reso 27 | param[8] = p8; // Filter Env 28 | param[9] = p9; // Filter LFO 29 | param[10] = p10; // Velocity 30 | param[11] = p11; // Filter Attack 31 | param[12] = p12; // Filter Decay 32 | param[13] = p13; // Filter Sustain 33 | param[14] = p14; // Filter Release 34 | param[15] = p15; // Env Attack 35 | param[16] = p16; // Env Decay 36 | param[17] = p17; // Env Sustain 37 | param[18] = p18; // Env Release 38 | param[19] = p19; // LFO Rate 39 | param[20] = p20; // Vibrato 40 | param[21] = p21; // Noise 41 | param[22] = p22; // Octave 42 | param[23] = p23; // Tuning 43 | param[24] = p24; // Output Level 44 | param[25] = p25; // Polyphony 45 | } 46 | 47 | char name[40]; 48 | float param[NUM_PARAMS]; 49 | }; 50 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/Synth.cpp: -------------------------------------------------------------------------------- 1 | #include "Synth.h" 2 | #include "Utils.h" 3 | 4 | Synth::Synth() 5 | { 6 | sampleRate = 44100.0f; 7 | } 8 | 9 | void Synth::allocateResources(double sampleRate_, int /*samplesPerBlock*/) 10 | { 11 | sampleRate = static_cast(sampleRate_); 12 | } 13 | 14 | void Synth::deallocateResources() 15 | { 16 | // do nothing 17 | } 18 | 19 | void Synth::reset() 20 | { 21 | voice.reset(); 22 | noiseGen.reset(); 23 | pitchBend = 1.0f; 24 | } 25 | 26 | void Synth::render(float** outputBuffers, int sampleCount) 27 | { 28 | float* outputBufferLeft = outputBuffers[0]; 29 | float* outputBufferRight = outputBuffers[1]; 30 | 31 | voice.osc1.period = voice.period * pitchBend; 32 | voice.osc2.period = voice.osc1.period * detune; 33 | 34 | for (int sample = 0; sample < sampleCount; ++sample) { 35 | float noise = noiseGen.nextValue() * noiseMix; 36 | 37 | float outputLeft = 0.0f; 38 | float outputRight = 0.0f; 39 | 40 | if (voice.env.isActive()) { 41 | float output = voice.render(noise); 42 | outputLeft += output * voice.panLeft; 43 | outputRight += output * voice.panRight; 44 | } 45 | 46 | if (outputBufferRight != nullptr) { 47 | outputBufferLeft[sample] = outputLeft; 48 | outputBufferRight[sample] = outputRight; 49 | } else { 50 | outputBufferLeft[sample] = (outputLeft + outputRight) * 0.5f; 51 | } 52 | } 53 | 54 | if (!voice.env.isActive()) { 55 | voice.env.reset(); 56 | } 57 | 58 | protectYourEars(outputBufferLeft, sampleCount); 59 | protectYourEars(outputBufferRight, sampleCount); 60 | } 61 | 62 | void Synth::midiMessage(uint8_t data0, uint8_t data1, uint8_t data2) 63 | { 64 | switch (data0 & 0xF0) { 65 | // Note off 66 | case 0x80: 67 | noteOff(data1 & 0x7F); 68 | break; 69 | 70 | // Note on 71 | case 0x90: { 72 | uint8_t note = data1 & 0x7F; 73 | uint8_t velo = data2 & 0x7F; 74 | if (velo > 0) { 75 | noteOn(note, velo); 76 | } else { 77 | noteOff(note); 78 | } 79 | break; 80 | } 81 | 82 | // Pitch bend 83 | case 0xE0: 84 | pitchBend = std::exp(-0.000014102f * float(data1 + 128 * data2 - 8192)); 85 | break; 86 | } 87 | } 88 | 89 | void Synth::noteOn(int note, int velocity) 90 | { 91 | voice.note = note; 92 | voice.updatePanning(); 93 | 94 | float period = calcPeriod(note); 95 | voice.period = period; 96 | 97 | voice.osc1.amplitude = (velocity / 127.0f) * 0.5f; 98 | voice.osc2.amplitude = voice.osc1.amplitude * oscMix; 99 | 100 | Envelope& env = voice.env; 101 | env.attackMultiplier = envAttack; 102 | env.decayMultiplier = envDecay; 103 | env.sustainLevel = envSustain; 104 | env.releaseMultiplier = envRelease; 105 | env.attack(); 106 | } 107 | 108 | void Synth::noteOff(int note) 109 | { 110 | if (voice.note == note) { 111 | voice.release(); 112 | } 113 | } 114 | 115 | float Synth::calcPeriod(int note) const 116 | { 117 | float period = tune * std::exp(-0.05776226505f * float(note)); 118 | while (period < 6.0f || (period * detune) < 6.0f) { period += period; } 119 | return period; 120 | } 121 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/Synth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Voice.h" 5 | #include "NoiseGenerator.h" 6 | 7 | class Synth 8 | { 9 | public: 10 | Synth(); 11 | 12 | void allocateResources(double sampleRate, int samplesPerBlock); 13 | void deallocateResources(); 14 | void reset(); 15 | void render(float** outputBuffers, int sampleCount); 16 | void midiMessage(uint8_t data0, uint8_t data1, uint8_t data2); 17 | 18 | float noiseMix; 19 | 20 | float envAttack, envDecay, envSustain, envRelease; 21 | 22 | float oscMix; 23 | float detune; 24 | float tune; 25 | 26 | private: 27 | void noteOn(int note, int velocity); 28 | void noteOff(int note); 29 | float calcPeriod(int note) const; 30 | 31 | float sampleRate; 32 | Voice voice; 33 | NoiseGenerator noiseGen; 34 | 35 | float pitchBend; 36 | }; 37 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | inline void protectYourEars(float* buffer, int sampleCount) 4 | { 5 | if (buffer == nullptr) { return; } 6 | bool firstWarning = true; 7 | for (int i = 0; i < sampleCount; ++i) { 8 | float x = buffer[i]; 9 | bool silence = false; 10 | if (std::isnan(x)) { 11 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 12 | silence = true; 13 | } else if (std::isinf(x)) { 14 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 15 | silence = true; 16 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 17 | DBG("!!! WARNING: sample out of range, silencing !!!"); 18 | silence = true; 19 | } else if (x < -1.0f) { 20 | if (firstWarning) { 21 | DBG("!!! WARNING: sample out of range, clamping !!!"); 22 | firstWarning = false; 23 | } 24 | buffer[i] = -1.0f; 25 | } else if (x > 1.0f) { 26 | if (firstWarning) { 27 | DBG("!!! WARNING: sample out of range, clamping !!!"); 28 | firstWarning = false; 29 | } 30 | buffer[i] = 1.0f; 31 | } 32 | if (silence) { 33 | memset(buffer, 0, sampleCount * sizeof(float)); 34 | return; 35 | } 36 | } 37 | } 38 | 39 | template 40 | inline static void castParameter(juce::AudioProcessorValueTreeState& apvts, const juce::ParameterID& id, T& destination) 41 | { 42 | destination = dynamic_cast(apvts.getParameter(id.getParamID())); 43 | jassert(destination); // parameter does not exist or wrong type 44 | } 45 | -------------------------------------------------------------------------------- /Chapter Code/Chapter 9/Source/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Oscillator.h" 4 | #include "Envelope.h" 5 | 6 | struct Voice 7 | { 8 | int note; 9 | float saw; 10 | float period; 11 | 12 | Oscillator osc1; 13 | Oscillator osc2; 14 | Envelope env; 15 | 16 | float panLeft, panRight; 17 | 18 | void reset() 19 | { 20 | note = 0; 21 | saw = 0.0f; 22 | 23 | osc1.reset(); 24 | osc2.reset(); 25 | env.reset(); 26 | 27 | panLeft = 0.707f; 28 | panRight = 0.707f; 29 | } 30 | 31 | float render(float input) 32 | { 33 | float sample1 = osc1.nextSample(); 34 | float sample2 = osc2.nextSample(); 35 | saw = saw * 0.997f + sample1 - sample2; 36 | 37 | float output = saw + input; 38 | float envelope = env.nextValue(); 39 | return output * envelope; 40 | } 41 | 42 | void updatePanning() 43 | { 44 | float panning = std::clamp((note - 60.0f) / 24.0f, -1.0f, 1.0f); 45 | panLeft = std::sin(PI_OVER_4 * (1.0f - panning)); 46 | panRight = std::sin(PI_OVER_4 * (1.0f + panning)); 47 | } 48 | 49 | void release() 50 | { 51 | env.release(); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /Errata.markdown: -------------------------------------------------------------------------------- 1 | # Errata 2 | 3 | This is a list of known mistakes and bugs in the book *Creating Synthesizer Plug-Ins with C++ and JUCE* and/or the accompanying source code. 4 | 5 | ## Chapter 3: Nyquist limit 6 | 7 | Chapter 3, page 66 says: 8 | 9 | > The maximum frequency the signal can contain is known as the Nyquist limit. When using a sampling rate of 44.1 kHz, the Nyquist limit is 22.5 kHz, and the sampled signal can faithfully represent any frequencies lower than 22500 Hz — well above the hearing range of humans. But frequencies higher than 22.5 kHz won't work. 10 | 11 | The Nyquist limit at a sampling rate of 44.1 kHz is 22.05 kHz or 22050 Hz, not 22500. [Thanks @metajack](https://github.com/TheAudioProgrammer/synth-plugin-book/issues/4) 12 | 13 | ## Chapter 6: The sawtooth oscillator 14 | 15 | Chapter 6, page 125 says: 16 | 17 | > The while loop adds up sine waves until it reaches the Nyquist limit. The sine values are accumulated in the variable `y`. 18 | 19 | The variable is not named `y` but `output`. 20 | 21 | ## Chapter 11: Modulation 22 | 23 | Chapter 11, page 288 says to add a new method `updatePeriod` and then replace the corresponding lines of code in `Synth::render` with call to `updatePeriod(voice);` 24 | 25 | However, at the top of p.289 the code still does the following: 26 | 27 | ```c++ 28 | for (int v = 0; v < MAX_VOICES; ++v) { 29 | Voice& voice = voices[v]; 30 | if (voice.env.isActive()) { 31 | voice.osc1.period = voice.period * pitchBend; 32 | voice.osc2.period = voice.osc1.period * detune; 33 | voice.glideRate = glideRate; // add this line 34 | } 35 | } 36 | ``` 37 | 38 | It should be: 39 | 40 | ```c++ 41 | for (int v = 0; v < MAX_VOICES; ++v) { 42 | Voice& voice = voices[v]; 43 | if (voice.env.isActive()) { 44 | updatePeriod(voice); 45 | voice.glideRate = glideRate; 46 | } 47 | } 48 | ``` 49 | 50 | The synth works OK with the old lines, but using `updatePeriod` here is cleaner. 51 | 52 | (Also note that `updatePeriod` does not actually have to be declared `inline` since it's placed inside the class definition in the .h file. Such methods are considered inline by default.) 53 | -------------------------------------------------------------------------------- /Finished Project/Source/Envelope.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const float SILENCE = 0.0001f; // voice choking 4 | 5 | // Analog style envelope generator. 6 | class Envelope 7 | { 8 | public: 9 | void reset() 10 | { 11 | level = 0.0f; 12 | target = 0.0f; 13 | multiplier = 0.0f; 14 | } 15 | 16 | float nextValue() 17 | { 18 | // Update the amplitude envelope. This is a one-pole filter creating 19 | // an analog-style exponential envelope curve. 20 | level = multiplier * (level - target) + target; 21 | 22 | // Done with the attack portion? Then go into decay. Notice that target 23 | // is 2.0 when the envelope is in the attack stage; that is how we tell 24 | // apart the different stages. 25 | if (level + target > 3.0f) { 26 | multiplier = decayMultiplier; 27 | target = sustainLevel; 28 | } 29 | 30 | return level; 31 | } 32 | 33 | inline bool isActive() const 34 | { 35 | return level > SILENCE; 36 | } 37 | 38 | inline bool isInAttack() const 39 | { 40 | return target >= 2.0f; 41 | } 42 | 43 | void attack() 44 | { 45 | // Make the envelope level greater than SILENCE, otherwise the voice 46 | // may not be seen as active. 47 | level += SILENCE + SILENCE; 48 | 49 | // Start the attack portion of the envelope. The target is not 1.0 but 50 | // 2.0 in order to make the attack steeper than a regular exponential 51 | // curve. The attack ends when the envelope level exceeds 1.0. 52 | target = 2.0f; 53 | multiplier = attackMultiplier; 54 | } 55 | 56 | void release() 57 | { 58 | target = 0.0f; 59 | multiplier = releaseMultiplier; 60 | } 61 | 62 | // Parameter values for this envelope. 63 | float attackMultiplier; 64 | float decayMultiplier; 65 | float sustainLevel; 66 | float releaseMultiplier; 67 | 68 | // Current envelope level. 69 | float level; 70 | 71 | private: 72 | float target; 73 | float multiplier; 74 | }; 75 | -------------------------------------------------------------------------------- /Finished Project/Source/Filter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if 1 4 | 5 | // Resonant low-pass filter based on Cytomic SVF. 6 | class Filter 7 | { 8 | public: 9 | float sampleRate; 10 | 11 | void updateCoefficients(float cutoff, float Q) 12 | { 13 | g = std::tan(PI * cutoff / sampleRate); 14 | k = 1.0f / Q; 15 | a1 = 1.0f / (1.0f + g * (g + k)); 16 | a2 = g * a1; 17 | a3 = g * a2; 18 | } 19 | 20 | void reset() 21 | { 22 | g = 0.0f; 23 | k = 0.0f; 24 | a1 = 0.0f; 25 | a2 = 0.0f; 26 | a3 = 0.0f; 27 | 28 | ic1eq = 0.0f; 29 | ic2eq = 0.0f; 30 | } 31 | 32 | float render(float x) 33 | { 34 | float v3 = x - ic2eq; 35 | float v1 = a1 * ic1eq + a2 * v3; 36 | float v2 = ic2eq + a2 * ic1eq + a3 * v3; 37 | ic1eq = 2.0f * v1 - ic1eq; 38 | ic2eq = 2.0f * v2 - ic2eq; 39 | return v2; 40 | } 41 | 42 | private: 43 | const float PI = 3.1415926535897932f; 44 | 45 | float g, k, a1, a2, a3; // filter coefficients 46 | float ic1eq, ic2eq; // internal state 47 | }; 48 | 49 | #else 50 | 51 | #include 52 | 53 | // This is the Moog Ladder Filter from the chapter 10 bonus section. 54 | class Filter : public juce::dsp::LadderFilter 55 | { 56 | public: 57 | void updateCoefficients(float cutoff, float Q) 58 | { 59 | setCutoffFrequencyHz(cutoff); 60 | setResonance(std::clamp(Q / 30.0f, 0.0f, 1.0f)); 61 | //setDrive(2.0f); 62 | } 63 | 64 | float render(float x) 65 | { 66 | updateSmoothers(); 67 | return processSample(x, 0); 68 | } 69 | }; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /Finished Project/Source/NoiseGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Very simple white noise generator. 4 | class NoiseGenerator 5 | { 6 | public: 7 | void reset() 8 | { 9 | noiseSeed = 22222; 10 | } 11 | 12 | float nextValue() 13 | { 14 | // Generate the next integer pseudorandom number. 15 | noiseSeed = noiseSeed * 196314165 + 907633515; 16 | 17 | // Convert to a signed value. 18 | int temp = int(noiseSeed >> 7) - 16777216; 19 | 20 | // Convert to a floating-point number between -1.0 and 1.0. 21 | return float(temp) / 16777216.0f; 22 | } 23 | 24 | private: 25 | unsigned int noiseSeed; 26 | }; 27 | -------------------------------------------------------------------------------- /Finished Project/Source/Oscillator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const float PI_OVER_4 = 0.7853981633974483f; 6 | const float PI = 3.1415926535897932f; 7 | const float TWO_PI = 6.2831853071795864f; 8 | 9 | // Bandlimited impulse train (BLIT) oscillator. 10 | class Oscillator 11 | { 12 | public: 13 | // The new period in samples. Won't take effect until the next cycle. 14 | float period = 0.0f; 15 | 16 | // Modulations to be applied to the period. 1.0 = no modulation. 17 | float modulation = 1.0f; 18 | 19 | // Output level for this oscillator. 20 | float amplitude = 1.0f; 21 | 22 | void reset() 23 | { 24 | inc = 0.0f; 25 | phase = 0.0f; 26 | sin0 = 0.0f; 27 | sin1 = 0.0f; 28 | dsin = 0.0f; 29 | dc = 0.0f; 30 | } 31 | 32 | // Creates a sinc pulse every `period` samples. 33 | float nextSample() 34 | { 35 | float output = 0.0f; 36 | 37 | phase += inc; // increment position in time 38 | 39 | if (phase <= PI_OVER_4) { 40 | // This is executed the very first time and after every cycle. 41 | 42 | // Set the period for the next cycle. Even though the period can be 43 | // modulated (vibrato, pitch bend, glide), it's only changed on the 44 | // start of the next cycle, never in the middle of an ongoing cycle. 45 | float halfPeriod = (period / 2.0f) * modulation; 46 | 47 | // Calculate the halfway point between this peak and the next, 48 | // expressed in samples. 49 | phaseMax = std::floor(0.5f + halfPeriod) - 0.5f; 50 | 51 | // The DC offset is necessary for turning the impulse train into a 52 | // sawtooth wave. The total DC offset for one cycle of the sawtooth 53 | // is half the amplitude. Divide that by the number of samples to 54 | // get the DC offset per sample. 55 | dc = 0.5f * amplitude / phaseMax; 56 | 57 | // The sinc function is sin(phase * PI) / (phase * PI), so to avoid 58 | // having to multiply by PI all the time, the unit of the phase and 59 | // therefore phaseMax and inc variables is "samples times PI". 60 | phaseMax *= PI; 61 | 62 | // In theory, the phase increment `inc` is equal to PI, except the 63 | // halfway point has been "fudged" a little to help reduce aliasing, 64 | // so `inc` will not be exactly PI (but close to it). 65 | inc = phaseMax / halfPeriod; 66 | 67 | // After the halfway point, the phase counts down to the next peak. 68 | // Once we're at the peak (now), we'll make the phase go up again. 69 | phase = -phase; 70 | 71 | // Initialize the sine oscillator. 72 | sin0 = amplitude * std::sin(phase); 73 | sin1 = amplitude * std::sin(phase - inc); 74 | dsin = 2.0f * std::cos(inc); 75 | 76 | // Output the peak of the sinc pulse. Make sure to not divide by 0. 77 | if (phase*phase > 1e-9) { 78 | output = sin0 / phase; 79 | } else { 80 | output = amplitude; 81 | } 82 | } else { 83 | // Crossed the halfway point? Then do the second half of the sinc 84 | // pulse in reverse, counting backwards until the next peak. 85 | if (phase > phaseMax) { 86 | phase = phaseMax + phaseMax - phase; 87 | inc = -inc; 88 | } 89 | 90 | // Sine wave approximation. 91 | float sinp = dsin * sin0 - sin1; 92 | sin1 = sin0; 93 | sin0 = sinp; 94 | 95 | // Sinc function: y = sin(x) / x. 96 | output = sinp / phase; 97 | } 98 | 99 | // Return the value minus the DC offset. 100 | return output - dc; 101 | } 102 | 103 | void squareWave(Oscillator& other, float newPeriod) 104 | { 105 | reset(); 106 | 107 | // Normally the two oscillators have their own independent phase that 108 | // is never "synced up" anywhere. However, to make a square wave, the 109 | // negative peak from the second oscillator should fall somewhere in 110 | // between two positive peaks from the first oscillator. To do this, 111 | // we explicitly set the phase of the second oscillator to the phase 112 | // of the first, but shifted by half a cycle. 113 | 114 | if (other.inc > 0.0f) { 115 | phase = other.phaseMax + other.phaseMax - other.phase; 116 | inc = -other.inc; 117 | } else if (other.inc < 0.0f) { 118 | phase = other.phase; 119 | inc = other.inc; 120 | } else { 121 | // The other oscillator has not started yet so its phase increment 122 | // is still zero. Usually `inc` is around PI, so just pick that. 123 | phase = -PI; 124 | inc = PI; 125 | } 126 | 127 | // Shift by 180 degrees relative to the other sawtooth wave. 128 | phase += PI * newPeriod / 2.0f; 129 | phaseMax = phase; 130 | } 131 | 132 | private: 133 | // Current phase, in samples times PI. 134 | float phase; 135 | 136 | // The phase counts up to this value... 137 | float phaseMax; 138 | 139 | // ...by this increment. 140 | float inc; 141 | 142 | // Direct form sine oscillator. 143 | float sin0; 144 | float sin1; 145 | float dsin; 146 | 147 | // DC offset. This is subtracted to create the sawtooth wave. 148 | float dc; 149 | }; 150 | -------------------------------------------------------------------------------- /Finished Project/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 | JX11AudioProcessorEditor::JX11AudioProcessorEditor (JX11AudioProcessor& 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 | JX11AudioProcessorEditor::~JX11AudioProcessorEditor() 22 | { 23 | } 24 | 25 | //============================================================================== 26 | void JX11AudioProcessorEditor::paint (juce::Graphics& g) 27 | { 28 | // (Our component is opaque, so we must completely fill the background with a solid colour) 29 | g.fillAll (getLookAndFeel().findColour (juce::ResizableWindow::backgroundColourId)); 30 | 31 | g.setColour (juce::Colours::white); 32 | g.setFont (15.0f); 33 | g.drawFittedText ("Hello World!", getLocalBounds(), juce::Justification::centred, 1); 34 | } 35 | 36 | void JX11AudioProcessorEditor::resized() 37 | { 38 | // This is generally where you'll want to lay out the positions of any 39 | // subcomponents in your editor.. 40 | } 41 | -------------------------------------------------------------------------------- /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 | 14 | //============================================================================== 15 | /** 16 | */ 17 | class JX11AudioProcessorEditor : public juce::AudioProcessorEditor 18 | { 19 | public: 20 | JX11AudioProcessorEditor (JX11AudioProcessor&); 21 | ~JX11AudioProcessorEditor() 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 | JX11AudioProcessor& audioProcessor; 31 | 32 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JX11AudioProcessorEditor) 33 | }; 34 | -------------------------------------------------------------------------------- /Finished Project/Source/Preset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | const int NUM_PARAMS = 26; 6 | 7 | // Describes a factory preset. 8 | struct Preset 9 | { 10 | Preset(const char* name, 11 | float p0, float p1, float p2, float p3, 12 | float p4, float p5, float p6, float p7, 13 | float p8, float p9, float p10, float p11, 14 | float p12, float p13, float p14, float p15, 15 | float p16, float p17, float p18, float p19, 16 | float p20, float p21, float p22, float p23, 17 | float p24, float p25) 18 | { 19 | strcpy(this->name, name); 20 | param[0] = p0; // Osc Mix 21 | param[1] = p1; // Osc Tune 22 | param[2] = p2; // Osc Fine 23 | param[3] = p3; // Glide Mode 24 | param[4] = p4; // Glide Rate 25 | param[5] = p5; // Glide Bend 26 | param[6] = p6; // Filter Freq 27 | param[7] = p7; // Filter Reso 28 | param[8] = p8; // Filter Env 29 | param[9] = p9; // Filter LFO 30 | param[10] = p10; // Velocity 31 | param[11] = p11; // Filter Attack 32 | param[12] = p12; // Filter Decay 33 | param[13] = p13; // Filter Sustain 34 | param[14] = p14; // Filter Release 35 | param[15] = p15; // Env Attack 36 | param[16] = p16; // Env Decay 37 | param[17] = p17; // Env Sustain 38 | param[18] = p18; // Env Release 39 | param[19] = p19; // LFO Rate 40 | param[20] = p20; // Vibrato 41 | param[21] = p21; // Noise 42 | param[22] = p22; // Octave 43 | param[23] = p23; // Tuning 44 | param[24] = p24; // Output Level 45 | param[25] = p25; // Polyphony 46 | } 47 | 48 | char name[40]; 49 | float param[NUM_PARAMS]; 50 | }; 51 | -------------------------------------------------------------------------------- /Finished Project/Source/Synth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Voice.h" 5 | #include "NoiseGenerator.h" 6 | 7 | // The main class for the synthesizer. 8 | class Synth 9 | { 10 | public: 11 | Synth(); 12 | 13 | void allocateResources(double sampleRate, int samplesPerBlock); 14 | void deallocateResources(); 15 | void reset(); 16 | void render(float** outputBuffers, int sampleCount); 17 | void midiMessage(uint8_t data0, uint8_t data1, uint8_t data2); 18 | 19 | // === Parameter values === 20 | 21 | // Gain for mixing noise into the output. 22 | float noiseMix; 23 | 24 | // Amplitude ADSR settings. 25 | float envAttack, envDecay, envSustain, envRelease; 26 | 27 | // How much oscillator 2 is mixed into the sound. 0.0 = osc2 is silent, 28 | // 1.0 = osc2 has same level as osc1. Note that osc2 is subtracted, so if 29 | // it is not detuned from osc1, they cancel each other out into silence. 30 | float oscMix; 31 | 32 | // Amount of detuning for oscillator 2. This is a multiplier for the period 33 | // of the oscillator. 34 | float detune; 35 | 36 | // Master tuning. 37 | float tune; 38 | 39 | // Max polyphony. 40 | static constexpr int MAX_VOICES = 8; 41 | 42 | // Mono (= 1 voice) / poly mode. 43 | int numVoices; 44 | 45 | // Used to keep the output gain constant after changing parameters. 46 | float volumeTrim; 47 | 48 | // Output gain. 49 | juce::LinearSmoothedValue outputLevelSmoother; 50 | 51 | // Used to set the low-pass filter's cutoff frequency based on the note's 52 | // velocity. There is no velocity sensitivity for the amplitude envelope, 53 | // only for the filter cutoff. 54 | float velocitySensitivity; 55 | 56 | // If this is set, all notes will be played with the same velocity. 57 | bool ignoreVelocity; 58 | 59 | // How often the LFO and other modulations are updated, in samples. 60 | const int LFO_MAX = 32; 61 | 62 | // Phase increment for the LFO. 63 | float lfoInc; 64 | 65 | // LFO intensity for vibrato and PWM. 66 | float vibrato; 67 | float pwmDepth; 68 | 69 | // Glide mode: 0 = off, 1 = legato-style playing, 2 = always. 70 | int glideMode; 71 | 72 | // Coefficient for the speed of the glide. 1.0 is instantaneous (no glide). 73 | float glideRate; 74 | 75 | // Number of semitones to glide up or down into any new note. This is used 76 | // even if the glide mode is set to off. 77 | float glideBend; 78 | 79 | // The user does not manually set the filter's cutoff frequency, this is 80 | // determined by the note's pitch and velocity. This variable is used as 81 | // a multiplier that shifts the cutoff up or down. 82 | float filterKeyTracking; 83 | 84 | // Resonance setting for the low-pass filter. 85 | float filterQ; 86 | 87 | // LFO intensity for the filter cutoff. 88 | float filterLFODepth; 89 | 90 | // Filter ADSR settings. 91 | float filterAttack, filterDecay, filterSustain, filterRelease; 92 | 93 | // Envelope intensity for the filter cutoff. 94 | float filterEnvDepth; 95 | 96 | private: 97 | // Performs the LFO update very 32 samples. 98 | void updateLFO(); 99 | 100 | // Handles a MIDI CC event. 101 | void controlChange(uint8_t data1, uint8_t data2); 102 | 103 | // Handles a MIDI note on event. 104 | void noteOn(int note, int velocity); 105 | 106 | // Handles a MIDI note off event. 107 | void noteOff(int note); 108 | 109 | // Helper functions that set up a voice to play a new note. 110 | void startVoice(int v, int note, int velocity); 111 | void restartMonoVoice(int note, int velocity); 112 | 113 | // Calculate the oscillator period based on the MIDI note number. 114 | float calcPeriod(int v, int note) const; 115 | 116 | // Find a voice to use in polyphonic mode. 117 | int findFreeVoice() const; 118 | 119 | // For note queuing in monophonic mode. 120 | void shiftQueuedNotes(); 121 | int nextQueuedNote(); 122 | 123 | inline void updatePeriod(Voice& voice) 124 | { 125 | voice.osc1.period = voice.period * pitchBend; 126 | voice.osc2.period = voice.osc1.period * detune; 127 | } 128 | 129 | // Is at least one key still held down for any of the playing voices? 130 | bool isPlayingLegatoStyle() const; 131 | 132 | // The current sample rate. 133 | float sampleRate; 134 | 135 | // List of the active voices. 136 | std::array voices; 137 | 138 | // Pseudo random noise generator. 139 | NoiseGenerator noiseGen; 140 | 141 | // Most recent note that was played. Used for gliding. 142 | int lastNote; 143 | 144 | // === Modulation === 145 | 146 | // The LFO only updates every 32 samples. This counter keeps track of when 147 | // the next update is. 148 | int lfoStep; 149 | 150 | // Current LFO value. 151 | float lfo; 152 | 153 | // Used to smoothen changes in the amount of low-pass filter modulation. 154 | float filterZip; 155 | 156 | // === MIDI CC values === 157 | 158 | // Current value for the pitch bend wheel. 159 | float pitchBend; 160 | 161 | // Status of the damper pedal: true = pressed, false = released. 162 | bool sustainPedalPressed; 163 | 164 | // Modulation wheel value. Sets the modulation depth for vibrato / PWM. 165 | float modWheel; 166 | 167 | // MIDI CC amount used to modulate the filter Q. 168 | float resonanceCtl; 169 | 170 | // Amount of channel aftertouch. Used to modulate the filter cutoff. 171 | float pressure; 172 | 173 | // MIDI CC amount used to modulate the cutoff frequency. 174 | float filterCtl; 175 | }; 176 | -------------------------------------------------------------------------------- /Finished Project/Source/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Silences the buffer if bad or loud values are detected in the output buffer. 4 | // Use this during debugging to avoid blowing out your eardrums on headphones. 5 | // If the output value is out of the range [-1, +1] it will be hard clipped. 6 | inline void protectYourEars(float* buffer, int sampleCount) 7 | { 8 | if (buffer == nullptr) { return; } 9 | bool firstWarning = true; 10 | for (int i = 0; i < sampleCount; ++i) { 11 | float x = buffer[i]; 12 | bool silence = false; 13 | if (std::isnan(x)) { 14 | DBG("!!! WARNING: nan detected in audio buffer, silencing !!!"); 15 | silence = true; 16 | } else if (std::isinf(x)) { 17 | DBG("!!! WARNING: inf detected in audio buffer, silencing !!!"); 18 | silence = true; 19 | } else if (x < -2.0f || x > 2.0f) { // screaming feedback 20 | DBG("!!! WARNING: sample out of range, silencing !!!"); 21 | silence = true; 22 | } else if (x < -1.0f) { 23 | if (firstWarning) { 24 | DBG("!!! WARNING: sample out of range, clamping !!!"); 25 | firstWarning = false; 26 | } 27 | buffer[i] = -1.0f; 28 | } else if (x > 1.0f) { 29 | if (firstWarning) { 30 | DBG("!!! WARNING: sample out of range, clamping !!!"); 31 | firstWarning = false; 32 | } 33 | buffer[i] = 1.0f; 34 | } 35 | if (silence) { 36 | memset(buffer, 0, sampleCount * sizeof(float)); 37 | return; 38 | } 39 | } 40 | } 41 | 42 | // Returns a typed pointer to a juce::AudioParameterXXX object from the APVTS. 43 | template 44 | inline static void castParameter(juce::AudioProcessorValueTreeState& apvts, const juce::ParameterID& id, T& destination) 45 | { 46 | destination = dynamic_cast(apvts.getParameter(id.getParamID())); 47 | jassert(destination); // parameter does not exist or wrong type 48 | } 49 | -------------------------------------------------------------------------------- /Finished Project/Source/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Oscillator.h" 4 | #include "Envelope.h" 5 | #include "Filter.h" 6 | 7 | // State for an active voice. 8 | struct Voice 9 | { 10 | // The MIDI note number that this voice is playing, or the special value 11 | // SUSTAIN when the key has been released but the sustain pedal is held 12 | // down. Is 0 if the voice is inactive. 13 | int note; 14 | 15 | // The current period of the waveform in samples, which may be gliding up 16 | // to the value from `target`. 17 | float period; 18 | 19 | // The desired period in samples. 20 | float target; 21 | 22 | // Oscillators 23 | Oscillator osc1; 24 | Oscillator osc2; 25 | 26 | // Integrates the outputs from the oscillators to produce a sawtooth wave. 27 | float saw; 28 | 29 | // Amplitude envelope. 30 | Envelope env; 31 | 32 | // Filter and its envelope. 33 | Filter filter; 34 | Envelope filterEnv; 35 | 36 | // The filter's base cutoff frequency based on pitch and velocity, in Hz. 37 | float cutoff; 38 | 39 | // The filter resonance. 40 | float filterQ; 41 | 42 | // Modulation value that is computed by Synth but that Voice needs. 43 | float filterMod; 44 | 45 | // The synth parameters and MIDI controller values this voice needs. 46 | float glideRate; 47 | float pitchBend; 48 | float filterEnvDepth; 49 | 50 | // Panning amounts for left and right channels. 51 | float panLeft, panRight; 52 | 53 | void reset() 54 | { 55 | note = 0; 56 | saw = 0.0f; 57 | 58 | osc1.reset(); 59 | osc2.reset(); 60 | env.reset(); 61 | filterEnv.reset(); 62 | filter.reset(); 63 | 64 | panLeft = 0.707f; 65 | panRight = 0.707f; 66 | } 67 | 68 | float render(float input) 69 | { 70 | // The two oscillators output a bandlimited impulse train, which 71 | // consists of a sinc pulse every `period` samples. 72 | float sample1 = osc1.nextSample(); 73 | float sample2 = osc2.nextSample(); 74 | 75 | // By adding up the sinc pulses over time, i.e. by integrating them, 76 | // this creates a bandlimited sawtooth wave without much aliasing. 77 | // Subtracting the osc2 sawtooth from osc1 creates a square wave. 78 | // For the best results, osc2 should be detuned otherwise it will 79 | // cancel out with osc1 and give silence. 80 | saw = saw * 0.997f + sample1 - sample2; 81 | 82 | // Note: It can be a little unpredictable how these two oscillators 83 | // interact. The oscillator state is not reset when an old voice is 84 | // reused for a new note, and so the phase difference between osc1 85 | // and osc2 is never the same -- which is part of the fun. 86 | 87 | // Combine the output from the oscillators with the noise. 88 | float output = saw + input; 89 | 90 | // Apply the resonant low-pass filter. 91 | output = filter.render(output); 92 | 93 | // Amplitude envelope. 94 | float envelope = env.nextValue(); 95 | 96 | // The output for this voice is the amplitude envelope times the 97 | // output from the filter. 98 | return output * envelope; 99 | } 100 | 101 | void updatePanning() 102 | { 103 | // Put middle C (note 60) in the center of the stereo field. 104 | // Fully panned left is note (60 - 24), fully right is note (60 + 24). 105 | float panning = std::clamp((note - 60.0f) / 24.0f, -1.0f, 1.0f); 106 | 107 | // Use constant power panning formula. 108 | panLeft = std::sin(PI_OVER_4 * (1.0f - panning)); 109 | panRight = std::sin(PI_OVER_4 * (1.0f + panning)); 110 | } 111 | 112 | void updateLFO() 113 | { 114 | // Do the following updates at the LFO update rate. 115 | 116 | // Glide between pitches using a simple one-pole smoothing filter. 117 | period += glideRate * (target - period); 118 | 119 | // Update the filter envelope. This is the same equation as for the 120 | // amplitude envelope, but only performed every LFO_MAX steps. 121 | float fenv = filterEnv.nextValue(); 122 | 123 | // Calculate the filter cutoff frequency. The base `cutoff` is given by 124 | // the pitch and velocity. This is modulated by a variety of other things 125 | // such as the filter envelope and the pitch bend. 126 | float modulatedCutoff = cutoff * std::exp(filterMod + filterEnvDepth * fenv) / pitchBend; 127 | 128 | // Make sure the cutoff frequency stays within reasonable bounds. 129 | modulatedCutoff = std::clamp(modulatedCutoff, 30.0f, 20000.0f); 130 | 131 | // Tell the filter to recalculate its coefficients. 132 | filter.updateCoefficients(modulatedCutoff, filterQ); 133 | } 134 | 135 | void release() 136 | { 137 | env.release(); 138 | filterEnv.release(); 139 | } 140 | }; 141 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2008 Paul Kellett 4 | Copyright (c) 2022-2023 M.I. Hollemans 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Creating Synthesizer Plug-Ins with C++ and JUCE 2 | 3 | This is the source code that accompanies the book [Creating Synthesizer Plug-Ins with C++ and JUCE](https://theaudioprogrammer.com/learn/synth-plugin-book) by Matthijs Hollemans, available for purchase from [The Audio Programmer](https://theaudioprogrammer.com/learn/synth-plugin-book) as PDF and EPUB. 4 | 5 | ![The book cover](book-cover.jpg) 6 | 7 | This 370-page book teaches step-by-step how to design and build a software synthesizer plug-in that can be used in all the popular DAWs such as Logic Pro, Ableton Live, REAPER, FL Studio, Cubase, Bitwig Studio, and others. 8 | 9 | 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 general and synths in particular, in an easy-to-follow guide that is light on math and heavy on being practical. 10 | 11 | If you don't have the book yet, you can read free sample chapters at [theaudioprogrammer.com/learn/synth-plugin-book](https://theaudioprogrammer.com/learn/synth-plugin-book) to see if you like it. 12 | 13 | ## How to use this repo 14 | 15 | For each chapter there is a folder with the finished source code. Open the **JX11.jucer** file in **Projucer** and click the export button to open the project in your IDE. 16 | 17 | The **Finished Project** folder contains the synth plug-in in its entirety, with added source code comments (it does not include the UI code from Chapter 13). 18 | 19 | Want to know what the synth sounds like? Check out a few examples in the **Audio Demos** folder. 20 | 21 | ## Found a bug or have a question? 22 | 23 | First, please [check the errata](Errata.markdown) to see if the issue has already been covered. 24 | 25 | If not, refer to the [list of open issues](https://github.com/TheAudioProgrammer/synth-plugin-book/issues) and submit a new issue if you can't find your question in the list. 26 | 27 | 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`. 28 | 29 | ## Source code license 30 | 31 | The book is copyright 2022-2023 M.I. Hollemans, all rights reserved. 32 | 33 | The source code in this repo is licensed under the terms of the [MIT license](LICENSE.txt). 34 | 35 | The JX11 synth from the book is based on the MDA JX10 synthesizer, originally written by Paul Kellett of maxim digital audio. The factory presets included in the synth are designed by Paul Kellett with additional patch design by Stefan Andersson and Zeitfraktur, Sascha Kujawa. 36 | 37 | JUCE is copyright © Raw Material Software. 38 | 39 | The Lato font is copyright (c) 2010-2014 Łukasz Dziedzic and is licensed under the SIL Open Font License. 40 | -------------------------------------------------------------------------------- /Resources/Lato-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BuildASynthPluginBook/08966f07e8974d7cca2e582e827c7b0be01dd8a3/Resources/Lato-Medium.ttf -------------------------------------------------------------------------------- /Resources/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2014, Łukasz Dziedzic (dziedzic@typoland.com), 2 | with Reserved Font Name Lato. 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is copied below, and is also available with a FAQ at: 6 | http://scripts.sil.org/OFL 7 | 8 | 9 | ----------------------------------------------------------- 10 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 11 | ----------------------------------------------------------- 12 | 13 | PREAMBLE 14 | The goals of the Open Font License (OFL) are to stimulate worldwide 15 | development of collaborative font projects, to support the font creation 16 | efforts of academic and linguistic communities, and to provide a free and 17 | open framework in which fonts may be shared and improved in partnership 18 | with others. 19 | 20 | The OFL allows the licensed fonts to be used, studied, modified and 21 | redistributed freely as long as they are not sold by themselves. The 22 | fonts, including any derivative works, can be bundled, embedded, 23 | redistributed and/or sold with any software provided that any reserved 24 | names are not used by derivative works. The fonts and derivatives, 25 | however, cannot be released under any other type of license. The 26 | requirement for fonts to remain under this license does not apply 27 | to any document created using the fonts or their derivatives. 28 | 29 | DEFINITIONS 30 | "Font Software" refers to the set of files released by the Copyright 31 | Holder(s) under this license and clearly marked as such. This may 32 | include source files, build scripts and documentation. 33 | 34 | "Reserved Font Name" refers to any names specified as such after the 35 | copyright statement(s). 36 | 37 | "Original Version" refers to the collection of Font Software components as 38 | distributed by the Copyright Holder(s). 39 | 40 | "Modified Version" refers to any derivative made by adding to, deleting, 41 | or substituting -- in part or in whole -- any of the components of the 42 | Original Version, by changing formats or by porting the Font Software to a 43 | new environment. 44 | 45 | "Author" refers to any designer, engineer, programmer, technical 46 | writer or other person who contributed to the Font Software. 47 | 48 | PERMISSION & CONDITIONS 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 51 | redistribute, and sell modified and unmodified copies of the Font 52 | Software, subject to the following conditions: 53 | 54 | 1) Neither the Font Software nor any of its individual components, 55 | in Original or Modified Versions, may be sold by itself. 56 | 57 | 2) Original or Modified Versions of the Font Software may be bundled, 58 | redistributed and/or sold with any software, provided that each copy 59 | contains the above copyright notice and this license. These can be 60 | included either as stand-alone text files, human-readable headers or 61 | in the appropriate machine-readable metadata fields within text or 62 | binary files as long as those fields can be easily viewed by the user. 63 | 64 | 3) No Modified Version of the Font Software may use the Reserved Font 65 | Name(s) unless explicit written permission is granted by the corresponding 66 | Copyright Holder. This restriction only applies to the primary font name as 67 | presented to the users. 68 | 69 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 70 | Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except to acknowledge the contribution(s) of the 72 | Copyright Holder(s) and the Author(s) or with their explicit written 73 | permission. 74 | 75 | 5) The Font Software, modified or unmodified, in part or in whole, 76 | must be distributed entirely under this license, and must not be 77 | distributed under any other license. The requirement for fonts to 78 | remain under this license does not apply to any document created 79 | using the Font Software. 80 | 81 | TERMINATION 82 | This license becomes null and void if any of the above conditions are 83 | not met. 84 | 85 | DISCLAIMER 86 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 87 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 88 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 89 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 90 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 91 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 92 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 93 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 94 | OTHER DEALINGS IN THE FONT SOFTWARE. 95 | -------------------------------------------------------------------------------- /book-cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BuildASynthPluginBook/08966f07e8974d7cca2e582e827c7b0be01dd8a3/book-cover.jpg -------------------------------------------------------------------------------- /midi-note-chart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/BuildASynthPluginBook/08966f07e8974d7cca2e582e827c7b0be01dd8a3/midi-note-chart.jpg --------------------------------------------------------------------------------