├── .gitignore ├── CMakeLists.txt ├── README.md ├── Resources └── TapLogo.png └── Source ├── AdsrComponent.cpp ├── AdsrComponent.h ├── PluginEditor.cpp ├── PluginEditor.h ├── PluginProcessor.cpp ├── PluginProcessor.h ├── WaveThumbnail.cpp └── WaveThumbnail.h /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /Builds/ 3 | /JuceLibraryCode/ 4 | 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | 3 | # Change to your project name 4 | project(TapAudioSampler VERSION 0.0.1) 5 | 6 | 7 | set(CMAKE_CXX_STANDARD 17) 8 | set(CMAKE_XCODE_GENERATE_SCHEME OFF) 9 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 10 | 11 | # We're going to use CPM as our package manager to bring in JUCE 12 | # Check to see if we have CPM installed already. Bring it in if we don't. 13 | set(CPM_DOWNLOAD_VERSION 0.34.0) 14 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 15 | 16 | if (NOT EXISTS ${CPM_DOWNLOAD_LOCATION}) 17 | message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}") 18 | file(DOWNLOAD https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake ${CPM_DOWNLOAD_LOCATION}) 19 | endif () 20 | 21 | include(${CPM_DOWNLOAD_LOCATION}) 22 | 23 | # Bring in JUCE locally 24 | CPMAddPackage( 25 | NAME juce 26 | GIT_REPOSITORY https://github.com/juce-framework/JUCE.git 27 | GIT_TAG origin/master 28 | ) 29 | 30 | # Make sure you include any new source files here 31 | set(SourceFiles 32 | Source/AdsrComponent.cpp 33 | Source/AdsrComponent.h 34 | Source/PluginEditor.cpp 35 | Source/PluginEditor.h 36 | Source/PluginProcessor.cpp 37 | Source/PluginProcessor.h 38 | Source/WaveThumbnail.cpp 39 | Source/WaveThumbnail.h 40 | ) 41 | 42 | juce_add_binary_data(ProjectResources SOURCES 43 | Resources/TapLogo.png 44 | ) 45 | 46 | # Change these to your own preferences 47 | juce_add_plugin(${PROJECT_NAME} 48 | COMPANY_NAME theaudioprogrammer 49 | IS_SYNTH FALSE 50 | NEEDS_MIDI_INPUT FALSE 51 | NEEDS_MIDI_OUTPUT FALSE 52 | IS_MIDI_EFFECT FALSE 53 | EDITOR_WANTS_KEYBOARD_FOCUS FALSE 54 | JUCE_VST3_CAN_REPLACE_VST2 FALSE 55 | COPY_PLUGIN_AFTER_BUILD TRUE 56 | PLUGIN_MANUFACTURER_CODE Tap1 57 | PLUGIN_CODE Reg0 58 | FORMATS VST3 AU Standalone 59 | PRODUCT_NAME "TapAudioSampler" 60 | ) 61 | 62 | # How we want our SourceFiles to appear in our IDE 63 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SourceFiles}) 64 | 65 | # Make the SourceFiles buildable 66 | target_sources(${PROJECT_NAME} PRIVATE ${SourceFiles}) 67 | 68 | # These are some toggleable options from the JUCE CMake API 69 | target_compile_definitions(${PROJECT_NAME} 70 | PUBLIC 71 | JUCE_WEB_BROWSER=0 72 | JUCE_USE_CURL=0 73 | JUCE_VST3_CAN_REPLACE_VST2=0 74 | ) 75 | 76 | # JUCE libraries to bring into our project 77 | target_link_libraries(${PROJECT_NAME} 78 | PUBLIC 79 | juce::juce_analytics 80 | juce::juce_audio_basics 81 | juce::juce_audio_devices 82 | juce::juce_core 83 | juce::juce_data_structures 84 | juce::juce_graphics 85 | juce::juce_gui_basics 86 | juce::juce_gui_extra 87 | juce::juce_audio_utils 88 | juce::juce_dsp 89 | juce::juce_recommended_config_flags 90 | juce::juce_recommended_lto_flags 91 | juce::juce_recommended_warning_flags 92 | ProjectResources 93 | ) 94 | 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tap Audio Sampler 2 | 3 | This is a simple audio sampler created with JUCE. 4 | 5 | ## Building the Project with CMake 6 | 7 | ### Prerequisites 8 | Ensure you have the following installed: 9 | - **CMake** (minimum version required: 3.x) 10 | - **C++ Compiler** (e.g., GCC, Clang, MSVC) 11 | - **(Optional) Ninja or Make** (for build system generation) 12 | 13 | ### Build Instructions 14 | 15 | 1. **Clone the repository:** 16 | 2. **Navigate into the repository folder** 17 | 3. **Create a folder called build** 18 | ```sh 19 | $ cmake -B build 20 | ``` 21 | 22 | **Optional:** You can also invoke the `-G` flag to build project files for the IDE of your choice. 23 | 24 | **XCode** 25 | ```sh 26 | $ cmake -B build -G "Xcode" 27 | ``` 28 | 29 | **Visual Studio Code 2019** 30 | ```sh 31 | $ cmake -S . -B build -G "Visual Studio 16 2019" 32 | ``` 33 | 34 | **Visual Studio Code 2022** 35 | ```sh 36 | $ cmake -S . -B build -G "Visual Studio 17 2022" 37 | ``` 38 | 39 | 5. **Navigate into the `build` folder and open the project file** 40 | 41 | 42 | -------------------------------------------------------------------------------- /Resources/TapLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheAudioProgrammer/AudioSampler/53ecc6786960deacd29ce1ccc52689add3097333/Resources/TapLogo.png -------------------------------------------------------------------------------- /Source/AdsrComponent.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | AdsrComponent.cpp 5 | Created: 4 Apr 2020 7:35:53pm 6 | Author: Joshua Hodge 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "AdsrComponent.h" 12 | 13 | //============================================================================== 14 | AdsrComponent::AdsrComponent (TapAudioSamplerAudioProcessor& p) : processor (p) 15 | { 16 | //Attack Slider 17 | attackSlider.setSliderStyle (juce::Slider::SliderStyle::RotaryVerticalDrag); 18 | attackSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, true, 40, 20); 19 | attackSlider.setColour (juce::Slider::ColourIds::thumbColourId, juce::Colours::red); 20 | addAndMakeVisible (attackSlider); 21 | 22 | attackLabel.setFont (10.0f); 23 | attackLabel.setText ("Attack", juce::NotificationType::dontSendNotification); 24 | attackLabel.setJustificationType (juce::Justification::centredTop); 25 | attackLabel.attachToComponent (&attackSlider, false); 26 | 27 | attackAttachment = std::make_unique(processor.getValueTree(), "ATTACK", attackSlider); 28 | 29 | //Decay Slider 30 | decaySlider.setSliderStyle (juce::Slider::SliderStyle::RotaryVerticalDrag); 31 | decaySlider.setTextBoxStyle (juce::Slider::TextBoxBelow, true, 40, 20); 32 | decaySlider.setColour (juce::Slider::ColourIds::thumbColourId, juce::Colours::red); 33 | addAndMakeVisible (decaySlider); 34 | 35 | decayLabel.setFont (10.0f); 36 | decayLabel.setText ("Decay", juce::NotificationType::dontSendNotification); 37 | decayLabel.setJustificationType (juce::Justification::centredTop); 38 | decayLabel.attachToComponent (&decaySlider, false); 39 | 40 | decayAttachment = std::make_unique(processor.getValueTree(), "DECAY", decaySlider); 41 | 42 | //Sustain Slider 43 | sustainSlider.setSliderStyle (juce::Slider::SliderStyle::RotaryVerticalDrag); 44 | sustainSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, true, 40, 20); 45 | sustainSlider.setColour (juce::Slider::ColourIds::thumbColourId, juce::Colours::red); 46 | addAndMakeVisible (sustainSlider); 47 | 48 | sustainLabel.setFont (10.0f); 49 | sustainLabel.setText ("Sustain", juce::NotificationType::dontSendNotification); 50 | sustainLabel.setJustificationType (juce::Justification::centredTop); 51 | sustainLabel.attachToComponent (&sustainSlider, false); 52 | 53 | sustainAttachment = std::make_unique(processor.getValueTree(), "SUSTAIN", sustainSlider); 54 | 55 | //Release Slider 56 | releaseSlider.setSliderStyle (juce::Slider::SliderStyle::RotaryVerticalDrag); 57 | releaseSlider.setTextBoxStyle (juce::Slider::TextBoxBelow, true, 40, 20); 58 | releaseSlider.setColour (juce::Slider::ColourIds::thumbColourId, juce::Colours::red); 59 | addAndMakeVisible (releaseSlider); 60 | 61 | releaseLabel.setFont (10.0f); 62 | releaseLabel.setText ("Release", juce::NotificationType::dontSendNotification); 63 | releaseLabel.setJustificationType (juce::Justification::centredTop); 64 | releaseLabel.attachToComponent (&releaseSlider, false); 65 | 66 | releaseAttachment = std::make_unique(processor.getValueTree(), "RELEASE", releaseSlider); 67 | } 68 | 69 | void AdsrComponent::paint (juce::Graphics& g) 70 | { 71 | g.fillAll (juce::Colours::black); 72 | } 73 | 74 | void AdsrComponent::resized() 75 | { 76 | constexpr auto startX = 0.6f; 77 | constexpr auto startY = 0.2f; 78 | constexpr auto dialWidth = 0.1f; 79 | constexpr auto dialHeight = 0.75f; 80 | 81 | attackSlider.setBoundsRelative (startX, startY, dialWidth, dialHeight); 82 | decaySlider.setBoundsRelative (startX + dialWidth, startY, dialWidth, dialHeight); 83 | sustainSlider.setBoundsRelative (startX + (dialWidth * 2), startY, dialWidth, dialHeight); 84 | releaseSlider.setBoundsRelative (startX + (dialWidth * 3), startY, dialWidth, dialHeight); 85 | } 86 | -------------------------------------------------------------------------------- /Source/AdsrComponent.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | AdsrComponent.h 5 | Created: 4 Apr 2020 7:35:53pm 6 | Author: Joshua Hodge 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | #include "PluginProcessor.h" 15 | 16 | 17 | //============================================================================== 18 | /* 19 | */ 20 | class AdsrComponent final : public juce::Component 21 | { 22 | public: 23 | explicit AdsrComponent (TapAudioSamplerAudioProcessor& p); 24 | 25 | void paint (juce::Graphics&) override; 26 | void resized() override; 27 | 28 | private: 29 | juce::Slider attackSlider, decaySlider, sustainSlider, releaseSlider; 30 | juce::Label attackLabel, decayLabel, sustainLabel, releaseLabel; 31 | 32 | std::unique_ptr attackAttachment; 33 | std::unique_ptr decayAttachment; 34 | std::unique_ptr sustainAttachment; 35 | std::unique_ptr releaseAttachment; 36 | 37 | TapAudioSamplerAudioProcessor& processor; 38 | 39 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AdsrComponent) 40 | }; 41 | -------------------------------------------------------------------------------- /Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "PluginProcessor.h" 3 | #include "PluginEditor.h" 4 | 5 | //============================================================================== 6 | TapAudioSamplerAudioProcessorEditor::TapAudioSamplerAudioProcessorEditor (TapAudioSamplerAudioProcessor& p) 7 | : AudioProcessorEditor (&p), thumbnail (p), adsr (p), processor (p) 8 | { 9 | auto tapImage = juce::ImageCache::getFromMemory(BinaryData::TapLogo_png, BinaryData::TapLogo_pngSize); 10 | 11 | if (! tapImage.isNull()) 12 | imageComponent.setImage (tapImage, juce::RectanglePlacement::fillDestination); 13 | else 14 | jassert (! tapImage.isNull()); 15 | 16 | addAndMakeVisible (thumbnail); 17 | addAndMakeVisible (adsr); 18 | addAndMakeVisible (imageComponent); 19 | 20 | startTimerHz (30); 21 | 22 | setSize (600, 400); 23 | } 24 | 25 | TapAudioSamplerAudioProcessorEditor::~TapAudioSamplerAudioProcessorEditor() 26 | { 27 | stopTimer(); 28 | } 29 | 30 | //============================================================================== 31 | void TapAudioSamplerAudioProcessorEditor::paint (juce::Graphics& g) 32 | { 33 | g.fillAll (juce::Colours::black); 34 | } 35 | 36 | void TapAudioSamplerAudioProcessorEditor::resized() 37 | { 38 | thumbnail.setBoundsRelative (0.0f, 0.25f, 1.0f, 0.5); 39 | adsr.setBoundsRelative (0.0f, 0.75f, 1.0f, 0.25f); 40 | imageComponent.setBoundsRelative (0.0f, 0.0f, 0.5f, 0.25f); 41 | } 42 | 43 | void TapAudioSamplerAudioProcessorEditor::timerCallback() 44 | { 45 | repaint(); 46 | } 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include "BinaryData.h" 6 | #include "PluginProcessor.h" 7 | #include "WaveThumbnail.h" 8 | #include "ADSRComponent.h" 9 | 10 | //============================================================================== 11 | /** 12 | */ 13 | class TapAudioSamplerAudioProcessorEditor final : public juce::AudioProcessorEditor, 14 | public juce::Timer 15 | { 16 | public: 17 | explicit TapAudioSamplerAudioProcessorEditor (TapAudioSamplerAudioProcessor&); 18 | ~TapAudioSamplerAudioProcessorEditor() override; 19 | 20 | //============================================================================== 21 | void paint (juce::Graphics&) override; 22 | void resized() override; 23 | 24 | void timerCallback() override; 25 | 26 | private: 27 | WaveThumbnail thumbnail; 28 | AdsrComponent adsr; 29 | juce::ImageComponent imageComponent; 30 | 31 | TapAudioSamplerAudioProcessor& processor; 32 | 33 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TapAudioSamplerAudioProcessorEditor) 34 | }; 35 | -------------------------------------------------------------------------------- /Source/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "PluginProcessor.h" 3 | #include "PluginEditor.h" 4 | 5 | //============================================================================== 6 | TapAudioSamplerAudioProcessor::TapAudioSamplerAudioProcessor() 7 | #ifndef JucePlugin_PreferredChannelConfigurations 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 | ), apvts (*this, nullptr, "Parameters", createParameters()) 16 | #endif 17 | { 18 | formatManager.registerBasicFormats(); 19 | apvts.state.addListener (this); 20 | 21 | for (int i = 0; i < numVoices; i++) 22 | { 23 | sampler.addVoice (new juce::SamplerVoice()); 24 | } 25 | } 26 | 27 | TapAudioSamplerAudioProcessor::~TapAudioSamplerAudioProcessor() 28 | { 29 | apvts.state.removeListener (this); 30 | } 31 | 32 | //============================================================================== 33 | const juce::String TapAudioSamplerAudioProcessor::getName() const 34 | { 35 | return JucePlugin_Name; 36 | } 37 | 38 | bool TapAudioSamplerAudioProcessor::acceptsMidi() const 39 | { 40 | #if JucePlugin_WantsMidiInput 41 | return true; 42 | #else 43 | return false; 44 | #endif 45 | } 46 | 47 | bool TapAudioSamplerAudioProcessor::producesMidi() const 48 | { 49 | #if JucePlugin_ProducesMidiOutput 50 | return true; 51 | #else 52 | return false; 53 | #endif 54 | } 55 | 56 | bool TapAudioSamplerAudioProcessor::isMidiEffect() const 57 | { 58 | #if JucePlugin_IsMidiEffect 59 | return true; 60 | #else 61 | return false; 62 | #endif 63 | } 64 | 65 | double TapAudioSamplerAudioProcessor::getTailLengthSeconds() const 66 | { 67 | return 0.0; 68 | } 69 | 70 | int TapAudioSamplerAudioProcessor::getNumPrograms() 71 | { 72 | return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, 73 | // so this should be at least 1, even if you're not really implementing programs. 74 | } 75 | 76 | int TapAudioSamplerAudioProcessor::getCurrentProgram() 77 | { 78 | return 0; 79 | } 80 | 81 | void TapAudioSamplerAudioProcessor::setCurrentProgram (int index) 82 | { 83 | } 84 | 85 | const juce::String TapAudioSamplerAudioProcessor::getProgramName (int index) 86 | { 87 | return {}; 88 | } 89 | 90 | void TapAudioSamplerAudioProcessor::changeProgramName (int index, const juce::String& newName) 91 | { 92 | } 93 | 94 | //============================================================================== 95 | void TapAudioSamplerAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) 96 | { 97 | sampler.setCurrentPlaybackSampleRate (sampleRate); 98 | updateADSR(); 99 | } 100 | 101 | void TapAudioSamplerAudioProcessor::releaseResources() 102 | { 103 | // When playback stops, you can use this as an opportunity to free up any 104 | // spare memory, etc. 105 | } 106 | 107 | #ifndef JucePlugin_PreferredChannelConfigurations 108 | bool TapAudioSamplerAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const 109 | { 110 | #if JucePlugin_IsMidiEffect 111 | ignoreUnused (layouts); 112 | return true; 113 | #else 114 | // This is the place where you check if the layout is supported. 115 | // In this template code we only support mono or stereo. 116 | if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() 117 | && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) 118 | return false; 119 | 120 | // This checks if the input layout matches the output layout 121 | #if ! JucePlugin_IsSynth 122 | if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) 123 | return false; 124 | #endif 125 | 126 | return true; 127 | #endif 128 | } 129 | #endif 130 | 131 | void TapAudioSamplerAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) 132 | { 133 | juce::ScopedNoDenormals noDenormals; 134 | auto totalNumInputChannels = getTotalNumInputChannels(); 135 | auto totalNumOutputChannels = getTotalNumOutputChannels(); 136 | 137 | for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) 138 | buffer.clear (i, 0, buffer.getNumSamples()); 139 | 140 | if (shouldUpdate) 141 | { 142 | updateADSR(); 143 | } 144 | 145 | for (const auto metadata : midiMessages) 146 | { 147 | const auto message = metadata.getMessage(); 148 | 149 | if (message.isNoteOn()) 150 | isNotePlayed = true; 151 | else if (message.isNoteOff()) 152 | isNotePlayed = false; 153 | } 154 | 155 | sampleCount = isNotePlayed ? sampleCount += buffer.getNumSamples() : 0; 156 | 157 | sampler.renderNextBlock (buffer, midiMessages, 0, buffer.getNumSamples()); 158 | } 159 | 160 | //============================================================================== 161 | bool TapAudioSamplerAudioProcessor::hasEditor() const 162 | { 163 | return true; // (change this to false if you choose to not supply an editor) 164 | } 165 | 166 | juce::AudioProcessorEditor* TapAudioSamplerAudioProcessor::createEditor() 167 | { 168 | return new TapAudioSamplerAudioProcessorEditor (*this); 169 | } 170 | 171 | //============================================================================== 172 | void TapAudioSamplerAudioProcessor::getStateInformation (juce::MemoryBlock& destData) 173 | { 174 | // You should use this method to store your parameters in the memory block. 175 | // You could do that either as raw data, or use the XML or ValueTree classes 176 | // as intermediaries to make it easy to save and load complex data. 177 | } 178 | 179 | void TapAudioSamplerAudioProcessor::setStateInformation (const void* data, int sizeInBytes) 180 | { 181 | // You should use this method to restore your parameters from this memory block, 182 | // whose contents will have been created by the getStateInformation() call. 183 | } 184 | 185 | void TapAudioSamplerAudioProcessor::loadFile() 186 | { 187 | sampler.clearSounds(); 188 | 189 | juce::FileChooser chooser { "Please load a file" }; 190 | 191 | chooser.launchAsync(juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles, 192 | [this](const juce::FileChooser& fc) 193 | { 194 | const auto file = fc.getResult(); 195 | 196 | // the reader can be a local variable here since it's not needed by the SamplerSound after this 197 | if (const std::unique_ptr reader{ formatManager.createReaderFor(file) }) 198 | { 199 | juce::BigInteger range; 200 | range.setRange(0, 128, true); 201 | sampler.addSound(new juce::SamplerSound("Sample", *reader, range, 60, 0.1, 0.1, 10.0)); 202 | } 203 | }); 204 | } 205 | 206 | void TapAudioSamplerAudioProcessor::loadFile (const juce::String& path) 207 | { 208 | sampler.clearSounds(); 209 | 210 | const auto file = juce::File (path); 211 | 212 | // the reader can be a local variable here since it's not needed by the other classes after this 213 | if (const std::unique_ptr reader{ formatManager.createReaderFor(file) }) 214 | { 215 | juce::BigInteger range; 216 | range.setRange(0, 128, true); 217 | sampler.addSound(new juce::SamplerSound("Sample", *reader, range, 60, 0.1, 0.1, 10.0)); 218 | updateADSR(); 219 | } 220 | } 221 | 222 | juce::AudioBuffer& TapAudioSamplerAudioProcessor::getWaveForm() const 223 | { 224 | // get the last added synth sound as a SamplerSound* 225 | if (const auto sound = dynamic_cast(sampler.getSound(sampler.getNumSounds() - 1).get())) 226 | { 227 | return *sound->getAudioData(); 228 | } 229 | 230 | // just in case it somehow happens that the sound doesn't exist or isn't a SamplerSound, 231 | // return a static instance of an empty AudioBuffer here... 232 | static juce::AudioBuffer dummybuffer; 233 | 234 | return dummybuffer; 235 | } 236 | 237 | void TapAudioSamplerAudioProcessor::updateADSR() 238 | { 239 | shouldUpdate = false; 240 | 241 | adsrParams.attack = apvts.getRawParameterValue ("ATTACK")->load(); 242 | adsrParams.decay = apvts.getRawParameterValue ("DECAY")->load(); 243 | adsrParams.sustain = apvts.getRawParameterValue ("SUSTAIN")->load(); 244 | adsrParams.release = apvts.getRawParameterValue ("RELEASE")->load(); 245 | 246 | for (int i = 0; i < sampler.getNumSounds(); ++i) 247 | { 248 | if (auto sound = dynamic_cast(sampler.getSound(i).get())) 249 | { 250 | sound->setEnvelopeParameters (adsrParams); 251 | } 252 | } 253 | } 254 | 255 | juce::AudioProcessorValueTreeState::ParameterLayout TapAudioSamplerAudioProcessor::createParameters() 256 | { 257 | std::vector> params; 258 | 259 | params.push_back (std::make_unique("ATTACK", "Attack", 0.0f, 5.0f, 0.0f)); 260 | params.push_back (std::make_unique("DECAY", "Decay", 0.0f, 5.0f, 2.0f)); 261 | params.push_back (std::make_unique("SUSTAIN", "Sustain", 0.0f, 1.0f, 1.0f)); 262 | params.push_back (std::make_unique("RELEASE", "Release", 0.0f, 5.0f, 0.0f)); 263 | 264 | return { params.begin(), params.end() }; 265 | } 266 | 267 | void TapAudioSamplerAudioProcessor::valueTreePropertyChanged (juce::ValueTree &treeWhosePropertyHasChanged, const juce::Identifier &property) 268 | { 269 | shouldUpdate = true; 270 | } 271 | 272 | //============================================================================== 273 | // This creates new instances of the plugin.. 274 | juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() 275 | { 276 | return new TapAudioSamplerAudioProcessor(); 277 | } 278 | -------------------------------------------------------------------------------- /Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | //============================================================================== 8 | /** 9 | */ 10 | class TapAudioSamplerAudioProcessor : public juce::AudioProcessor, 11 | public juce::ValueTree::Listener 12 | { 13 | public: 14 | //============================================================================== 15 | TapAudioSamplerAudioProcessor(); 16 | ~TapAudioSamplerAudioProcessor() override; 17 | 18 | //============================================================================== 19 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 20 | void releaseResources() override; 21 | 22 | #ifndef JucePlugin_PreferredChannelConfigurations 23 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 24 | #endif 25 | 26 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 27 | 28 | //============================================================================== 29 | juce::AudioProcessorEditor* createEditor() override; 30 | bool hasEditor() const override; 31 | 32 | //============================================================================== 33 | const juce::String getName() const override; 34 | 35 | bool acceptsMidi() const override; 36 | bool producesMidi() const override; 37 | bool isMidiEffect() const override; 38 | double getTailLengthSeconds() const override; 39 | 40 | //============================================================================== 41 | int getNumPrograms() override; 42 | int getCurrentProgram() override; 43 | void setCurrentProgram (int index) override; 44 | const juce::String getProgramName (int index) override; 45 | void changeProgramName (int index, const juce::String& newName) override; 46 | 47 | //============================================================================== 48 | void getStateInformation (juce::MemoryBlock& destData) override; 49 | void setStateInformation (const void* data, int sizeInBytes) override; 50 | 51 | void loadFile(); 52 | void loadFile (const juce::String& path); 53 | 54 | int getNumSamplerSounds() const { return sampler.getNumSounds(); } 55 | juce::AudioBuffer& getWaveForm() const; 56 | 57 | void updateADSR(); 58 | juce::ADSR::Parameters& getAdsrParams() { return adsrParams; } 59 | 60 | juce::AudioProcessorValueTreeState& getValueTree() { return apvts; } 61 | std::atomic& isNoteCurrentlyPlayed() { return isNotePlayed; } 62 | std::atomic& getSampleCount() { return sampleCount; } 63 | 64 | private: 65 | juce::Synthesiser sampler; 66 | const int numVoices { 3 }; 67 | juce::ADSR::Parameters adsrParams; 68 | juce::AudioFormatManager formatManager; 69 | 70 | juce::AudioProcessorValueTreeState apvts; 71 | 72 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameters(); 73 | void valueTreePropertyChanged (juce::ValueTree &treeWhosePropertyHasChanged, const juce::Identifier &property) override; 74 | 75 | std::atomic shouldUpdate { false }; 76 | std::atomic isNotePlayed { false }; 77 | std::atomic sampleCount { 0 }; 78 | 79 | //============================================================================== 80 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TapAudioSamplerAudioProcessor) 81 | }; 82 | -------------------------------------------------------------------------------- /Source/WaveThumbnail.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | WaveThumbnail.cpp 5 | Created: 4 Apr 2020 5:47:38pm 6 | Author: Joshua Hodge 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #include "WaveThumbnail.h" 12 | 13 | //============================================================================== 14 | WaveThumbnail::WaveThumbnail (TapAudioSamplerAudioProcessor& p) : processor (p) 15 | { 16 | // In your constructor, you should add any child components, and 17 | // initialise any special settings that your component needs. 18 | } 19 | 20 | void WaveThumbnail::paint (juce::Graphics& g) 21 | { 22 | g.fillAll (juce::Colours::cadetblue.darker()); 23 | 24 | const auto waveform = processor.getWaveForm(); 25 | 26 | if (waveform.getNumSamples() > 0) 27 | { 28 | juce::Path p; 29 | audioPoints.clear(); 30 | 31 | 32 | const auto ratio = waveform.getNumSamples() / getWidth(); 33 | const auto buffer = waveform.getReadPointer (0); 34 | 35 | //scale audio file to window on x-axis 36 | for (int sample = 0; sample < waveform.getNumSamples(); sample+=ratio) 37 | { 38 | audioPoints.push_back (buffer[sample]); 39 | } 40 | 41 | const auto height = static_cast(getHeight()); 42 | 43 | g.setColour (juce::Colours::yellow); 44 | p.startNewSubPath (0.0f, height / 2.0f); 45 | 46 | //scale on y axis 47 | for (int sample = 0; sample < audioPoints.size(); ++sample) 48 | { 49 | auto point = juce::jmap (audioPoints[sample], -1.0f, 1.0f, height, 0); 50 | p.lineTo (static_cast(sample), point); 51 | } 52 | 53 | g.strokePath(p, juce::PathStrokeType (2)); 54 | 55 | g.setColour (juce::Colours::white); 56 | g.setFont (15.0f); 57 | auto textBounds = getLocalBounds().reduced (10, 10); 58 | g.drawFittedText (fileName, textBounds, juce::Justification::topRight, 1); 59 | 60 | auto playHeadPosition = juce::jmap (processor.getSampleCount(), 0, processor.getWaveForm().getNumSamples(), 0, getWidth()); 61 | 62 | g.setColour (juce::Colours::white); 63 | 64 | const auto playPosition = static_cast(playHeadPosition); 65 | 66 | g.drawLine (playPosition, 0, playPosition, static_cast(getHeight()), 2.0f); 67 | 68 | g.setColour (juce::Colours::black.withAlpha (0.2f)); 69 | g.fillRect (0, 0, playHeadPosition, getHeight()); 70 | } 71 | else 72 | { 73 | g.setColour (juce::Colours::white); 74 | g.setFont (40.0f); 75 | g.drawFittedText ("Drop an Audio File to Load", getLocalBounds(), juce::Justification::centred, 1); 76 | } 77 | } 78 | 79 | void WaveThumbnail::resized() 80 | { 81 | // This method is where you should set the bounds of any child 82 | // components that your component contains.. 83 | 84 | } 85 | 86 | bool WaveThumbnail::isInterestedInFileDrag (const juce::StringArray& files) 87 | { 88 | for (const auto& file : files) 89 | { 90 | if (file.contains (".wav") || file.contains (".mp3") || file.contains (".aif")) 91 | { 92 | return true; 93 | } 94 | } 95 | 96 | return false; 97 | } 98 | 99 | void WaveThumbnail::filesDropped (const juce::StringArray& files, int x, int y) 100 | { 101 | for (const auto& file : files) 102 | { 103 | if (isInterestedInFileDrag (file)) 104 | { 105 | auto myFile = std::make_unique(file); 106 | fileName = myFile->getFileNameWithoutExtension(); 107 | 108 | processor.loadFile (file); 109 | } 110 | } 111 | 112 | repaint(); 113 | } 114 | -------------------------------------------------------------------------------- /Source/WaveThumbnail.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | WaveThumbnail.h 5 | Created: 4 Apr 2020 5:47:38pm 6 | Author: Joshua Hodge 7 | 8 | ============================================================================== 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | #include "PluginProcessor.h" 15 | 16 | //============================================================================== 17 | /* 18 | */ 19 | class WaveThumbnail : public juce::Component, 20 | public juce::FileDragAndDropTarget 21 | { 22 | public: 23 | explicit WaveThumbnail (TapAudioSamplerAudioProcessor& p); 24 | 25 | void paint (juce::Graphics&) override; 26 | void resized() override; 27 | 28 | bool isInterestedInFileDrag (const juce::StringArray& files) override; 29 | void filesDropped (const juce::StringArray& files, int x, int y) override; 30 | 31 | private: 32 | std::vector audioPoints; 33 | bool shouldBePainting { false }; 34 | 35 | juce::String fileName { "" }; 36 | 37 | TapAudioSamplerAudioProcessor& processor; 38 | 39 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaveThumbnail) 40 | }; 41 | --------------------------------------------------------------------------------