├── Documents └── ScreenShot.png ├── .gitmodules ├── Scripts ├── build_vs2019.bat └── build_xcode.command ├── .gitignore ├── CMakeLists.txt ├── README.md ├── LICENSE └── Examples ├── DrumKit ├── Source │ ├── PluginEditor.h │ ├── PluginProcessor.h │ ├── WaveShapeVisualizer.h │ ├── PluginEditor.cpp │ └── PluginProcessor.cpp └── DrumKit.cmake └── HelloDaisySP ├── Source ├── PluginEditor.h ├── PluginProcessor.h ├── WaveShapeVisualizer.h ├── PluginEditor.cpp └── PluginProcessor.cpp └── HelloDaisySP.cmake /Documents/ScreenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/COx2/juce_meets_DaisySP/HEAD/Documents/ScreenShot.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "External/JUCE"] 2 | path = External/JUCE 3 | url = https://github.com/juce-framework/JUCE.git 4 | [submodule "External/DaisySP"] 5 | path = External/DaisySP 6 | url = https://github.com/electro-smith/DaisySP.git 7 | -------------------------------------------------------------------------------- /Scripts/build_vs2019.bat: -------------------------------------------------------------------------------- 1 | set SCRIPT_DIRECTORY=%~dp0 2 | cd "%SCRIPT_DIRECTORY%\.." 3 | 4 | call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsDevCmd.bat" -arch=amd64 5 | cmake -B Build -G "Visual Studio 16 2019" . 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build 2 | Build 3 | 4 | # Prerequisites 5 | *.d 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | *.smod 25 | 26 | # Compiled Static libraries 27 | *.lai 28 | *.la 29 | *.a 30 | *.lib 31 | 32 | # Executables 33 | *.exe 34 | *.out 35 | *.app 36 | -------------------------------------------------------------------------------- /Scripts/build_xcode.command: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e -u 4 | 5 | # Refer: https://stackoverflow.com/questions/3572030/bash-script-absolute-path-with-os-x 6 | realpath() { 7 | [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}" 8 | } 9 | 10 | SCRIPT_NAME="$(basename "$(realpath "${BASH_SOURCE:-$0}")")" 11 | SCRIPT_DIRECTORY="$(dirname "$(realpath "${BASH_SOURCE:-$0}")")" 12 | 13 | cd "${SCRIPT_DIRECTORY}/.." 14 | 15 | cmake -B Build -G Xcode . 16 | 17 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | enable_language(CXX) 4 | set(CMAKE_CXX_STANDARD 14) # C++14... 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) #...is required... 6 | set(CMAKE_CXX_EXTENSIONS OFF) #...without compiler extensions like gnu++11 7 | 8 | project(juce_meets_DaisySP VERSION 0.0.1) 9 | 10 | add_subdirectory(External/JUCE) 11 | add_subdirectory(External/DaisySP) 12 | 13 | include(Examples/HelloDaisySP/HelloDaisySP.cmake) 14 | include(Examples/DrumKit/DrumKit.cmake) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # juce_meets_DaisySP 2 | An example of integration of DaisySP and JUCE6 with CMake 3 | 4 | ***DEMO:*** 5 | 6 | ![Demo](./Documents/ScreenShot.png) 7 | 8 | ## Requirement 9 | + CMake 3.15 or later 10 | + Git 11 | 12 | ## How to build 13 | + Get Sources 14 | ~~~ 15 | $ git clone https://github.com/COx2/juce_meets_DaisySP.git 16 | $ cd juce_meets_DaisySP 17 | $ git submodule update --init --recursive 18 | ~~~ 19 | 20 | + Generate project by CMake 21 | 22 | Shortcut: For Visual Studio 2019 users 23 | ~~~ 24 | .\Scripts\build_vs2019.bat 25 | ~~~ 26 | 27 | Shortcut: For Xcode users 28 | ~~~ 29 | ./Scripts/build_xcode.command 30 | ~~~ 31 | 32 | + Build by IDE 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Tatsuya Shiozawa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Examples/DrumKit/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "PluginProcessor.h" 5 | 6 | //============================================================================== 7 | class AudioPluginAudioProcessorEditor : public juce::AudioProcessorEditor 8 | , private juce::Timer 9 | { 10 | public: 11 | explicit AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor&); 12 | ~AudioPluginAudioProcessorEditor() override; 13 | 14 | //============================================================================== 15 | void paint (juce::Graphics&) override; 16 | void resized() override; 17 | 18 | private: 19 | //============================================================================== 20 | virtual void timerCallback() override; 21 | 22 | AudioPluginAudioProcessor& processorRef; 23 | 24 | juce::Rectangle rectDrumKit; 25 | std::unique_ptr buttonBassDrum; 26 | std::unique_ptr buttonSnareDrum; 27 | std::unique_ptr buttonHiHat; 28 | 29 | std::unique_ptr midiKeyboard; 30 | 31 | juce::OwnedArray sliderAttachments; 32 | juce::OwnedArray comboboxAttachments; 33 | 34 | 35 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessorEditor) 36 | }; 37 | -------------------------------------------------------------------------------- /Examples/HelloDaisySP/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "PluginProcessor.h" 5 | 6 | //============================================================================== 7 | class AudioPluginAudioProcessorEditor : public juce::AudioProcessorEditor 8 | , private juce::Timer 9 | { 10 | public: 11 | explicit AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor&); 12 | ~AudioPluginAudioProcessorEditor() override; 13 | 14 | //============================================================================== 15 | void paint (juce::Graphics&) override; 16 | void resized() override; 17 | 18 | private: 19 | //============================================================================== 20 | virtual void timerCallback() override; 21 | 22 | AudioPluginAudioProcessor& processorRef; 23 | 24 | std::unique_ptr sliderGain; 25 | std::unique_ptr sliderFrequency; 26 | std::unique_ptr comboboxWaveform; 27 | std::unique_ptr labelGain; 28 | std::unique_ptr labelFrequency; 29 | std::unique_ptr labelWaveform; 30 | std::unique_ptr groupOscillator; 31 | 32 | std::unique_ptr sliderTremoloFrequnency; 33 | std::unique_ptr sliderTremoloDepth; 34 | std::unique_ptr comboboxTremoloWaveform; 35 | std::unique_ptr labelTremoloFrequency; 36 | std::unique_ptr labelTremoloDepth; 37 | std::unique_ptr labelTremoloWaveform; 38 | std::unique_ptr groupTremolo; 39 | 40 | juce::OwnedArray sliderAttachments; 41 | juce::OwnedArray comboboxAttachments; 42 | 43 | 44 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessorEditor) 45 | }; 46 | -------------------------------------------------------------------------------- /Examples/DrumKit/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "WaveShapeVisualizer.h" 5 | #include "Drums/synthbassdrum.h" 6 | #include "Drums/synthsnaredrum.h" 7 | #include "Drums/hihat.h" 8 | 9 | //============================================================================== 10 | class AudioPluginAudioProcessor : public juce::AudioProcessor 11 | { 12 | public: 13 | //============================================================================== 14 | AudioPluginAudioProcessor(); 15 | ~AudioPluginAudioProcessor() override; 16 | 17 | //============================================================================== 18 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 19 | void releaseResources() override; 20 | 21 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 22 | 23 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 24 | using AudioProcessor::processBlock; 25 | 26 | //============================================================================== 27 | juce::AudioProcessorEditor* createEditor() override; 28 | bool hasEditor() const override; 29 | 30 | //============================================================================== 31 | const juce::String getName() const override; 32 | 33 | bool acceptsMidi() const override; 34 | bool producesMidi() const override; 35 | bool isMidiEffect() const override; 36 | double getTailLengthSeconds() const override; 37 | 38 | //============================================================================== 39 | int getNumPrograms() override; 40 | int getCurrentProgram() override; 41 | void setCurrentProgram (int index) override; 42 | const juce::String getProgramName (int index) override; 43 | void changeProgramName (int index, const juce::String& newName) override; 44 | 45 | //============================================================================== 46 | void getStateInformation (juce::MemoryBlock& destData) override; 47 | void setStateInformation (const void* data, int sizeInBytes) override; 48 | 49 | //============================================================================== 50 | juce::AudioProcessorValueTreeState& getProcessorState() { return apvts; } 51 | juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout() const; 52 | 53 | juce::MidiKeyboardState& getMidiKeyboardState() { return keyboardState; } 54 | 55 | WaveDrawBuffer& getWaveDrawBuffer() { return waveDrawBuffer; } 56 | 57 | void triggerBassDrum(); 58 | void triggerSnareDrum(); 59 | void triggerHiHat(); 60 | 61 | private: 62 | //============================================================================== 63 | juce::AudioProcessorValueTreeState apvts; 64 | juce::MidiKeyboardState keyboardState; 65 | 66 | std::unique_ptr bassdrum; 67 | std::unique_ptr snaredrum; 68 | std::unique_ptr> hihat; 69 | 70 | // Wave shape visualizer 71 | WaveDrawBuffer waveDrawBuffer; 72 | WaveSampleCollector waveSampleCollector; 73 | 74 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessor) 75 | }; 76 | -------------------------------------------------------------------------------- /Examples/HelloDaisySP/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "WaveShapeVisualizer.h" 5 | #include "Synthesis/oscillator.h" 6 | #include "Effects/tremolo.h" 7 | 8 | //============================================================================== 9 | class AudioPluginAudioProcessor : public juce::AudioProcessor 10 | { 11 | public: 12 | //============================================================================== 13 | AudioPluginAudioProcessor(); 14 | ~AudioPluginAudioProcessor() override; 15 | 16 | //============================================================================== 17 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 18 | void releaseResources() override; 19 | 20 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 21 | 22 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 23 | using AudioProcessor::processBlock; 24 | 25 | //============================================================================== 26 | juce::AudioProcessorEditor* createEditor() override; 27 | bool hasEditor() const override; 28 | 29 | //============================================================================== 30 | const juce::String getName() const override; 31 | 32 | bool acceptsMidi() const override; 33 | bool producesMidi() const override; 34 | bool isMidiEffect() const override; 35 | double getTailLengthSeconds() const override; 36 | 37 | //============================================================================== 38 | int getNumPrograms() override; 39 | int getCurrentProgram() override; 40 | void setCurrentProgram (int index) override; 41 | const juce::String getProgramName (int index) override; 42 | void changeProgramName (int index, const juce::String& newName) override; 43 | 44 | //============================================================================== 45 | void getStateInformation (juce::MemoryBlock& destData) override; 46 | void setStateInformation (const void* data, int sizeInBytes) override; 47 | 48 | //============================================================================== 49 | juce::AudioProcessorValueTreeState& getProcessorState() { return apvts; } 50 | juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout() const; 51 | 52 | WaveDrawBuffer& getWaveDrawBuffer() { return waveDrawBuffer; } 53 | 54 | private: 55 | //============================================================================== 56 | enum WaveformType 57 | { 58 | kUnknown = -1, 59 | kSine = 0, 60 | kSquare, 61 | kTriangle, 62 | kSaw, 63 | }; 64 | const juce::StringArray waveformTypes{ "Sine", "Square", "Triangle", "Saw"}; 65 | 66 | //============================================================================== 67 | juce::AudioProcessorValueTreeState apvts; 68 | 69 | std::unique_ptr oscillator; 70 | std::unique_ptr tremolo; 71 | 72 | // Wave shape visualizer 73 | WaveDrawBuffer waveDrawBuffer; 74 | WaveSampleCollector waveSampleCollector; 75 | 76 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginAudioProcessor) 77 | }; 78 | -------------------------------------------------------------------------------- /Examples/DrumKit/Source/WaveShapeVisualizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | //============================================================================== 6 | class WaveDrawBuffer 7 | { 8 | public: 9 | WaveDrawBuffer() 10 | { 11 | bufferToDraw.clear(); 12 | } 13 | 14 | void push(const float* dataToPush, const int numSamples) 15 | { 16 | jassert(numSamples <= bufferToDraw.getNumSamples()); 17 | 18 | int start1, size1, start2, size2; 19 | 20 | abstractFifo.prepareToWrite(1, start1, size1, start2, size2); 21 | 22 | if (size1 > 0) 23 | juce::FloatVectorOperations::copy(bufferToDraw.getWritePointer(start1), dataToPush, numSamples); 24 | 25 | abstractFifo.finishedWrite(size1); 26 | } 27 | 28 | void pop(float* outputBuffer, const int numSamples) 29 | { 30 | jassert(numSamples <= bufferToDraw.getNumSamples()); 31 | 32 | int start1, size1, start2, size2; 33 | abstractFifo.prepareToRead(1, start1, size1, start2, size2); 34 | 35 | if (size1 > 0) 36 | { 37 | juce::FloatVectorOperations::copy(outputBuffer, bufferToDraw.getReadPointer(start1), numSamples); 38 | lastReadPointer = start1; 39 | resetCount = 0; 40 | } 41 | else 42 | { 43 | resetCount++; 44 | if (resetCount > resetThreshold) 45 | bufferToDraw.clear(); 46 | 47 | juce::FloatVectorOperations::copy(outputBuffer, bufferToDraw.getReadPointer(lastReadPointer), numSamples); 48 | } 49 | 50 | abstractFifo.finishedRead(size1); 51 | } 52 | 53 | int getBufferSize() const 54 | { 55 | return bufferToDraw.getNumSamples(); 56 | } 57 | 58 | private: 59 | const int numBuffers{ 5 }; 60 | const int bufferSize{ 1024 }; 61 | int lastReadPointer{ 0 }; 62 | const int resetThreshold{ 30 }; 63 | int resetCount{ 0 }; 64 | juce::AbstractFifo abstractFifo{ numBuffers }; 65 | juce::AudioBuffer bufferToDraw{ numBuffers, bufferSize }; 66 | 67 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveDrawBuffer) 68 | }; 69 | 70 | class WaveSampleCollector 71 | { 72 | public: 73 | WaveSampleCollector(WaveDrawBuffer& bufferToUse) 74 | : drawBuffer(bufferToUse) 75 | { 76 | sampleCollecion.setSize(1, drawBuffer.getBufferSize()); 77 | } 78 | 79 | void process(const float* samples, const int numSamples) 80 | { 81 | int index = 0; 82 | 83 | if (state == State::waitingForTrigger) 84 | { 85 | while (index++ < numSamples) 86 | { 87 | const auto currentSample = *samples++; 88 | 89 | if (currentSample >= triggerLevel && prevSample < triggerLevel) 90 | { 91 | numCollected = 0; 92 | state = State::collecting; 93 | break; 94 | } 95 | 96 | prevSample = currentSample; 97 | } 98 | } 99 | 100 | if (state == State::collecting) 101 | { 102 | while (index++ < numSamples) 103 | { 104 | sampleCollecion.getWritePointer(0)[numCollected++] = *samples++; 105 | 106 | if (numCollected == drawBuffer.getBufferSize()) 107 | { 108 | drawBuffer.push(sampleCollecion.getReadPointer(0), sampleCollecion.getNumSamples()); 109 | state = State::waitingForTrigger; 110 | prevSample = 100.0f; 111 | numCollected = 0; 112 | break; 113 | } 114 | } 115 | } 116 | } 117 | 118 | private: 119 | enum class State 120 | { 121 | waitingForTrigger, 122 | collecting 123 | }; 124 | State state{ State::waitingForTrigger }; 125 | 126 | WaveDrawBuffer& drawBuffer; 127 | juce::AudioBuffer sampleCollecion; 128 | int numCollected{ 0 }; 129 | float prevSample = 100.0f; 130 | static constexpr float triggerLevel = 0.001f; 131 | 132 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveSampleCollector) 133 | }; 134 | -------------------------------------------------------------------------------- /Examples/HelloDaisySP/Source/WaveShapeVisualizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | //============================================================================== 6 | class WaveDrawBuffer 7 | { 8 | public: 9 | WaveDrawBuffer() 10 | { 11 | bufferToDraw.clear(); 12 | } 13 | 14 | void push(const float* dataToPush, const int numSamples) 15 | { 16 | jassert(numSamples <= bufferToDraw.getNumSamples()); 17 | 18 | int start1, size1, start2, size2; 19 | 20 | abstractFifo.prepareToWrite(1, start1, size1, start2, size2); 21 | 22 | if (size1 > 0) 23 | juce::FloatVectorOperations::copy(bufferToDraw.getWritePointer(start1), dataToPush, numSamples); 24 | 25 | abstractFifo.finishedWrite(size1); 26 | } 27 | 28 | void pop(float* outputBuffer, const int numSamples) 29 | { 30 | jassert(numSamples <= bufferToDraw.getNumSamples()); 31 | 32 | int start1, size1, start2, size2; 33 | abstractFifo.prepareToRead(1, start1, size1, start2, size2); 34 | 35 | if (size1 > 0) 36 | { 37 | juce::FloatVectorOperations::copy(outputBuffer, bufferToDraw.getReadPointer(start1), numSamples); 38 | lastReadPointer = start1; 39 | resetCount = 0; 40 | } 41 | else 42 | { 43 | resetCount++; 44 | if (resetCount > resetThreshold) 45 | bufferToDraw.clear(); 46 | 47 | juce::FloatVectorOperations::copy(outputBuffer, bufferToDraw.getReadPointer(lastReadPointer), numSamples); 48 | } 49 | 50 | abstractFifo.finishedRead(size1); 51 | } 52 | 53 | int getBufferSize() const 54 | { 55 | return bufferToDraw.getNumSamples(); 56 | } 57 | 58 | private: 59 | const int numBuffers{ 5 }; 60 | const int bufferSize{ 1024 }; 61 | int lastReadPointer{ 0 }; 62 | const int resetThreshold{ 30 }; 63 | int resetCount{ 0 }; 64 | juce::AbstractFifo abstractFifo{ numBuffers }; 65 | juce::AudioBuffer bufferToDraw{ numBuffers, bufferSize }; 66 | 67 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveDrawBuffer) 68 | }; 69 | 70 | class WaveSampleCollector 71 | { 72 | public: 73 | WaveSampleCollector(WaveDrawBuffer& bufferToUse) 74 | : drawBuffer(bufferToUse) 75 | { 76 | sampleCollecion.setSize(1, drawBuffer.getBufferSize()); 77 | } 78 | 79 | void process(const float* samples, const int numSamples) 80 | { 81 | int index = 0; 82 | 83 | if (state == State::waitingForTrigger) 84 | { 85 | while (index++ < numSamples) 86 | { 87 | const auto currentSample = *samples++; 88 | 89 | if (currentSample >= triggerLevel && prevSample < triggerLevel) 90 | { 91 | numCollected = 0; 92 | state = State::collecting; 93 | break; 94 | } 95 | 96 | prevSample = currentSample; 97 | } 98 | } 99 | 100 | if (state == State::collecting) 101 | { 102 | while (index++ < numSamples) 103 | { 104 | sampleCollecion.getWritePointer(0)[numCollected++] = *samples++; 105 | 106 | if (numCollected == drawBuffer.getBufferSize()) 107 | { 108 | drawBuffer.push(sampleCollecion.getReadPointer(0), sampleCollecion.getNumSamples()); 109 | state = State::waitingForTrigger; 110 | prevSample = 100.0f; 111 | numCollected = 0; 112 | break; 113 | } 114 | } 115 | } 116 | } 117 | 118 | private: 119 | enum class State 120 | { 121 | waitingForTrigger, 122 | collecting 123 | }; 124 | State state{ State::waitingForTrigger }; 125 | 126 | WaveDrawBuffer& drawBuffer; 127 | juce::AudioBuffer sampleCollecion; 128 | int numCollected{ 0 }; 129 | float prevSample = 100.0f; 130 | static constexpr float triggerLevel = 0.001f; 131 | 132 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveSampleCollector) 133 | }; 134 | -------------------------------------------------------------------------------- /Examples/DrumKit/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | #include "PluginProcessor.h" 2 | #include "PluginEditor.h" 3 | 4 | //============================================================================== 5 | AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor& p) 6 | : AudioProcessorEditor (&p), processorRef (p) 7 | , buttonBassDrum(std::make_unique("BassDrum", "BassDrum")) 8 | , buttonSnareDrum(std::make_unique("SnareDrum", "SnareDrum")) 9 | , buttonHiHat(std::make_unique("HiHat", "HiHat")) 10 | , midiKeyboard(std::make_unique(processorRef.getMidiKeyboardState(), juce::MidiKeyboardComponent::Orientation::horizontalKeyboard)) 11 | { 12 | addAndMakeVisible(midiKeyboard.get()); 13 | 14 | buttonBassDrum->onClick = [this]() { 15 | processorRef.triggerBassDrum(); 16 | }; 17 | addAndMakeVisible(buttonBassDrum.get()); 18 | 19 | buttonSnareDrum->onClick = [this]() { 20 | processorRef.triggerSnareDrum(); 21 | }; 22 | addAndMakeVisible(buttonSnareDrum.get()); 23 | 24 | buttonHiHat->onClick = [this]() { 25 | processorRef.triggerHiHat(); 26 | }; 27 | addAndMakeVisible(buttonHiHat.get()); 28 | 29 | setSize(600, 400); 30 | 31 | startTimerHz(30); 32 | } 33 | 34 | AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor() 35 | { 36 | sliderAttachments.clear(); 37 | comboboxAttachments.clear(); 38 | } 39 | 40 | //============================================================================== 41 | void drawWaveShape(juce::Graphics& g, const juce::Rectangle& drawArea, const float* plotData, const int numSamples) 42 | { 43 | juce::Path wavePath; 44 | const float x0 = drawArea.getX(); 45 | const float cloped_sample0 = juce::jmax(-1.0f, juce::jmin(1.0f, plotData[0])); 46 | const float y0 = juce::jmap(cloped_sample0, -1.0f, 1.0f, drawArea.getBottom(), drawArea.getY()); 47 | wavePath.startNewSubPath(x0, y0); 48 | 49 | for (int i = 1; i < numSamples; ++i) 50 | { 51 | const float x = juce::jmap(i, 0, numSamples, x0, x0 + drawArea.getWidth()); 52 | const float cloped_sample = juce::jmax(-1.0f, juce::jmin(1.0f, plotData[i])); 53 | const float y = juce::jmap(cloped_sample, -1.0f, 1.0f, drawArea.getBottom(), drawArea.getY()); 54 | wavePath.lineTo(x, y); 55 | } 56 | g.setColour(juce::Colours::cyan); 57 | g.strokePath(wavePath, juce::PathStrokeType(2.0f)); 58 | } 59 | 60 | void AudioPluginAudioProcessorEditor::paint (juce::Graphics& g) 61 | { 62 | g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId)); 63 | 64 | const auto bounds = getLocalBounds(); 65 | 66 | // Draw wave shape background 67 | const juce::Rectangle drawArea = { bounds.getWidth() * 0.1f, bounds.getHeight() * 0.5f, bounds.getWidth() * 0.8f, bounds.getHeight() * 0.225f }; 68 | g.setColour(juce::Colours::darkgrey); 69 | g.fillRect(drawArea); 70 | 71 | // Draw wave shape 72 | juce::AudioBuffer samplesToDraw(1, processorRef.getWaveDrawBuffer().getBufferSize()); 73 | processorRef.getWaveDrawBuffer().pop(samplesToDraw.getWritePointer(0), samplesToDraw.getNumSamples()); 74 | drawWaveShape(g, drawArea, samplesToDraw.getWritePointer(0), samplesToDraw.getNumSamples()); 75 | } 76 | 77 | void AudioPluginAudioProcessorEditor::resized() 78 | { 79 | auto bounds = getLocalBounds(); 80 | 81 | rectDrumKit = juce::Rectangle(bounds.getWidth() * 0.05f, bounds.getHeight() * 0.015f, bounds.getWidth() * 0.9f, bounds.getHeight() * 0.35f); 82 | 83 | const int button_w = bounds.getWidth() * 0.2f; 84 | const int button_h = bounds.getHeight() * 0.2f; 85 | 86 | { 87 | buttonBassDrum->setSize(button_w, button_h); 88 | buttonBassDrum->setCentrePosition( 89 | rectDrumKit.getX() + rectDrumKit.getWidth() * 0.2f, 90 | rectDrumKit.getY() + rectDrumKit.getHeight() * 0.6f); 91 | 92 | buttonSnareDrum->setSize(button_w, button_h); 93 | buttonSnareDrum->setCentrePosition( 94 | rectDrumKit.getX() + rectDrumKit.getWidth() * 0.5f, 95 | rectDrumKit.getY() + rectDrumKit.getHeight() * 0.6f); 96 | 97 | buttonHiHat->setSize(button_w, button_h); 98 | buttonHiHat->setCentrePosition( 99 | rectDrumKit.getX() + rectDrumKit.getWidth() * 0.8f, 100 | rectDrumKit.getY() + rectDrumKit.getHeight() * 0.6f); 101 | } 102 | 103 | const auto rect_keyboard = juce::Rectangle(bounds.getWidth() * 0.0f, bounds.getHeight() * 0.8f, bounds.getWidth() * 1.0f, bounds.getHeight() * 0.2f); 104 | midiKeyboard->setBounds(rect_keyboard); 105 | } 106 | 107 | void AudioPluginAudioProcessorEditor::timerCallback() 108 | { 109 | repaint(); 110 | } 111 | -------------------------------------------------------------------------------- /Examples/DrumKit/DrumKit.cmake: -------------------------------------------------------------------------------- 1 | juce_add_plugin(DrumKit 2 | # VERSION ... # Set this if the plugin version is different to the project version 3 | # ICON_BIG ... # ICON_* arguments specify a path to an image file to use as an icon for the Standalone 4 | # ICON_SMALL ... 5 | COMPANY_NAME "COCOTONE" # Specify the name of the plugin's author 6 | IS_SYNTH TRUE # Is this a synth or an effect? 7 | NEEDS_MIDI_INPUT FALSE # Does the plugin need midi input? 8 | NEEDS_MIDI_OUTPUT FALSE # Does the plugin need midi output? 9 | IS_MIDI_EFFECT FALSE # Is this plugin a MIDI effect? 10 | EDITOR_WANTS_KEYBOARD_FOCUS FALSE # Does the editor need keyboard focus? 11 | COPY_PLUGIN_AFTER_BUILD FALSE # Should the plugin be installed to a default location after building? 12 | PLUGIN_MANUFACTURER_CODE Coco # A four-character manufacturer id with at least one upper-case character 13 | PLUGIN_CODE Drum # A unique four-character plugin id with exactly one upper-case character 14 | # GarageBand 10.3 requires the first letter to be upper-case, and the remaining letters to be lower-case 15 | FORMATS AU VST3 Standalone # The formats to build. Other valid formats are: AAX Unity VST AU AUv3 16 | PRODUCT_NAME "DrumKit") # The name of the final executable, which can differ from the target name 17 | 18 | juce_generate_juce_header(DrumKit) 19 | 20 | ### Find source files with generate source groups ### 21 | # Test that the target_path should be excluded 22 | # if the path is excluded, IS_EXCLUDED will be set to TRUE 23 | function(TEST_EXCLUDED TARGET_PATH EXCLUSION_PATTERN_LIST IS_EXCLUDED) 24 | set(IS_EXCLUDED FALSE PARENT_SCOPE) 25 | foreach(PAT ${EXCLUSION_PATTERN_LIST}) 26 | if(TARGET_PATH MATCHES ${PAT}) 27 | set(IS_EXCLUDED TRUE PARENT_SCOPE) 28 | break() 29 | endif() 30 | endforeach() 31 | endfunction() 32 | 33 | # set target extension list 34 | set(TARGET_SOURCE_PATTERNS "*.c" "*.cc" "*.cpp" "*.h" "*.hpp") 35 | 36 | if(MSVC) 37 | list(APPEND TARGET_SOURCE_PATTERNS "*.rc") 38 | elseif(XCODE) 39 | list(APPEND TARGET_SOURCE_PATTERNS "*.mm") 40 | endif() 41 | 42 | # generate_source_list( 43 | # output 44 | # BASE_DIR dir 45 | # SOURCE_DIRS dir1 [dir2...] 46 | # EXCLUSION_PATTERNS [pattern1...] 47 | # ) 48 | function(generate_source_list output) 49 | cmake_parse_arguments(GSL "" "BASE_DIR" "SOURCE_DIRS;EXCLUSION_PATTERNS" ${ARGN}) 50 | 51 | if(NOT DEFINED GSL_BASE_DIR) 52 | message(FATAL_ERROR "BASE_DIR must be specified.") 53 | endif() 54 | 55 | if(NOT DEFINED GSL_SOURCE_DIRS) 56 | message(FATAL_ERROR "SOURCE_DIRS must be specified.") 57 | endif() 58 | 59 | message("Print EXCLUSION_PATTERNS: ${GSL_EXCLUSION_PATTERNS}") 60 | 61 | # set the root directory of the source file tree in IDE 62 | get_filename_component(GSL_BASE_DIR "${GSL_BASE_DIR}" ABSOLUTE) 63 | 64 | foreach(SOURCE_DIR ${GSL_SOURCE_DIRS}) 65 | get_filename_component(SOURCE_DIR "${SOURCE_DIR}" ABSOLUTE) 66 | 67 | set(PATTERNS "") 68 | foreach(PATTERN ${TARGET_SOURCE_PATTERNS}) 69 | list(APPEND PATTERNS "${SOURCE_DIR}/${PATTERN}") 70 | endforeach() 71 | 72 | file(GLOB_RECURSE FILES ${PATTERNS}) 73 | 74 | # Define SourceGroup reflecting filesystem hierarchy. 75 | foreach(FILE_PATH ${FILES}) 76 | get_filename_component(FILEPATH "${FILE_PATH}" ABSOLUTE) 77 | TEST_EXCLUDED(${FILE_PATH} "${GSL_EXCLUSION_PATTERNS}" IS_EXCLUDED) 78 | if(IS_EXCLUDED) 79 | continue() 80 | endif() 81 | 82 | get_filename_component(PARENT_DIR "${FILE_PATH}" DIRECTORY) 83 | file(RELATIVE_PATH GROUP_NAME "${GSL_BASE_DIR}" "${PARENT_DIR}") 84 | string(REPLACE "/" "\\" GROUP_NAME "${GROUP_NAME}") 85 | source_group("${GROUP_NAME}" FILES "${FILE_PATH}") 86 | list(APPEND SOURCE_FILES "${FILE_PATH}") 87 | endforeach() 88 | endforeach() 89 | 90 | message("Files ${SOURCE_FILES}") 91 | set(${output} "${SOURCE_FILES}" PARENT_SCOPE) 92 | endfunction() 93 | 94 | generate_source_list( 95 | DrumKit_SOURCES 96 | BASE_DIR "${CMAKE_CURRENT_LIST_DIR}/Source" 97 | SOURCE_DIRS "${CMAKE_CURRENT_LIST_DIR}/Source" 98 | # EXCLUSION_PATTERNS ".+/Test/.+" 99 | ) 100 | 101 | target_sources(DrumKit 102 | PRIVATE 103 | ${DrumKit_SOURCES} 104 | ) 105 | 106 | ###^^^ Find source files with generate source groups ^^^### 107 | 108 | target_compile_definitions(DrumKit 109 | PUBLIC 110 | # JUCE_WEB_BROWSER and JUCE_USE_CURL would be on by default, but you might not need them. 111 | JUCE_WEB_BROWSER=0 # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call 112 | JUCE_USE_CURL=0 # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call 113 | JUCE_VST3_CAN_REPLACE_VST2=0 114 | JUCE_DISPLAY_SPLASH_SCREEN=0) 115 | 116 | target_link_libraries(DrumKit 117 | PRIVATE 118 | DaisySP 119 | juce::juce_audio_utils 120 | PUBLIC 121 | juce::juce_recommended_config_flags 122 | juce::juce_recommended_lto_flags 123 | juce::juce_recommended_warning_flags) 124 | -------------------------------------------------------------------------------- /Examples/HelloDaisySP/HelloDaisySP.cmake: -------------------------------------------------------------------------------- 1 | juce_add_plugin(HelloDaisySP 2 | # VERSION ... # Set this if the plugin version is different to the project version 3 | # ICON_BIG ... # ICON_* arguments specify a path to an image file to use as an icon for the Standalone 4 | # ICON_SMALL ... 5 | COMPANY_NAME "COCOTONE" # Specify the name of the plugin's author 6 | IS_SYNTH TRUE # Is this a synth or an effect? 7 | NEEDS_MIDI_INPUT FALSE # Does the plugin need midi input? 8 | NEEDS_MIDI_OUTPUT FALSE # Does the plugin need midi output? 9 | IS_MIDI_EFFECT FALSE # Is this plugin a MIDI effect? 10 | EDITOR_WANTS_KEYBOARD_FOCUS FALSE # Does the editor need keyboard focus? 11 | COPY_PLUGIN_AFTER_BUILD FALSE # Should the plugin be installed to a default location after building? 12 | PLUGIN_MANUFACTURER_CODE Coco # A four-character manufacturer id with at least one upper-case character 13 | PLUGIN_CODE Jdsp # A unique four-character plugin id with exactly one upper-case character 14 | # GarageBand 10.3 requires the first letter to be upper-case, and the remaining letters to be lower-case 15 | FORMATS AU VST3 Standalone # The formats to build. Other valid formats are: AAX Unity VST AU AUv3 16 | PRODUCT_NAME "JUCE and DaisySP Example") # The name of the final executable, which can differ from the target name 17 | 18 | juce_generate_juce_header(HelloDaisySP) 19 | 20 | ### Find source files with generate source groups ### 21 | # Test that the target_path should be excluded 22 | # if the path is excluded, IS_EXCLUDED will be set to TRUE 23 | function(TEST_EXCLUDED TARGET_PATH EXCLUSION_PATTERN_LIST IS_EXCLUDED) 24 | set(IS_EXCLUDED FALSE PARENT_SCOPE) 25 | foreach(PAT ${EXCLUSION_PATTERN_LIST}) 26 | if(TARGET_PATH MATCHES ${PAT}) 27 | set(IS_EXCLUDED TRUE PARENT_SCOPE) 28 | break() 29 | endif() 30 | endforeach() 31 | endfunction() 32 | 33 | # set target extension list 34 | set(TARGET_SOURCE_PATTERNS "*.c" "*.cc" "*.cpp" "*.h" "*.hpp") 35 | 36 | if(MSVC) 37 | list(APPEND TARGET_SOURCE_PATTERNS "*.rc") 38 | elseif(XCODE) 39 | list(APPEND TARGET_SOURCE_PATTERNS "*.mm") 40 | endif() 41 | 42 | # generate_source_list( 43 | # output 44 | # BASE_DIR dir 45 | # SOURCE_DIRS dir1 [dir2...] 46 | # EXCLUSION_PATTERNS [pattern1...] 47 | # ) 48 | function(generate_source_list output) 49 | cmake_parse_arguments(GSL "" "BASE_DIR" "SOURCE_DIRS;EXCLUSION_PATTERNS" ${ARGN}) 50 | 51 | if(NOT DEFINED GSL_BASE_DIR) 52 | message(FATAL_ERROR "BASE_DIR must be specified.") 53 | endif() 54 | 55 | if(NOT DEFINED GSL_SOURCE_DIRS) 56 | message(FATAL_ERROR "SOURCE_DIRS must be specified.") 57 | endif() 58 | 59 | message("Print EXCLUSION_PATTERNS: ${GSL_EXCLUSION_PATTERNS}") 60 | 61 | # set the root directory of the source file tree in IDE 62 | get_filename_component(GSL_BASE_DIR "${GSL_BASE_DIR}" ABSOLUTE) 63 | 64 | foreach(SOURCE_DIR ${GSL_SOURCE_DIRS}) 65 | get_filename_component(SOURCE_DIR "${SOURCE_DIR}" ABSOLUTE) 66 | 67 | set(PATTERNS "") 68 | foreach(PATTERN ${TARGET_SOURCE_PATTERNS}) 69 | list(APPEND PATTERNS "${SOURCE_DIR}/${PATTERN}") 70 | endforeach() 71 | 72 | file(GLOB_RECURSE FILES ${PATTERNS}) 73 | 74 | # Define SourceGroup reflecting filesystem hierarchy. 75 | foreach(FILE_PATH ${FILES}) 76 | get_filename_component(FILEPATH "${FILE_PATH}" ABSOLUTE) 77 | TEST_EXCLUDED(${FILE_PATH} "${GSL_EXCLUSION_PATTERNS}" IS_EXCLUDED) 78 | if(IS_EXCLUDED) 79 | continue() 80 | endif() 81 | 82 | get_filename_component(PARENT_DIR "${FILE_PATH}" DIRECTORY) 83 | file(RELATIVE_PATH GROUP_NAME "${GSL_BASE_DIR}" "${PARENT_DIR}") 84 | string(REPLACE "/" "\\" GROUP_NAME "${GROUP_NAME}") 85 | source_group("${GROUP_NAME}" FILES "${FILE_PATH}") 86 | list(APPEND SOURCE_FILES "${FILE_PATH}") 87 | endforeach() 88 | endforeach() 89 | 90 | message("Files ${SOURCE_FILES}") 91 | set(${output} "${SOURCE_FILES}" PARENT_SCOPE) 92 | endfunction() 93 | 94 | generate_source_list( 95 | HelloDaisySP_SOURCES 96 | BASE_DIR "${CMAKE_CURRENT_LIST_DIR}/Source" 97 | SOURCE_DIRS "${CMAKE_CURRENT_LIST_DIR}/Source" 98 | # EXCLUSION_PATTERNS ".+/Test/.+" 99 | ) 100 | 101 | target_sources(HelloDaisySP 102 | PRIVATE 103 | ${HelloDaisySP_SOURCES} 104 | ) 105 | 106 | ###^^^ Find source files with generate source groups ^^^### 107 | 108 | target_compile_definitions(HelloDaisySP 109 | PUBLIC 110 | # JUCE_WEB_BROWSER and JUCE_USE_CURL would be on by default, but you might not need them. 111 | JUCE_WEB_BROWSER=0 # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call 112 | JUCE_USE_CURL=0 # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call 113 | JUCE_VST3_CAN_REPLACE_VST2=0 114 | JUCE_DISPLAY_SPLASH_SCREEN=0) 115 | 116 | target_link_libraries(HelloDaisySP 117 | PRIVATE 118 | DaisySP 119 | juce::juce_audio_utils 120 | PUBLIC 121 | juce::juce_recommended_config_flags 122 | juce::juce_recommended_lto_flags 123 | juce::juce_recommended_warning_flags) 124 | -------------------------------------------------------------------------------- /Examples/DrumKit/Source/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | #include "PluginProcessor.h" 2 | #include "PluginEditor.h" 3 | 4 | 5 | 6 | //============================================================================== 7 | AudioPluginAudioProcessor::AudioPluginAudioProcessor() 8 | : AudioProcessor (BusesProperties() 9 | #if ! JucePlugin_IsMidiEffect 10 | #if ! JucePlugin_IsSynth 11 | .withInput ("Input", juce::AudioChannelSet::stereo(), true) 12 | #endif 13 | .withOutput ("Output", juce::AudioChannelSet::stereo(), true) 14 | #endif 15 | ) 16 | , apvts(*this, nullptr, "PARAMETERS", createParameterLayout()) 17 | , waveSampleCollector(waveDrawBuffer) 18 | { 19 | bassdrum = std::make_unique(); 20 | snaredrum = std::make_unique(); 21 | hihat = std::make_unique>(); 22 | } 23 | 24 | AudioPluginAudioProcessor::~AudioPluginAudioProcessor() 25 | { 26 | } 27 | 28 | //============================================================================== 29 | const juce::String AudioPluginAudioProcessor::getName() const 30 | { 31 | return JucePlugin_Name; 32 | } 33 | 34 | bool AudioPluginAudioProcessor::acceptsMidi() const 35 | { 36 | #if JucePlugin_WantsMidiInput 37 | return true; 38 | #else 39 | return false; 40 | #endif 41 | } 42 | 43 | bool AudioPluginAudioProcessor::producesMidi() const 44 | { 45 | #if JucePlugin_ProducesMidiOutput 46 | return true; 47 | #else 48 | return false; 49 | #endif 50 | } 51 | 52 | bool AudioPluginAudioProcessor::isMidiEffect() const 53 | { 54 | #if JucePlugin_IsMidiEffect 55 | return true; 56 | #else 57 | return false; 58 | #endif 59 | } 60 | 61 | double AudioPluginAudioProcessor::getTailLengthSeconds() const 62 | { 63 | return 0.0; 64 | } 65 | 66 | int AudioPluginAudioProcessor::getNumPrograms() 67 | { 68 | return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, 69 | // so this should be at least 1, even if you're not really implementing programs. 70 | } 71 | 72 | int AudioPluginAudioProcessor::getCurrentProgram() 73 | { 74 | return 0; 75 | } 76 | 77 | void AudioPluginAudioProcessor::setCurrentProgram (int index) 78 | { 79 | juce::ignoreUnused (index); 80 | } 81 | 82 | const juce::String AudioPluginAudioProcessor::getProgramName (int index) 83 | { 84 | juce::ignoreUnused (index); 85 | return {}; 86 | } 87 | 88 | void AudioPluginAudioProcessor::changeProgramName (int index, const juce::String& newName) 89 | { 90 | juce::ignoreUnused (index, newName); 91 | } 92 | 93 | //============================================================================== 94 | void AudioPluginAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) 95 | { 96 | // Use this method as the place to do any pre-playback 97 | // initialisation that you need.. 98 | juce::ignoreUnused (sampleRate, samplesPerBlock); 99 | 100 | bassdrum->Init(sampleRate); 101 | snaredrum->Init(sampleRate); 102 | hihat->Init(sampleRate); 103 | } 104 | 105 | void AudioPluginAudioProcessor::releaseResources() 106 | { 107 | // When playback stops, you can use this as an opportunity to free up any 108 | // spare memory, etc. 109 | } 110 | 111 | bool AudioPluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const 112 | { 113 | #if JucePlugin_IsMidiEffect 114 | juce::ignoreUnused (layouts); 115 | return true; 116 | #else 117 | // This is the place where you check if the layout is supported. 118 | // In this template code we only support mono or stereo. 119 | // Some plugin hosts, such as certain GarageBand versions, will only 120 | // load plugins that support stereo bus layouts. 121 | if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() 122 | && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) 123 | return false; 124 | 125 | // This checks if the input layout matches the output layout 126 | #if ! JucePlugin_IsSynth 127 | if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) 128 | return false; 129 | #endif 130 | 131 | return true; 132 | #endif 133 | } 134 | 135 | void AudioPluginAudioProcessor::processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) 136 | { 137 | keyboardState.processNextMidiBuffer(midiMessages, 0, buffer.getNumSamples(), true); 138 | 139 | for (const auto iter : midiMessages) 140 | { 141 | const auto msg = iter.getMessage(); 142 | if (msg.getNoteNumber() == 36) 143 | { 144 | if (msg.isNoteOn()) 145 | { 146 | bassdrum->Trig(); 147 | } 148 | } 149 | else if (msg.getNoteNumber() == 38) 150 | { 151 | if (msg.isNoteOn()) 152 | { 153 | snaredrum->Trig(); 154 | } 155 | } 156 | else if (msg.getNoteNumber() == 42) 157 | { 158 | if (msg.isNoteOn()) 159 | { 160 | hihat->Trig(); 161 | } 162 | } 163 | } 164 | 165 | 166 | juce::ScopedNoDenormals noDenormals; 167 | auto totalNumInputChannels = getTotalNumInputChannels(); 168 | auto totalNumOutputChannels = getTotalNumOutputChannels(); 169 | 170 | // In case we have more outputs than inputs, this code clears any output 171 | // channels that didn't contain input data, (because these aren't 172 | // guaranteed to be empty - they may contain garbage). 173 | // This is here to avoid people getting screaming feedback 174 | // when they first compile a plugin, but obviously you don't need to keep 175 | // this code if your algorithm always overwrites all the output channels. 176 | for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) 177 | buffer.clear(i, 0, buffer.getNumSamples()); 178 | 179 | for (int sample_idx = 0; sample_idx < buffer.getNumSamples(); sample_idx++) 180 | { 181 | const float sample_bd = bassdrum->Process(); 182 | const float sample_sd = snaredrum->Process(); 183 | const float sample_hh = hihat->Process(); 184 | 185 | for (int ch_idx = 0; ch_idx < buffer.getNumChannels(); ch_idx++) 186 | { 187 | buffer.getWritePointer(ch_idx)[sample_idx] = sample_bd + sample_sd + sample_hh; 188 | } 189 | } 190 | 191 | // Collect wave sample 192 | waveSampleCollector.process(buffer.getReadPointer(0), buffer.getNumSamples()); 193 | } 194 | 195 | //============================================================================== 196 | bool AudioPluginAudioProcessor::hasEditor() const 197 | { 198 | return true; // (change this to false if you choose to not supply an editor) 199 | } 200 | 201 | juce::AudioProcessorEditor* AudioPluginAudioProcessor::createEditor() 202 | { 203 | return new AudioPluginAudioProcessorEditor (*this); 204 | } 205 | 206 | //============================================================================== 207 | void AudioPluginAudioProcessor::getStateInformation (juce::MemoryBlock& destData) 208 | { 209 | // You should use this method to store your parameters in the memory block. 210 | // You could do that either as raw data, or use the XML or ValueTree classes 211 | // as intermediaries to make it easy to save and load complex data. 212 | juce::ignoreUnused (destData); 213 | } 214 | 215 | void AudioPluginAudioProcessor::setStateInformation (const void* data, int sizeInBytes) 216 | { 217 | // You should use this method to restore your parameters from this memory block, 218 | // whose contents will have been created by the getStateInformation() call. 219 | juce::ignoreUnused (data, sizeInBytes); 220 | } 221 | 222 | //============================================================================== 223 | juce::AudioProcessorValueTreeState::ParameterLayout AudioPluginAudioProcessor::createParameterLayout() const 224 | { 225 | juce::AudioProcessorValueTreeState::ParameterLayout layout; 226 | return layout; 227 | } 228 | 229 | void AudioPluginAudioProcessor::triggerBassDrum() 230 | { 231 | bassdrum->Trig(); 232 | } 233 | 234 | void AudioPluginAudioProcessor::triggerSnareDrum() 235 | { 236 | snaredrum->Trig(); 237 | } 238 | 239 | void AudioPluginAudioProcessor::triggerHiHat() 240 | { 241 | hihat->Trig(); 242 | } 243 | 244 | //============================================================================== 245 | // This creates new instances of the plugin.. 246 | juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() 247 | { 248 | return new AudioPluginAudioProcessor(); 249 | } 250 | -------------------------------------------------------------------------------- /Examples/HelloDaisySP/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | #include "PluginProcessor.h" 2 | #include "PluginEditor.h" 3 | 4 | //============================================================================== 5 | AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor (AudioPluginAudioProcessor& p) 6 | : AudioProcessorEditor (&p), processorRef (p) 7 | , groupOscillator(std::make_unique("Oscillator", "Oscillator")) 8 | , sliderGain(std::make_unique(juce::Slider::RotaryHorizontalVerticalDrag, juce::Slider::TextBoxBelow)) 9 | , sliderFrequency(std::make_unique(juce::Slider::RotaryHorizontalVerticalDrag, juce::Slider::TextBoxBelow)) 10 | , comboboxWaveform(std::make_unique()) 11 | , labelGain(std::make_unique("Gain", "GAIN")) 12 | , labelFrequency(std::make_unique("Frequency", "FREQ")) 13 | , labelWaveform(std::make_unique("Waveform", "WAVE")) 14 | , groupTremolo(std::make_unique("Tremolo", "Tremolo")) 15 | , sliderTremoloFrequnency(std::make_unique(juce::Slider::RotaryHorizontalVerticalDrag, juce::Slider::TextBoxBelow)) 16 | , sliderTremoloDepth(std::make_unique(juce::Slider::RotaryHorizontalVerticalDrag, juce::Slider::TextBoxBelow)) 17 | , comboboxTremoloWaveform(std::make_unique()) 18 | , labelTremoloFrequency(std::make_unique("Frequency", "FREQ")) 19 | , labelTremoloDepth(std::make_unique("Depth", "DEPTH")) 20 | , labelTremoloWaveform(std::make_unique("Waveform", "WAVE")) 21 | { 22 | addAndMakeVisible(groupOscillator.get()); 23 | 24 | labelGain->attachToComponent(sliderGain.get(), false); 25 | labelGain->setJustificationType(juce::Justification::centred); 26 | 27 | sliderAttachments.add(std::make_unique 28 | (processorRef.getProcessorState(), "Gain", *sliderGain)); 29 | addAndMakeVisible(sliderGain.get()); 30 | 31 | labelFrequency->attachToComponent(sliderFrequency.get(), false); 32 | labelFrequency->setJustificationType(juce::Justification::centred); 33 | 34 | sliderAttachments.add(std::make_unique 35 | (processorRef.getProcessorState(), "Frequency", *sliderFrequency)); 36 | addAndMakeVisible(sliderFrequency.get()); 37 | 38 | labelWaveform->attachToComponent(comboboxWaveform.get(), false); 39 | labelWaveform->setJustificationType(juce::Justification::centred); 40 | 41 | comboboxAttachments.add(std::make_unique 42 | (processorRef.getProcessorState(), "Waveform", *comboboxWaveform)); 43 | comboboxWaveform->setJustificationType(juce::Justification::centred); 44 | comboboxWaveform->addItemList(processorRef.getProcessorState().getParameter("Waveform")->getAllValueStrings(), 1); 45 | comboboxWaveform->setText(processorRef.getProcessorState().getParameter("Waveform")->getCurrentValueAsText()); 46 | addAndMakeVisible(comboboxWaveform.get()); 47 | 48 | addAndMakeVisible(groupTremolo.get()); 49 | 50 | labelTremoloFrequency->attachToComponent(sliderTremoloFrequnency.get(), false); 51 | labelTremoloFrequency->setJustificationType(juce::Justification::centred); 52 | 53 | sliderAttachments.add(std::make_unique 54 | (processorRef.getProcessorState(), "TremoloFrequency", *sliderTremoloFrequnency)); 55 | addAndMakeVisible(sliderTremoloFrequnency.get()); 56 | 57 | labelTremoloDepth->attachToComponent(sliderTremoloDepth.get(), false); 58 | labelTremoloDepth->setJustificationType(juce::Justification::centred); 59 | 60 | sliderAttachments.add(std::make_unique 61 | (processorRef.getProcessorState(), "TremoloDepth", *sliderTremoloDepth)); 62 | addAndMakeVisible(sliderTremoloDepth.get()); 63 | 64 | labelTremoloWaveform->attachToComponent(comboboxTremoloWaveform.get(), false); 65 | labelTremoloWaveform->setJustificationType(juce::Justification::centred); 66 | 67 | comboboxAttachments.add(std::make_unique 68 | (processorRef.getProcessorState(), "TremoloWaveform", *comboboxTremoloWaveform)); 69 | comboboxTremoloWaveform->setJustificationType(juce::Justification::centred); 70 | comboboxTremoloWaveform->addItemList(processorRef.getProcessorState().getParameter("TremoloWaveform")->getAllValueStrings(), 1); 71 | comboboxTremoloWaveform->setText(processorRef.getProcessorState().getParameter("TremoloWaveform")->getCurrentValueAsText()); 72 | addAndMakeVisible(comboboxTremoloWaveform.get()); 73 | 74 | 75 | setSize(600, 400); 76 | 77 | startTimerHz(30); 78 | } 79 | 80 | AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor() 81 | { 82 | sliderAttachments.clear(); 83 | comboboxAttachments.clear(); 84 | } 85 | 86 | //============================================================================== 87 | void drawWaveShape(juce::Graphics& g, const juce::Rectangle& drawArea, const float* plotData, const int numSamples) 88 | { 89 | juce::Path wavePath; 90 | const float x0 = drawArea.getX(); 91 | const float cloped_sample0 = juce::jmax(-1.0f, juce::jmin(1.0f, plotData[0])); 92 | const float y0 = juce::jmap(cloped_sample0, -1.0f, 1.0f, drawArea.getBottom(), drawArea.getY()); 93 | wavePath.startNewSubPath(x0, y0); 94 | 95 | for (int i = 1; i < numSamples; ++i) 96 | { 97 | const float x = juce::jmap(i, 0, numSamples, x0, x0 + drawArea.getWidth()); 98 | const float cloped_sample = juce::jmax(-1.0f, juce::jmin(1.0f, plotData[i])); 99 | const float y = juce::jmap(cloped_sample, -1.0f, 1.0f, drawArea.getBottom(), drawArea.getY()); 100 | wavePath.lineTo(x, y); 101 | } 102 | g.setColour(juce::Colours::cyan); 103 | g.strokePath(wavePath, juce::PathStrokeType(2.0f)); 104 | } 105 | 106 | void AudioPluginAudioProcessorEditor::paint (juce::Graphics& g) 107 | { 108 | g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId)); 109 | 110 | const auto bounds = getLocalBounds(); 111 | 112 | // Draw wave shape background 113 | const juce::Rectangle drawArea = { bounds.getWidth() * 0.1f, bounds.getHeight() * 0.75f, bounds.getWidth() * 0.8f, bounds.getHeight() * 0.225f }; 114 | g.setColour(juce::Colours::darkgrey); 115 | g.fillRect(drawArea); 116 | 117 | // Draw wave shape 118 | juce::AudioBuffer samplesToDraw(1, processorRef.getWaveDrawBuffer().getBufferSize()); 119 | processorRef.getWaveDrawBuffer().pop(samplesToDraw.getWritePointer(0), samplesToDraw.getNumSamples()); 120 | drawWaveShape(g, drawArea, samplesToDraw.getWritePointer(0), samplesToDraw.getNumSamples()); 121 | } 122 | 123 | void AudioPluginAudioProcessorEditor::resized() 124 | { 125 | auto bounds = getLocalBounds(); 126 | 127 | groupOscillator->setBounds(juce::Rectangle(bounds.getWidth() * 0.05f, bounds.getHeight() * 0.015f, bounds.getWidth() * 0.9f, bounds.getHeight() * 0.35f)); 128 | 129 | groupTremolo->setBounds(juce::Rectangle(bounds.getWidth() * 0.05f, bounds.getHeight() * 0.38f, bounds.getWidth() * 0.9f, bounds.getHeight() * 0.35f)); 130 | 131 | const int slider_w = bounds.getWidth() * 0.2f; 132 | const int slider_h = bounds.getHeight() * 0.2f; 133 | const int combobox_w = bounds.getWidth() * 0.2f; 134 | const int combobox_h = bounds.getHeight() * 0.1f; 135 | 136 | { 137 | sliderGain->setSize(slider_w, slider_h); 138 | sliderGain->setCentrePosition( 139 | groupOscillator->getX() + groupOscillator->getWidth() * 0.2f, 140 | groupOscillator->getY() + groupOscillator->getHeight() * 0.6f); 141 | 142 | comboboxWaveform->setSize(combobox_w, combobox_h); 143 | comboboxWaveform->setCentrePosition( 144 | groupOscillator->getX() + groupOscillator->getWidth() * 0.5f, 145 | groupOscillator->getY() + groupOscillator->getHeight() * 0.5f); 146 | 147 | sliderFrequency->setSize(slider_w, slider_h); 148 | sliderFrequency->setCentrePosition( 149 | groupOscillator->getX() + groupOscillator->getWidth() * 0.8f, 150 | groupOscillator->getY() + groupOscillator->getHeight() * 0.6f); 151 | } 152 | 153 | { 154 | sliderTremoloFrequnency->setSize(slider_w, slider_h); 155 | sliderTremoloFrequnency->setCentrePosition( 156 | groupTremolo->getX() + groupTremolo->getWidth() * 0.2f, 157 | groupTremolo->getY() + groupTremolo->getHeight() * 0.6f); 158 | 159 | comboboxTremoloWaveform->setSize(combobox_w, combobox_h); 160 | comboboxTremoloWaveform->setCentrePosition( 161 | groupTremolo->getX() + groupTremolo->getWidth() * 0.5f, 162 | groupTremolo->getY() + groupTremolo->getHeight() * 0.5f); 163 | 164 | sliderTremoloDepth->setSize(slider_w, slider_h); 165 | sliderTremoloDepth->setCentrePosition( 166 | groupTremolo->getX() + groupTremolo->getWidth() * 0.8f, 167 | groupTremolo->getY() + groupTremolo->getHeight() * 0.6f); 168 | } 169 | } 170 | 171 | void AudioPluginAudioProcessorEditor::timerCallback() 172 | { 173 | repaint(); 174 | } 175 | -------------------------------------------------------------------------------- /Examples/HelloDaisySP/Source/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | #include "PluginProcessor.h" 2 | #include "PluginEditor.h" 3 | 4 | 5 | 6 | //============================================================================== 7 | AudioPluginAudioProcessor::AudioPluginAudioProcessor() 8 | : AudioProcessor (BusesProperties() 9 | #if ! JucePlugin_IsMidiEffect 10 | #if ! JucePlugin_IsSynth 11 | .withInput ("Input", juce::AudioChannelSet::stereo(), true) 12 | #endif 13 | .withOutput ("Output", juce::AudioChannelSet::stereo(), true) 14 | #endif 15 | ) 16 | , apvts(*this, nullptr, "PARAMETERS", createParameterLayout()) 17 | , waveSampleCollector(waveDrawBuffer) 18 | { 19 | oscillator = std::make_unique(); 20 | tremolo = std::make_unique(); 21 | } 22 | 23 | AudioPluginAudioProcessor::~AudioPluginAudioProcessor() 24 | { 25 | } 26 | 27 | //============================================================================== 28 | const juce::String AudioPluginAudioProcessor::getName() const 29 | { 30 | return JucePlugin_Name; 31 | } 32 | 33 | bool AudioPluginAudioProcessor::acceptsMidi() const 34 | { 35 | #if JucePlugin_WantsMidiInput 36 | return true; 37 | #else 38 | return false; 39 | #endif 40 | } 41 | 42 | bool AudioPluginAudioProcessor::producesMidi() const 43 | { 44 | #if JucePlugin_ProducesMidiOutput 45 | return true; 46 | #else 47 | return false; 48 | #endif 49 | } 50 | 51 | bool AudioPluginAudioProcessor::isMidiEffect() const 52 | { 53 | #if JucePlugin_IsMidiEffect 54 | return true; 55 | #else 56 | return false; 57 | #endif 58 | } 59 | 60 | double AudioPluginAudioProcessor::getTailLengthSeconds() const 61 | { 62 | return 0.0; 63 | } 64 | 65 | int AudioPluginAudioProcessor::getNumPrograms() 66 | { 67 | return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, 68 | // so this should be at least 1, even if you're not really implementing programs. 69 | } 70 | 71 | int AudioPluginAudioProcessor::getCurrentProgram() 72 | { 73 | return 0; 74 | } 75 | 76 | void AudioPluginAudioProcessor::setCurrentProgram (int index) 77 | { 78 | juce::ignoreUnused (index); 79 | } 80 | 81 | const juce::String AudioPluginAudioProcessor::getProgramName (int index) 82 | { 83 | juce::ignoreUnused (index); 84 | return {}; 85 | } 86 | 87 | void AudioPluginAudioProcessor::changeProgramName (int index, const juce::String& newName) 88 | { 89 | juce::ignoreUnused (index, newName); 90 | } 91 | 92 | //============================================================================== 93 | void AudioPluginAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) 94 | { 95 | // Use this method as the place to do any pre-playback 96 | // initialisation that you need.. 97 | juce::ignoreUnused (sampleRate, samplesPerBlock); 98 | 99 | oscillator->Init(sampleRate); 100 | tremolo->Init(sampleRate); 101 | } 102 | 103 | void AudioPluginAudioProcessor::releaseResources() 104 | { 105 | // When playback stops, you can use this as an opportunity to free up any 106 | // spare memory, etc. 107 | 108 | oscillator->Reset(); 109 | } 110 | 111 | bool AudioPluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const 112 | { 113 | #if JucePlugin_IsMidiEffect 114 | juce::ignoreUnused (layouts); 115 | return true; 116 | #else 117 | // This is the place where you check if the layout is supported. 118 | // In this template code we only support mono or stereo. 119 | // Some plugin hosts, such as certain GarageBand versions, will only 120 | // load plugins that support stereo bus layouts. 121 | if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() 122 | && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) 123 | return false; 124 | 125 | // This checks if the input layout matches the output layout 126 | #if ! JucePlugin_IsSynth 127 | if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) 128 | return false; 129 | #endif 130 | 131 | return true; 132 | #endif 133 | } 134 | 135 | void AudioPluginAudioProcessor::processBlock(juce::AudioBuffer& buffer, 136 | juce::MidiBuffer& midiMessages) 137 | { 138 | juce::ignoreUnused(midiMessages); 139 | 140 | juce::ScopedNoDenormals noDenormals; 141 | auto totalNumInputChannels = getTotalNumInputChannels(); 142 | auto totalNumOutputChannels = getTotalNumOutputChannels(); 143 | 144 | // In case we have more outputs than inputs, this code clears any output 145 | // channels that didn't contain input data, (because these aren't 146 | // guaranteed to be empty - they may contain garbage). 147 | // This is here to avoid people getting screaming feedback 148 | // when they first compile a plugin, but obviously you don't need to keep 149 | // this code if your algorithm always overwrites all the output channels. 150 | for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) 151 | buffer.clear(i, 0, buffer.getNumSamples()); 152 | 153 | // Apply oscillator parameter 154 | { 155 | const auto* wave_param = apvts.getParameter("Waveform"); 156 | const WaveformType wave_type = (WaveformType)wave_param->getNormalisableRange().convertFrom0to1(wave_param->getValue()); 157 | switch (wave_type) 158 | { 159 | case kSine: 160 | oscillator->SetWaveform(daisysp::Oscillator::WAVE_SIN); 161 | break; 162 | case kSquare: 163 | oscillator->SetWaveform(daisysp::Oscillator::WAVE_SQUARE); 164 | break; 165 | case kTriangle: 166 | oscillator->SetWaveform(daisysp::Oscillator::WAVE_TRI); 167 | break; 168 | case kSaw: 169 | oscillator->SetWaveform(daisysp::Oscillator::WAVE_SAW); 170 | break; 171 | } 172 | 173 | const auto* freq_param = apvts.getParameter("Frequency"); 174 | const float frequency = freq_param->getNormalisableRange().convertFrom0to1(freq_param->getValue()); 175 | oscillator->SetFreq(frequency); 176 | 177 | const auto* gain_param = apvts.getParameter("Gain"); 178 | const float gain = gain_param->getNormalisableRange().convertFrom0to1(gain_param->getValue()); 179 | oscillator->SetAmp(gain); 180 | } 181 | 182 | // Apply tremolo parameter 183 | { 184 | const auto* wave_param = apvts.getParameter("TremoloWaveform"); 185 | const WaveformType wave_type = (WaveformType)wave_param->getNormalisableRange().convertFrom0to1(wave_param->getValue()); 186 | switch (wave_type) 187 | { 188 | case kSine: 189 | tremolo->SetWaveform(daisysp::Oscillator::WAVE_SIN); 190 | break; 191 | case kSquare: 192 | tremolo->SetWaveform(daisysp::Oscillator::WAVE_SQUARE); 193 | break; 194 | case kTriangle: 195 | tremolo->SetWaveform(daisysp::Oscillator::WAVE_TRI); 196 | break; 197 | case kSaw: 198 | tremolo->SetWaveform(daisysp::Oscillator::WAVE_SAW); 199 | break; 200 | } 201 | 202 | const auto* freq_param = apvts.getParameter("TremoloFrequency"); 203 | const float frequency = freq_param->getNormalisableRange().convertFrom0to1(freq_param->getValue()); 204 | tremolo->SetFreq(frequency); 205 | 206 | const auto* depth_param = apvts.getParameter("TremoloDepth"); 207 | const float depth = depth_param->getNormalisableRange().convertFrom0to1(depth_param->getValue()); 208 | tremolo->SetDepth(depth); 209 | } 210 | 211 | for (int sample_idx = 0; sample_idx < buffer.getNumSamples(); sample_idx++) 212 | { 213 | float sample_data = oscillator->Process(); 214 | sample_data = tremolo->Process(sample_data); 215 | 216 | for (int ch_idx = 0; ch_idx < buffer.getNumChannels(); ch_idx++) 217 | { 218 | buffer.getWritePointer(ch_idx)[sample_idx] = sample_data; 219 | } 220 | } 221 | 222 | // Collect wave sample 223 | waveSampleCollector.process(buffer.getReadPointer(0), buffer.getNumSamples()); 224 | } 225 | 226 | //============================================================================== 227 | bool AudioPluginAudioProcessor::hasEditor() const 228 | { 229 | return true; // (change this to false if you choose to not supply an editor) 230 | } 231 | 232 | juce::AudioProcessorEditor* AudioPluginAudioProcessor::createEditor() 233 | { 234 | return new AudioPluginAudioProcessorEditor (*this); 235 | } 236 | 237 | //============================================================================== 238 | void AudioPluginAudioProcessor::getStateInformation (juce::MemoryBlock& destData) 239 | { 240 | // You should use this method to store your parameters in the memory block. 241 | // You could do that either as raw data, or use the XML or ValueTree classes 242 | // as intermediaries to make it easy to save and load complex data. 243 | juce::ignoreUnused (destData); 244 | } 245 | 246 | void AudioPluginAudioProcessor::setStateInformation (const void* data, int sizeInBytes) 247 | { 248 | // You should use this method to restore your parameters from this memory block, 249 | // whose contents will have been created by the getStateInformation() call. 250 | juce::ignoreUnused (data, sizeInBytes); 251 | } 252 | 253 | //============================================================================== 254 | juce::AudioProcessorValueTreeState::ParameterLayout AudioPluginAudioProcessor::createParameterLayout() const 255 | { 256 | juce::AudioProcessorValueTreeState::ParameterLayout layout; 257 | layout.add(std::make_unique("Gain", "Gain", juce::NormalisableRange{ 0.0f, 1.0f, 0.01f }, 0.5f)); 258 | layout.add(std::make_unique("Frequency", "Frequency", juce::NormalisableRange{ 20.0f, 2000.0f, 1.0f }, 440.0f)); 259 | layout.add(std::make_unique("Waveform", "Waveform", waveformTypes, 0)); 260 | layout.add(std::make_unique("TremoloFrequency", "TremoloFrequency", juce::NormalisableRange{ 20.0f, 2000.0f, 1.0f }, 20.0f)); 261 | layout.add(std::make_unique("TremoloDepth", "TremoloDepth", juce::NormalisableRange{ 0.0f, 1.0f, 0.01f }, 0.0f)); 262 | layout.add(std::make_unique("TremoloWaveform", "TremoloWaveform", waveformTypes, 0)); 263 | return layout; 264 | } 265 | 266 | //============================================================================== 267 | // This creates new instances of the plugin.. 268 | juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() 269 | { 270 | return new AudioPluginAudioProcessor(); 271 | } 272 | --------------------------------------------------------------------------------