/* ============================================================================== AdsrData.cpp Created: 7 Feb 2021 2:29:21pm Author: Joshua Hodge ============================================================================== */ #include "AdsrData.h" void AdsrData::update (const float attack, const float decay, const float sustain, const float release) { adsrParams.attack = attack; adsrParams.decay = decay; adsrParams.sustain = sustain; adsrParams.release = release; setParameters (adsrParams); } /* ============================================================================== FilterData.cpp Created: 1 Apr 2021 12:56:20pm Author: Joshua Hodge ============================================================================== */ #include "FilterData.h" void FilterData::prepareToPlay (double sampleRate, int samplesPerBlock, int numChannels) { filter.reset(); juce::dsp::ProcessSpec spec; spec.maximumBlockSize = samplesPerBlock; spec.sampleRate = sampleRate; spec.numChannels = numChannels; filter.prepare (spec); isPrepared = true; } void FilterData::process (juce::AudioBuffer<float>& buffer) { jassert (isPrepared); juce::dsp::AudioBlock<float> block { buffer }; filter.process (juce::dsp::ProcessContextReplacing<float> { block }); } void FilterData::updateParameters (const float modulator, const int filterType, const float frequency, const float resonance) { switch (filterType) { case 0: filter.setType (juce::dsp::StateVariableTPTFilterType::lowpass); break; case 1: filter.setType (juce::dsp::StateVariableTPTFilterType::bandpass); break; case 2: filter.setType (juce::dsp::StateVariableTPTFilterType::highpass); break; } float modulatedFreq = frequency * modulator; modulatedFreq = std::fmax (std::fmin (modulatedFreq, 20000.0f), 20.0f); filter.setCutoffFrequency (modulatedFreq); filter.setResonance (resonance); } void FilterData::reset() { filter.reset(); } /* ============================================================================== OscData.cpp Created: 21 Feb 2021 4:34:01pm Author: Joshua Hodge ============================================================================== */ #include "OscData.h" void OscData::prepareToPlay (juce::dsp::ProcessSpec& spec) { prepare (spec); fmOsc.prepare (spec); } void OscData::setWaveType (const int choice) { switch (choice) { case 0: // Sine initialise ([](float x) { return std::sin (x); }); break; case 1: // Saw wave initialise ([](float x) { return x / juce::MathConstants<float>::pi; }); break; case 2: // Square wave initialise ([](float x) { return x < 0.0f ? -1.0f : 1.0f; }); break; default: jassertfalse; // You're not supposed to be here! break; } } void OscData::setWaveFrequency (const int midiNoteNumber) { setFrequency (juce::MidiMessage::getMidiNoteInHertz (midiNoteNumber) + fmMod); lastMidiNote = midiNoteNumber; } void OscData::getNextAudioBlock (juce::dsp::AudioBlock<float>& block) { processFmOsc (block); process (juce::dsp::ProcessContextReplacing<float> (block)); } void OscData::processFmOsc (juce::dsp::AudioBlock<float>& block) { for (int ch = 0; ch < block.getNumChannels(); ++ch) { for (int s = 0; s < block.getNumSamples(); ++s) { fmMod = fmOsc.processSample (block.getSample (ch, s)) * fmDepth; } } } void OscData::updateFm (const float freq, const float depth) { fmOsc.setFrequency (freq); fmDepth = depth; auto currentFreq = juce::MidiMessage::getMidiNoteInHertz (lastMidiNote) + fmMod; setFrequency (currentFreq >= 0 ? currentFreq : currentFreq * -1.0f); } /* ============================================================================== This file contains the basic framework code for a JUCE plugin editor. ============================================================================== */ #include "PluginProcessor.h" #include "PluginEditor.h" //============================================================================== TapSynthAudioProcessorEditor::TapSynthAudioProcessorEditor (TapSynthAudioProcessor& p) : AudioProcessorEditor (&p) , audioProcessor (p) , osc (audioProcessor.apvts, "OSC1WAVETYPE", "OSC1FMFREQ", "OSC1FMDEPTH") , adsr ("Amp Envelope", audioProcessor.apvts, "ATTACK", "DECAY", "SUSTAIN", "RELEASE") , filterAdsr ("Mod Envelope", audioProcessor.apvts, "FILTERATTACK", "FILTERDECAY", "FILTERSUSTAIN", "FILTERRELEASE") , filter (audioProcessor.apvts, "FILTERTYPE", "FILTERFREQ", "FILTERRES") { setSize (620, 500); addAndMakeVisible (osc); addAndMakeVisible (adsr); addAndMakeVisible (filterAdsr); addAndMakeVisible (filter); } TapSynthAudioProcessorEditor::~TapSynthAudioProcessorEditor() { } //============================================================================== void TapSynthAudioProcessorEditor::paint (juce::Graphics& g) { g.fillAll (juce::Colours::black); } void TapSynthAudioProcessorEditor::resized() { const auto paddingX = 5; const auto paddingY = 35; const auto paddingY2 = 235; osc.setBounds (paddingX, paddingY, 300, 200); adsr.setBounds (osc.getRight(), paddingY, 300, 200); filterAdsr.setBounds (paddingX, paddingY2, 300, 200); filter.setBounds (filterAdsr.getRight(), paddingY2, 300, 200); } /* ============================================================================== This file contains the basic framework code for a JUCE plugin processor. ============================================================================== */ #include "PluginProcessor.h" #include "PluginEditor.h" //============================================================================== TapSynthAudioProcessor::TapSynthAudioProcessor() #ifndef JucePlugin_PreferredChannelConfigurations : AudioProcessor (BusesProperties() #if ! JucePlugin_IsMidiEffect #if ! JucePlugin_IsSynth .withInput ("Input", juce::AudioChannelSet::stereo(), true) #endif .withOutput ("Output", juce::AudioChannelSet::stereo(), true) #endif ), apvts (*this, nullptr, "Parameters", createParams()) #endif { synth.addSound (new SynthSound()); synth.addVoice (new SynthVoice()); } TapSynthAudioProcessor::~TapSynthAudioProcessor() { } //============================================================================== const juce::String TapSynthAudioProcessor::getName() const { return JucePlugin_Name; } bool TapSynthAudioProcessor::acceptsMidi() const { #if JucePlugin_WantsMidiInput return true; #else return false; #endif } bool TapSynthAudioProcessor::producesMidi() const { #if JucePlugin_ProducesMidiOutput return true; #else return false; #endif } bool TapSynthAudioProcessor::isMidiEffect() const { #if JucePlugin_IsMidiEffect return true; #else return false; #endif } double TapSynthAudioProcessor::getTailLengthSeconds() const { return 0.0; } int TapSynthAudioProcessor::getNumPrograms() { return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, // so this should be at least 1, even if you're not really implementing programs. } int TapSynthAudioProcessor::getCurrentProgram() { return 0; } void TapSynthAudioProcessor::setCurrentProgram (int index) { } const juce::String TapSynthAudioProcessor::getProgramName (int index) { return {}; } void TapSynthAudioProcessor::changeProgramName (int index, const juce::String& newName) { } //============================================================================== void TapSynthAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { synth.setCurrentPlaybackSampleRate (sampleRate); for (int i = 0; i < synth.getNumVoices(); i++) { if (auto voice = dynamic_cast<SynthVoice*>(synth.getVoice(i))) { voice->prepareToPlay (sampleRate, samplesPerBlock, getTotalNumOutputChannels()); } } } void TapSynthAudioProcessor::releaseResources() { // When playback stops, you can use this as an opportunity to free up any // spare memory, etc. } #ifndef JucePlugin_PreferredChannelConfigurations bool TapSynthAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const { #if JucePlugin_IsMidiEffect juce::ignoreUnused (layouts); return true; #else // This is the place where you check if the layout is supported. // In this template code we only support mono or stereo. if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) return false; // This checks if the input layout matches the output layout #if ! JucePlugin_IsSynth if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) return false; #endif return true; #endif } #endif void TapSynthAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) { juce::ScopedNoDenormals noDenormals; auto totalNumInputChannels = getTotalNumInputChannels(); auto totalNumOutputChannels = getTotalNumOutputChannels(); for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) buffer.clear (i, 0, buffer.getNumSamples()); // Update voice { for (int i = 0; i < synth.getNumVoices(); ++i) { if (auto voice = dynamic_cast<SynthVoice*>(synth.getVoice(i))) { // Osc auto& oscWaveChoice = *apvts.getRawParameterValue ("OSC1WAVETYPE"); // FM auto& fmFreq = *apvts.getRawParameterValue ("OSC1FMFREQ"); auto& fmDepth = *apvts.getRawParameterValue ("OSC1FMDEPTH"); // Amp Adsr auto& attack = *apvts.getRawParameterValue ("ATTACK"); auto& decay = *apvts.getRawParameterValue ("DECAY"); auto& sustain = *apvts.getRawParameterValue ("SUSTAIN"); auto& release = *apvts.getRawParameterValue ("RELEASE"); // Filter Adsr auto& fAttack = *apvts.getRawParameterValue ("FILTERATTACK"); auto& fDecay = *apvts.getRawParameterValue ("FILTERDECAY"); auto& fSustain = *apvts.getRawParameterValue ("FILTERSUSTAIN"); auto& fRelease = *apvts.getRawParameterValue ("FILTERRELEASE"); // Filter auto& filterType = *apvts.getRawParameterValue ("FILTERTYPE"); auto& cutoff = *apvts.getRawParameterValue ("FILTERFREQ"); auto& resonance = *apvts.getRawParameterValue ("FILTERRES"); // Update voice voice->getOscillator().setWaveType (oscWaveChoice); voice->getOscillator().updateFm (fmFreq, fmDepth); voice->getAdsr().update (attack.load(), decay.load(), sustain.load(), release.load()); voice->getFilterAdsr().update (fAttack.load(), fDecay.load(), fSustain.load(), fRelease.load()); voice->updateFilter (filterType, cutoff, resonance); } } } synth.renderNextBlock (buffer, midiMessages, 0, buffer.getNumSamples()); } //============================================================================== bool TapSynthAudioProcessor::hasEditor() const { return true; // (change this to false if you choose to not supply an editor) } juce::AudioProcessorEditor* TapSynthAudioProcessor::createEditor() { return new TapSynthAudioProcessorEditor (*this); } //============================================================================== void TapSynthAudioProcessor::getStateInformation (juce::MemoryBlock& destData) { // You should use this method to store your parameters in the memory block. // You could do that either as raw data, or use the XML or ValueTree classes // as intermediaries to make it easy to save and load complex data. } void TapSynthAudioProcessor::setStateInformation (const void* data, int sizeInBytes) { // You should use this method to restore your parameters from this memory block, // whose contents will have been created by the getStateInformation() call. } //============================================================================== // This creates new instances of the plugin.. juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new TapSynthAudioProcessor(); } juce::AudioProcessorValueTreeState::ParameterLayout TapSynthAudioProcessor::createParams() { std::vector<std::unique_ptr<juce::RangedAudioParameter>> params; // OSC select params.push_back (std::make_unique<juce::AudioParameterChoice>("OSC1WAVETYPE", "Osc 1 Wave Type", juce::StringArray { "Sine", "Saw", "Square" }, 0)); // FM params.push_back (std::make_unique<juce::AudioParameterFloat>("OSC1FMFREQ", "Osc 1 FM Frequency", juce::NormalisableRange<float> { 0.0f, 1000.0f, 0.01f, 0.3f }, 0.0f)); params.push_back (std::make_unique<juce::AudioParameterFloat>("OSC1FMDEPTH", "Osc 1 FM Depth", juce::NormalisableRange<float> { 0.0f, 1000.0f, 0.01f, 0.3f }, 0.0f)); // ADSR params.push_back (std::make_unique<juce::AudioParameterFloat>("ATTACK", "Attack", juce::NormalisableRange<float> { 0.1f, 1.0f, 0.1f }, 0.1f)); params.push_back (std::make_unique<juce::AudioParameterFloat>("DECAY", "Decay", juce::NormalisableRange<float> { 0.1f, 1.0f, 0.1f }, 0.1f)); params.push_back (std::make_unique<juce::AudioParameterFloat>("SUSTAIN", "Sustain", juce::NormalisableRange<float> { 0.1f, 1.0f, 0.1f }, 1.0f)); params.push_back (std::make_unique<juce::AudioParameterFloat>("RELEASE", "Release", juce::NormalisableRange<float> { 0.1f, 3.0f, 0.1f }, 0.4f)); // Filter ADSR params.push_back (std::make_unique<juce::AudioParameterFloat>("FILTERATTACK", "Filter Attack", juce::NormalisableRange<float> { 0.1f, 1.0f, 0.1f }, 0.1f)); params.push_back (std::make_unique<juce::AudioParameterFloat>("FILTERDECAY", "Filter Decay", juce::NormalisableRange<float> { 0.1f, 1.0f, 0.1f }, 0.1f)); params.push_back (std::make_unique<juce::AudioParameterFloat>("FILTERSUSTAIN", "Filter Sustain", juce::NormalisableRange<float> { 0.1f, 1.0f, 0.1f }, 1.0f)); params.push_back (std::make_unique<juce::AudioParameterFloat>("FILTERRELEASE", "Filter Release", juce::NormalisableRange<float> { 0.1f, 3.0f, 0.1f }, 0.4f)); // Filter params.push_back (std::make_unique<juce::AudioParameterChoice>("FILTERTYPE", "Filter Type", juce::StringArray { "Low-Pass", "Band-Pass", "High-Pass" }, 0)); params.push_back (std::make_unique<juce::AudioParameterFloat>("FILTERFREQ", "Filter Freq", juce::NormalisableRange<float> { 20.0f, 20000.0f, 0.1f, 0.6f }, 200.0f)); params.push_back (std::make_unique<juce::AudioParameterFloat>("FILTERRES", "Filter Resonance", juce::NormalisableRange<float> { 1.0f, 10.0f, 0.1f }, 1.0f)); return { params.begin(), params.end() }; } /* ============================================================================== SynthVoice.cpp Created: 10 Dec 2020 1:55:41pm Author: Joshua Hodge ============================================================================== */ #include "SynthVoice.h" bool SynthVoice::canPlaySound (juce::SynthesiserSound* sound) { return dynamic_cast<juce::SynthesiserSound*>(sound) != nullptr; } void SynthVoice::startNote (int midiNoteNumber, float velocity, juce::SynthesiserSound *sound, int currentPitchWheelPosition) { osc.setWaveFrequency (midiNoteNumber); adsr.noteOn(); filterAdsr.noteOn(); } void SynthVoice::stopNote (float velocity, bool allowTailOff) { adsr.noteOff(); filterAdsr.noteOff(); if (! allowTailOff || ! adsr.isActive()) clearCurrentNote(); } void SynthVoice::controllerMoved (int controllerNumber, int newControllerValue) { } void SynthVoice::pitchWheelMoved (int newPitchWheelValue) { } void SynthVoice::prepareToPlay (double sampleRate, int samplesPerBlock, int outputChannels) { juce::dsp::ProcessSpec spec; spec.maximumBlockSize = samplesPerBlock; spec.sampleRate = sampleRate; spec.numChannels = outputChannels; osc.prepareToPlay (spec); filterAdsr.setSampleRate (sampleRate); filter.prepareToPlay (sampleRate, samplesPerBlock, outputChannels); adsr.setSampleRate (sampleRate); gain.prepare (spec); gain.setGainLinear (0.3f); isPrepared = true; } void SynthVoice::renderNextBlock (juce::AudioBuffer< float > &outputBuffer, int startSample, int numSamples) { jassert (isPrepared); if (! isVoiceActive()) return; synthBuffer.setSize (outputBuffer.getNumChannels(), numSamples, false, false, true); filterAdsr.applyEnvelopeToBuffer (outputBuffer, 0, numSamples); synthBuffer.clear(); juce::dsp::AudioBlock<float> audioBlock { synthBuffer }; osc.getNextAudioBlock (audioBlock); adsr.applyEnvelopeToBuffer (synthBuffer, 0, synthBuffer.getNumSamples()); filter.process (synthBuffer); gain.process (juce::dsp::ProcessContextReplacing<float> (audioBlock)); for (int channel = 0; channel < outputBuffer.getNumChannels(); ++channel) { outputBuffer.addFrom (channel, startSample, synthBuffer, channel, 0, numSamples); if (! adsr.isActive()) clearCurrentNote(); } } void SynthVoice::updateFilter (const int filterType, const float frequency, const float resonance) { auto modulator = filterAdsr.getNextSample(); filter.updateParameters (modulator, filterType, frequency, resonance); } /* ============================================================================== AdsrComponent.cpp Created: 7 Feb 2021 2:28:49pm Author: Joshua Hodge ============================================================================== */ #include <JuceHeader.h> #include "AdsrComponent.h" //============================================================================== AdsrComponent::AdsrComponent (juce::String name, juce::AudioProcessorValueTreeState& apvts, juce::String attackId, juce::String decayId, juce::String sustainId, juce::String releaseId) { componentName = name; setSliderWithLabel (attackSlider, attackLabel, apvts, attackId, attackAttachment); setSliderWithLabel (decaySlider, decayLabel, apvts, decayId, decayAttachment); setSliderWithLabel (sustainSlider, sustainLabel, apvts, sustainId, sustainAttachment); setSliderWithLabel (releaseSlider, releaseLabel, apvts, releaseId, releaseAttachment); } AdsrComponent::~AdsrComponent() { } void AdsrComponent::paint (juce::Graphics& g) { auto bounds = getLocalBounds().reduced (5); auto labelSpace = bounds.removeFromTop (25.0f); g.fillAll (juce::Colours::black); g.setColour (juce::Colours::white); g.setFont (20.0f); g.drawText (componentName, labelSpace.withX (5), juce::Justification::left); g.drawRoundedRectangle (bounds.toFloat(), 5.0f, 2.0f); } void AdsrComponent::resized() { const auto bounds = getLocalBounds().reduced (10); const auto padding = 10; const auto sliderWidth = bounds.getWidth() / 4 - padding; const auto sliderHeight = bounds.getHeight() - 45; const auto sliderStartX = padding + 5; const auto sliderStartY = 55; const auto labelYOffset = 20; const auto labelHeight = 20; const auto labelStart = sliderStartY - labelYOffset; attackSlider.setBounds (sliderStartX, sliderStartY, sliderWidth, sliderHeight); attackLabel.setBounds (attackSlider.getX(), labelStart, sliderWidth, labelHeight); decaySlider.setBounds (attackSlider.getRight() + padding, sliderStartY, sliderWidth, sliderHeight); decayLabel.setBounds (decaySlider.getX(), labelStart, sliderWidth, labelHeight); sustainSlider.setBounds (decaySlider.getRight() + padding, sliderStartY, sliderWidth, sliderHeight); sustainLabel.setBounds (sustainSlider.getX(), labelStart, sliderWidth, labelHeight); releaseSlider.setBounds (sustainSlider.getRight() + padding, sliderStartY, sliderWidth, sliderHeight); releaseLabel.setBounds (releaseSlider.getX(), labelStart, sliderWidth, labelHeight); } using Attachment = juce::AudioProcessorValueTreeState::SliderAttachment; void AdsrComponent::setSliderWithLabel (juce::Slider& slider, juce::Label& label, juce::AudioProcessorValueTreeState& apvts, juce::String paramId, std::unique_ptr<Attachment>& attachment) { slider.setSliderStyle (juce::Slider::SliderStyle::LinearVertical); slider.setTextBoxStyle (juce::Slider::TextBoxBelow, true, 50, 25); addAndMakeVisible (slider); attachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(apvts, paramId, slider); label.setColour (juce::Label::ColourIds::textColourId, juce::Colours::white); label.setFont (15.0f); label.setJustificationType (juce::Justification::centred); addAndMakeVisible (label); } /* ============================================================================== FilterComponent.cpp Created: 1 Apr 2021 1:30:00pm Author: Joshua Hodge ============================================================================== */ #include <JuceHeader.h> #include "FilterComponent.h" //============================================================================== FilterComponent::FilterComponent (juce::AudioProcessorValueTreeState& apvts, juce::String filterTypeSelectorId, juce::String filterFreqId, juce::String filterResId) { juce::StringArray choices { "Low-Pass", "Band-Pass", "High-Pass" }; filterTypeSelector.addItemList (choices, 1); addAndMakeVisible (filterTypeSelector); filterTypeSelectorAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ComboBoxAttachment>(apvts, filterTypeSelectorId, filterTypeSelector); filterSelectorLabel.setColour (juce::Label::ColourIds::textColourId, juce::Colours::white); filterSelectorLabel.setFont (15.0f); filterSelectorLabel.setJustificationType (juce::Justification::left); addAndMakeVisible (filterSelectorLabel); setSliderWithLabel (filterFreqSlider, filterFreqLabel, apvts, filterFreqId, filterFreqAttachment); setSliderWithLabel (filterResSlider, filterResLabel, apvts, filterResId, filterResAttachment); } FilterComponent::~FilterComponent() { } void FilterComponent::paint (juce::Graphics& g) { auto bounds = getLocalBounds().reduced (5); auto labelSpace = bounds.removeFromTop (25.0f); g.fillAll (juce::Colours::black); g.setColour (juce::Colours::white); g.setFont (20.0f); g.drawText ("Filter", labelSpace.withX (5), juce::Justification::left); g.drawRoundedRectangle (bounds.toFloat(), 5.0f, 2.0f); } void FilterComponent::resized() { const auto startY = 55; const auto sliderWidth = 100; const auto sliderHeight = 90; const auto labelYOffset = 20; const auto labelHeight = 20; filterTypeSelector.setBounds (10, startY + 5, 90, 30); filterSelectorLabel.setBounds (10, startY - labelYOffset, 90, labelHeight); filterFreqSlider.setBounds (filterTypeSelector.getRight(), startY, sliderWidth, sliderHeight); filterSelectorLabel.setBounds (filterFreqSlider.getX(), filterFreqSlider.getY() - labelYOffset, filterFreqSlider.getWidth(), labelHeight); filterResSlider.setBounds (filterFreqSlider.getRight(), startY, sliderWidth, sliderHeight); filterResLabel.setBounds (filterResSlider.getX(), filterResSlider.getY() - labelYOffset, filterResSlider.getWidth(), labelHeight); } using Attachment = juce::AudioProcessorValueTreeState::SliderAttachment; void FilterComponent::setSliderWithLabel (juce::Slider& slider, juce::Label& label, juce::AudioProcessorValueTreeState& apvts, juce::String paramId, std::unique_ptr<Attachment>& attachment) { slider.setSliderStyle (juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); slider.setTextBoxStyle (juce::Slider::TextBoxBelow, true, 50, 25); addAndMakeVisible (slider); attachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(apvts, paramId, slider); label.setColour (juce::Label::ColourIds::textColourId, juce::Colours::white); label.setFont (15.0f); label.setJustificationType (juce::Justification::centred); addAndMakeVisible (label); } /* ============================================================================== OscComponent.cpp Created: 21 Feb 2021 4:55:21pm Author: Joshua Hodge ============================================================================== */ #include <JuceHeader.h> #include "OscComponent.h" //============================================================================== OscComponent::OscComponent (juce::AudioProcessorValueTreeState& apvts, juce::String waveSelectorId, juce::String fmFreqId, juce::String fmDepthId) { juce::StringArray choices { "Sine", "Saw", "Square" }; oscWaveSelector.addItemList (choices, 1); addAndMakeVisible (oscWaveSelector); oscWaveSelectorAttachment = std::make_unique<juce::AudioProcessorValueTreeState::ComboBoxAttachment>(apvts, waveSelectorId, oscWaveSelector); waveSelectorLabel.setColour (juce::Label::ColourIds::textColourId, juce::Colours::white); waveSelectorLabel.setFont (15.0f); waveSelectorLabel.setJustificationType (juce::Justification::left); addAndMakeVisible (waveSelectorLabel); setSliderWithLabel (fmFreqSlider, fmFreqLabel, apvts, fmFreqId, fmFreqAttachment); setSliderWithLabel (fmDepthSlider, fmDepthLabel, apvts, fmDepthId, fmDepthAttachment); } OscComponent::~OscComponent() { } void OscComponent::paint (juce::Graphics& g) { auto bounds = getLocalBounds().reduced (5); auto labelSpace = bounds.removeFromTop (25.0f); g.fillAll (juce::Colours::black); g.setColour (juce::Colours::white); g.setFont (20.0f); g.drawText ("Oscillator", labelSpace.withX (5), juce::Justification::left); g.drawRoundedRectangle (bounds.toFloat(), 5.0f, 2.0f); } void OscComponent::resized() { const auto startY = 55; const auto sliderWidth = 100; const auto sliderHeight = 90; const auto labelYOffset = 20; const auto labelHeight = 20; oscWaveSelector.setBounds (10, startY + 5, 90, 30); waveSelectorLabel.setBounds (10, startY - labelYOffset, 90, labelHeight); fmFreqSlider.setBounds (oscWaveSelector.getRight(), startY, sliderWidth, sliderHeight); fmFreqLabel.setBounds (fmFreqSlider.getX(), fmFreqSlider.getY() - labelYOffset, fmFreqSlider.getWidth(), labelHeight); fmDepthSlider.setBounds (fmFreqSlider.getRight(), startY, sliderWidth, sliderHeight); fmDepthLabel.setBounds (fmDepthSlider.getX(), fmDepthSlider.getY() - labelYOffset, fmDepthSlider.getWidth(), labelHeight); } using Attachment = juce::AudioProcessorValueTreeState::SliderAttachment; void OscComponent::setSliderWithLabel (juce::Slider& slider, juce::Label& label, juce::AudioProcessorValueTreeState& apvts, juce::String paramId, std::unique_ptr<Attachment>& attachment) { slider.setSliderStyle (juce::Slider::SliderStyle::RotaryHorizontalVerticalDrag); slider.setTextBoxStyle (juce::Slider::TextBoxBelow, true, 50, 25); addAndMakeVisible (slider); attachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(apvts, paramId, slider); label.setColour (juce::Label::ColourIds::textColourId, juce::Colours::white); label.setFont (15.0f); label.setJustificationType (juce::Justification::centred); addAndMakeVisible (label); }