/*
==============================================================================
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);
}