From 78208f093993ba05a73c8cba4eab09c64c2a5ac8 Mon Sep 17 00:00:00 2001 From: Repairer of Reputations Date: Thu, 17 Jul 2025 05:35:28 -0400 Subject: [PATCH 1/5] Add files via upload Fixed envelope generator code --- Source/Graphics.h | 76 ++++++++++++++++++++++++++----------------- Source/Operator.cpp | 24 ++++++++++++-- Source/Operator.h | 7 ++++ Source/PluginEditor.h | 1 + 4 files changed, 75 insertions(+), 33 deletions(-) diff --git a/Source/Graphics.h b/Source/Graphics.h index 342e4e6..36ef83d 100644 --- a/Source/Graphics.h +++ b/Source/Graphics.h @@ -3,7 +3,6 @@ #include #include "LookAndFeel.h" -//Takuma your waveforms look like a butt class OperatorDisplayGraphics : public juce::Component { @@ -62,6 +61,15 @@ class OperatorDisplayGraphics : public juce::Component void resized() override {} + void setEnvelope(int index, float attack, float decay, float sustain, float release) + { + op[index].attack = attack; + op[index].decay = decay; + op[index].sustain = sustain/10.0f; + op[index].release = release; + repaint(); + } + void setRatioAndAmplitude(float ratio, float fixed, float modIndex, bool isRatio) { this->ratio = ratio; @@ -138,13 +146,11 @@ class EnvelopeDisplayGraphics : public juce::Component float heightMargin = bounds.getHeight() * 0.05f; points[0].coords = { x + widthMargin, y + height + heightMargin }; // initial - points[1].coords = { x + widthMargin + width * 0.1f, y + heightMargin + height * 0.5f }; // attack slope - points[2].coords = { x + widthMargin + width * 0.2f, y + heightMargin }; // peak - points[3].coords = { x + widthMargin + width * 0.3f, y + heightMargin + height * 0.25f }; // decay slope - points[4].coords = { x + widthMargin + width * 0.4f, y + heightMargin + height * 0.5f }; // sustain start - points[5].coords = { x + widthMargin + width * 0.5f, y + heightMargin + height * 0.5f }; // // sustain end - points[6].coords = { x + widthMargin + width * 0.7f, y + heightMargin + height * 0.75f }; // release start - points[7].coords = { x + widthMargin + width * 0.8f, y + heightMargin + height }; // release end; + points[1].coords = { x + widthMargin + width * attackPct, y + heightMargin + height * 0.5f }; // peak + points[2].coords = { x + widthMargin + width * decayPct, y + heightMargin + height * 0.5f}; // sustain start + points[3].coords = { x + widthMargin + width * sustainPct, y + heightMargin + height * 0.3f }; // sustain end + points[4].coords = { x + widthMargin + width * releasePct, y + heightMargin + height }; // end of envelope + repaint(); } void setEnvelope(float attack, float decay, float sustain, float release) @@ -156,24 +162,21 @@ class EnvelopeDisplayGraphics : public juce::Component float sustainPercent = 0.25f; float remainderPercent = 1.0f - sustainPercent; + float adrSum = attack + decay + release; - float adrSum = attack + decay + release; - - if (adrSum > 0.0f) - { - attackPct = (attack / adrSum) * remainderPercent; - decayPct = (decay / adrSum) * remainderPercent; - releasePct = (release / adrSum) * remainderPercent; - } - else - { - float evenShare = remainderPercent / 3.0f; - attackPct = evenShare; - decayPct = evenShare; - releasePct = evenShare; - } - sustainPct = sustainPercent; - + if (adrSum > 0.0f) { + attackPct = (attack / adrSum) * remainderPercent; + decayPct = (decay / adrSum) * remainderPercent; + releasePct = (release / adrSum) * remainderPercent; + + } else { + float evenShare = remainderPercent / 3.0f; + attackPct = evenShare; + decayPct = evenShare; + releasePct = evenShare; + + } + sustainPct = sustainPercent; } void drawSegment(juce::Graphics &g, float x, float y, float width, float height) @@ -184,9 +187,9 @@ class EnvelopeDisplayGraphics : public juce::Component envelopePath.lineTo(points[2].coords); envelopePath.lineTo(points[3].coords); envelopePath.lineTo(points[4].coords); - envelopePath.lineTo(points[5].coords); - envelopePath.lineTo(points[6].coords); - envelopePath.lineTo(points[7].coords); + // envelopePath.lineTo(points[5].coords); + // envelopePath.lineTo(points[6].coords); + // envelopePath.lineTo(points[7].coords); g.setColour(juce::Colour(90, 224, 184)); @@ -301,7 +304,11 @@ class WaveformDisplayGraphics : public juce::Component float amp0 = op[0].generateAmplitude(k); float heightScaled = y + heightMargin + heightIncrement * j; - graphicLines.startNewSubPath(x + widthMargin, heightScaled + height * 0.005f); + + float sin0Phase = fmodf(((0/40.7f) * op[0].ratio * 2.0f), 6.28318f); + float sin = amp0 * fastSin.sin(sin0Phase); + + graphicLines.startNewSubPath(x + widthMargin, (heightScaled + height/envelopeSegments) + sin * height * 0.005f); for (int i = 0; i < domainResolution; i++) { @@ -330,15 +337,24 @@ class WaveformDisplayGraphics : public juce::Component void resized() override {}; + void setEnvelope(int index, float attack, float decay, float sustain, float release) + { + op[index].attack = attack; + op[index].decay = decay; + op[index].sustain = sustain/10.0f; + op[index].release = release; + repaint(); + } + void setFMParameter(int index, float ratio, float fixed, bool isRatio, float modIndex) { op[index].ratio = ratio; op[index].fixed = fixed; op[index].modIndex = modIndex/10.0f; op[index].isRatio = isRatio; + repaint(); } - void calculateEnvelopeSegments() { for (int index = 0; index < 4; index++) diff --git a/Source/Operator.cpp b/Source/Operator.cpp index a3a66ac..d22f721 100644 --- a/Source/Operator.cpp +++ b/Source/Operator.cpp @@ -19,6 +19,13 @@ void FMOperator::prepareToPlay(double sampleRate, float samplesPerBlock, int num ratioSmoothed.reset(sampleRate, 0.001); fixedSmoothed.reset(sampleRate, 0.001); modIndexSmoothed.reset(sampleRate, 0.001); + + + float smoothingTimeSeconds = frequencySmoothingTimeMs / 1000.0f; + frequencySmoothingCoeff = 1.0f - std::exp(-1.0f / (smoothingTimeSeconds * sampleRate)); + + currentFrequency = 0.0f; + targetFrequency = 0.0f; } void FMOperator::startNote() @@ -47,7 +54,7 @@ void FMOperator::setEnvelope(float attackInMs, float decayInMs, float sustainInF void FMOperator::setNoteNumber(float noteNumber) { - noteFrequency = juce::MidiMessage::getMidiNoteInHertz(noteNumber); + targetFrequency = juce::MidiMessage::getMidiNoteInHertz(noteNumber); } void FMOperator::setOperator(float ratio, float fixed, bool isFixed, float modIndex) @@ -60,9 +67,20 @@ void FMOperator::setOperator(float ratio, float fixed, bool isFixed, float modIn float FMOperator::processOperator(float phase1, float phase2, float phase3, float phase4, float feedback) { - frequency = noteFrequency * ratioSmoothed.getNextValue(); - if (isFixed) frequency = fixedSmoothed.getNextValue(); + + + + currentFrequency += frequencySmoothingCoeff * (targetFrequency - currentFrequency); + + + frequency = currentFrequency * ratioSmoothed.getNextValue(); + if (isFixed) frequency = fixedSmoothed.getNextValue(); + + operatorAngle = frequency/sampleRate; + + + float modulatorPhase = phase1 + phase2 + phase3 + phase4 + feedback; diff --git a/Source/Operator.h b/Source/Operator.h index e532a3d..db711f0 100644 --- a/Source/Operator.h +++ b/Source/Operator.h @@ -23,7 +23,14 @@ class FMOperator float noteFrequency, frequency, ratio, fixed; bool isFixed = false; + float targetFrequency = 0.0f; + float currentFrequency = 0.0f; + float frequencySmoothingTimeMs = 100.0f; + float frequencySmoothingCoeff = 0.0f; + juce::SmoothedValue ratioSmoothed, fixedSmoothed, modIndexSmoothed; juce::ADSR ampEnvelope; juce::ADSR::Parameters envParameters; + + }; diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index b1ecde6..dfc7ca3 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -13,6 +13,7 @@ #include "UserInterface.h" #include "AlgorithmGraphics.h" #include "LookAndFeel.h" +#include "Graphics.h" //============================================================================== /** From 6c77fd73aa3586992b8f0d3ef217f21334e44eb3 Mon Sep 17 00:00:00 2001 From: Repairer of Reputations Date: Fri, 18 Jul 2025 11:54:35 -0400 Subject: [PATCH 2/5] Add files via upload Fixed text box click and drag issue, now calculates delta from current position. From 110b6c6c351cbf6008cf9f4573c2e9990a39f442 Mon Sep 17 00:00:00 2001 From: Repairer of Reputations Date: Fri, 18 Jul 2025 14:43:40 -0400 Subject: [PATCH 3/5] Add files via upload --- Source/AlgorithmGraphics.h | 79 ++++++++++++++++++++++++++++++-------- Source/DialLookAndFeel.h | 29 ++++++++++++-- Source/Graphics.h | 69 +++++++++++++++------------------ Source/Operator.cpp | 26 ++++--------- Source/Operator.h | 5 +-- Source/PluginEditor.h | 1 - Source/PluginProcessor.cpp | 24 +++++++----- Source/UserInterface.cpp | 28 ++++++++++---- Source/UserInterface.h | 23 +++++++++++ Source/VoiceProcessor.h | 63 +++++++++++++++++------------- 10 files changed, 223 insertions(+), 124 deletions(-) diff --git a/Source/AlgorithmGraphics.h b/Source/AlgorithmGraphics.h index dc4130c..a15e7fa 100644 --- a/Source/AlgorithmGraphics.h +++ b/Source/AlgorithmGraphics.h @@ -116,13 +116,23 @@ class PatchCable : public juce::Component { return outputIndex; } + + void setCableInputIndex(int inputIndex) + { + this->inputIndex = inputIndex; + } + + int getCableInputIndex() + { + return inputIndex; + } + private: - int outputIndex; + int outputIndex, inputIndex; bool isConnected = false, isInUse = false; juce::Point outputPoint, inputPoint, mousePoint; - }; @@ -487,13 +497,6 @@ class AlgorithmGraphics : public juce::Component void mouseDown(const juce::MouseEvent& m) override { - /* - 1. Creating new cables - 2. editing exising cables - 3. - - - */ bool modifier = m.mods.isCommandDown(); for (int i = 0; i < 4; i++) { @@ -547,6 +550,26 @@ class AlgorithmGraphics : public juce::Component op[i].setBlockCenter(globalMouse.x, globalMouse.y); // REFRESH CABLE POSITION WITH BLOCK + for (int j = 0; j < 4; j++) + { + bool inUse = cable[i][j].getIsInUse(); + bool isConnected = cable[i][j].getIsConnected(); + int outputIndex = cable[i][j].getCableOutputIndex(); + int inputIndex = cable[i][j].getCableInputIndex(); + + + if (outputIndex == blk && outputIndex != -1 && inUse && isConnected) + { + auto outputPoint = op[blk].getOutputPoint(); + cable[i][j].setOutputPoint(outputPoint); + } + + if (inputIndex == i && inputIndex != -1 && inUse && isConnected) + { + auto inputPoint = op[i].getInputPoint(); + cable[i][j].setInputPoint(inputPoint); + } + } } if (currentCableIndex.has_value() && *dragState == 2) @@ -574,8 +597,6 @@ class AlgorithmGraphics : public juce::Component { op[i].setPointInFocus(true); } - - } } @@ -587,12 +608,12 @@ class AlgorithmGraphics : public juce::Component for (int i = 0; i < 4; i++) { auto mouse = m.getEventRelativeTo(&op[i]).getPosition().toFloat(); - if (op[i].isOverInputPoint(mouse) && *dragState == 2) + if (currentCableIndex.has_value() && op[i].isOverInputPoint(mouse) && *dragState == 2) { - auto inputPoint = op[i].getInputPoint(); cable[blk][cbl].setInputPoint(inputPoint); - cable[blk][cbl].setCableOutputIndex(i); + cable[blk][cbl].setCableInputIndex(i); + cable[blk][cbl].setCableOutputIndex(blk); cable[blk][cbl].setIsInUse(true); cable[blk][cbl].setIsConnected(true); @@ -600,9 +621,12 @@ class AlgorithmGraphics : public juce::Component op[outputIndex].setNumCableAvailable(-1); op[i].setInput(blk, 1.0f); - + + } else if (currentCableIndex.has_value() && !op[i].isOverInputPoint(mouse) && *dragState == 2) + { + cable[blk][cbl].setIsInUse(false); + cable[blk][cbl].setIsConnected(false); } - } currentCableIndex.reset(); @@ -635,7 +659,29 @@ class AlgorithmGraphics : public juce::Component return coords; } + + private: + std::array toBinary4(int input) + { + std::array bits; + for (int i = 0; i < 4; ++i) + bits[i] = (input >> i) & 1; + return bits; + } + + int fromBinary4(const std::array& bits) + { + int result = 0; + for (int i = 0; i < 4; ++i) + { + if (bits[i] >= 0.5f) + result |= (1 << (3 - i)); + } + return result; + } + + juce::Rectangle bounds; float x, y, width, widthMargin, height, heightMargin, blockIncr; @@ -812,7 +858,6 @@ class BlockDiagrams : public juce::LookAndFeel_V4 } } } - } diff --git a/Source/DialLookAndFeel.h b/Source/DialLookAndFeel.h index 279d87b..944835a 100644 --- a/Source/DialLookAndFeel.h +++ b/Source/DialLookAndFeel.h @@ -131,7 +131,7 @@ class EditableTextBoxSlider : public juce::Component , juce::AudioProcessorParam } - void mouseDown(const juce::MouseEvent& m) override + /* void mouseDown(const juce::MouseEvent& m) override { auto mousePoint = m.getPosition().toFloat(); dragStartPoint.y = mousePoint.y; @@ -145,6 +145,28 @@ class EditableTextBoxSlider : public juce::Component , juce::AudioProcessorParam float value = juce::jlimit(0.0f, 1.0f, deltaY/100.0f); // clamp this textValueToParamValue(value); } + */ + + void mouseDown(const juce::MouseEvent& m) override + { + auto mousePoint = m.getPosition().toFloat(); + dragStartPoint.y = mousePoint.y; + + // Get the normalized parameter value (0.0-1.0) + initialParamValue = audioProcessor.apvts.getParameter(parameterID)->getValue(); + } + + void mouseDrag(const juce::MouseEvent& m) override + { + auto mousePoint = m.getPosition().toFloat(); + float deltaY = mousePoint.y - dragStartPoint.y; // Remove std::abs to allow bidirectional dragging + + // Modify the initial normalized value based on drag distance + float sensitivity = 0.01f; // Adjust this value to change drag sensitivity + float newValue = juce::jlimit(0.0f, 1.0f, initialParamValue + (-deltaY * sensitivity)); + textValueToParamValue(newValue); + } + void mouseUp(const juce::MouseEvent& m) override { @@ -165,7 +187,7 @@ class EditableTextBoxSlider : public juce::Component , juce::AudioProcessorParam auto value = l->getText().getFloatValue(); float valueLimited = juce::jlimit(rangeStart, rangeEnd, value); - l->setText(juce::String(valueLimited, 2), juce::dontSendNotification); + l->setText(juce::String(valueLimited, 3), juce::dontSendNotification); textBox.setInterceptsMouseClicks(false, false); float normalized = (valueLimited - rangeStart) / (rangeEnd - rangeStart); @@ -206,7 +228,7 @@ class EditableTextBoxSlider : public juce::Component , juce::AudioProcessorParam if (newParameterID == parameterID) { - juce::String formattedValue = juce::String(scaledValue, 2) + parameterSuffix; + juce::String formattedValue = juce::String(scaledValue, 3) + parameterSuffix; textBox.setText(formattedValue, juce::dontSendNotification); } } @@ -217,6 +239,7 @@ class EditableTextBoxSlider : public juce::Component , juce::AudioProcessorParam } private: + float initialParamValue; float rangeStart, rangeEnd; std::atomic newValueAtomic; std::atomic parameterIndexAtomic; diff --git a/Source/Graphics.h b/Source/Graphics.h index 36ef83d..ca02cff 100644 --- a/Source/Graphics.h +++ b/Source/Graphics.h @@ -3,6 +3,7 @@ #include #include "LookAndFeel.h" +//Takuma your waveforms look like a butt class OperatorDisplayGraphics : public juce::Component { @@ -61,14 +62,7 @@ class OperatorDisplayGraphics : public juce::Component void resized() override {} - void setEnvelope(int index, float attack, float decay, float sustain, float release) - { - op[index].attack = attack; - op[index].decay = decay; - op[index].sustain = sustain/10.0f; - op[index].release = release; - repaint(); - } + void setRatioAndAmplitude(float ratio, float fixed, float modIndex, bool isRatio) { @@ -77,7 +71,7 @@ class OperatorDisplayGraphics : public juce::Component this->modIndex = modIndex; repaint(); } - + private: float ratio, fixed, modIndex; }; @@ -145,40 +139,41 @@ class EnvelopeDisplayGraphics : public juce::Component float widthMargin = bounds.getWidth() * 0.05f; float heightMargin = bounds.getHeight() * 0.05f; - points[0].coords = { x + widthMargin, y + height + heightMargin }; // initial - points[1].coords = { x + widthMargin + width * attackPct, y + heightMargin + height * 0.5f }; // peak - points[2].coords = { x + widthMargin + width * decayPct, y + heightMargin + height * 0.5f}; // sustain start - points[3].coords = { x + widthMargin + width * sustainPct, y + heightMargin + height * 0.3f }; // sustain end - points[4].coords = { x + widthMargin + width * releasePct, y + heightMargin + height }; // end of envelope + points[0].coords = { x + widthMargin, y + height + heightMargin }; // Bottom (0) + points[1].coords = { x + widthMargin + width * attackPct, y + heightMargin }; // Top (1.0) + points[2].coords = { x + widthMargin + width * (attackPct + decayPct), y + heightMargin + height * (1.0f - sustain) }; // Sustain level + points[3].coords = { x + widthMargin + width * (attackPct + decayPct + sustainPct), y + heightMargin + height * (1.0f - sustain) }; // Same sustain level + points[4].coords = { x + widthMargin + width, y + height + heightMargin }; // Bottom (0) repaint(); } - void setEnvelope(float attack, float decay, float sustain, float release) - { - this->attack = attack; - this->decay = decay; - this->sustain = sustain; - this->release = release; - - float sustainPercent = 0.25f; - float remainderPercent = 1.0f - sustainPercent; - float adrSum = attack + decay + release; + +void setEnvelope(float attack, float decay, float sustain, float release) +{ + this->attack = attack; + this->decay = decay; + this->sustain = sustain / 100.0f; + this->release = release; - if (adrSum > 0.0f) { - attackPct = (attack / adrSum) * remainderPercent; - decayPct = (decay / adrSum) * remainderPercent; - releasePct = (release / adrSum) * remainderPercent; - - } else { - float evenShare = remainderPercent / 3.0f; - attackPct = evenShare; - decayPct = evenShare; - releasePct = evenShare; - - } - sustainPct = sustainPercent; + // Sustain always takes 25% of width + sustainPct = 0.25f; + + // Calculate A+D+R proportions for remaining 75% + float adrSum = attack + decay + release; + + if (adrSum > 0.0f) { + attackPct = (attack / adrSum) * 0.75f; + decayPct = (decay / adrSum) * 0.75f; + releasePct = (release / adrSum) * 0.75f; + } else { + attackPct = 0.25f; + decayPct = 0.25f; + releasePct = 0.25f; } + calculateSegment(); +} + void drawSegment(juce::Graphics &g, float x, float y, float width, float height) { juce::Path envelopePath; diff --git a/Source/Operator.cpp b/Source/Operator.cpp index d22f721..fc72f84 100644 --- a/Source/Operator.cpp +++ b/Source/Operator.cpp @@ -19,13 +19,6 @@ void FMOperator::prepareToPlay(double sampleRate, float samplesPerBlock, int num ratioSmoothed.reset(sampleRate, 0.001); fixedSmoothed.reset(sampleRate, 0.001); modIndexSmoothed.reset(sampleRate, 0.001); - - - float smoothingTimeSeconds = frequencySmoothingTimeMs / 1000.0f; - frequencySmoothingCoeff = 1.0f - std::exp(-1.0f / (smoothingTimeSeconds * sampleRate)); - - currentFrequency = 0.0f; - targetFrequency = 0.0f; } void FMOperator::startNote() @@ -54,7 +47,7 @@ void FMOperator::setEnvelope(float attackInMs, float decayInMs, float sustainInF void FMOperator::setNoteNumber(float noteNumber) { - targetFrequency = juce::MidiMessage::getMidiNoteInHertz(noteNumber); + noteFrequency = juce::MidiMessage::getMidiNoteInHertz(noteNumber); } void FMOperator::setOperator(float ratio, float fixed, bool isFixed, float modIndex) @@ -65,29 +58,24 @@ void FMOperator::setOperator(float ratio, float fixed, bool isFixed, float modIn this->isFixed = isFixed; } -float FMOperator::processOperator(float phase1, float phase2, float phase3, float phase4, float feedback) +float FMOperator::processOperator(float phase1, float phase2, float phase3, float phase4) { - - - currentFrequency += frequencySmoothingCoeff * (targetFrequency - currentFrequency); - - + currentFrequency += frequencySmoothingCoeff * (targetFrequency - currentFrequency); frequency = currentFrequency * ratioSmoothed.getNextValue(); if (isFixed) frequency = fixedSmoothed.getNextValue(); - operatorAngle = frequency/sampleRate; - - - - float modulatorPhase = phase1 + phase2 + phase3 + phase4 + feedback; + float modulatorPhase = phase1 + phase2 + phase3 + phase4; float twopi = juce::MathConstants::twoPi; float envelope = ampEnvelope.getNextSample(); float waveform = std::sin(operatorPhase * twopi + (modulatorPhase * modIndexSmoothed.getNextValue())) * envelope; + + + // accumulate and wrap operatorPhase += operatorAngle; if (operatorPhase >= 1.0) operatorPhase -= 1.0; diff --git a/Source/Operator.h b/Source/Operator.h index db711f0..f3d8649 100644 --- a/Source/Operator.h +++ b/Source/Operator.h @@ -13,7 +13,7 @@ class FMOperator void setEnvelope(float attack, float decay, float sustain, float release, bool isLooping); void setNoteNumber(float noteNumber); void setOperator(float ratio, float fixed, bool isFixed, float modIndex); - float processOperator(float phase1, float phase2, float phase3, float phase4, float feedbackPhase); + float processOperator(float phase1, float phase2, float phase3, float phase4); private: double sampleRate; @@ -28,9 +28,8 @@ class FMOperator float frequencySmoothingTimeMs = 100.0f; float frequencySmoothingCoeff = 0.0f; + juce::SmoothedValue ratioSmoothed, fixedSmoothed, modIndexSmoothed; juce::ADSR ampEnvelope; juce::ADSR::Parameters envParameters; - - }; diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index dfc7ca3..b1ecde6 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -13,7 +13,6 @@ #include "UserInterface.h" #include "AlgorithmGraphics.h" #include "LookAndFeel.h" -#include "Graphics.h" //============================================================================== /** diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 6fa71cb..ee4c6e1 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -150,7 +150,7 @@ void FledgeAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce: { juce::ScopedNoDenormals noDenormals; - /* + float globalAttack = apvts.getRawParameterValue("globalAttack")->load(); float globalDecay = apvts.getRawParameterValue("globalDecay")->load(); float globalSustain = apvts.getRawParameterValue("globalSustain")->load(); @@ -170,10 +170,12 @@ void FledgeAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce: juce::String ratioID = "ratio" + juce::String(oper); juce::String fixedID = "fixed" + juce::String(oper); juce::String modIndexID = "amplitude" + juce::String(oper); + juce::String operatorRoutingID = "operator" + juce::String(oper) + "Routing"; float ratio = apvts.getRawParameterValue(ratioID)->load(); float fixed = apvts.getRawParameterValue(fixedID)->load(); float modIndex = apvts.getRawParameterValue(modIndexID)->load(); + float routing = apvts.getRawParameterValue(operatorRoutingID)->load(); for (int v = 0; v < synth.getNumVoices(); v++) { @@ -182,10 +184,12 @@ void FledgeAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce: voice->setEnvelope(oper, attack, decay, sustain/100.0f, release, globalAttack, globalDecay, globalSustain, globalRelease); voice->setFMParameters(oper, ratio, fixed, false, modIndex); + voice->setOperatorGain(oper, routing); } } } - */ + + synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples()); } @@ -226,15 +230,15 @@ juce::AudioProcessorValueTreeState::ParameterLayout FledgeAudioProcessor::create { juce::AudioProcessorValueTreeState::ParameterLayout layout; - layout.add(std::make_unique(juce::ParameterID { "port", 1 }, "Glide", juce::NormalisableRange(0.0f, 100.0f, 0.1f), 0.0f)); + layout.add(std::make_unique(juce::ParameterID { "port", 1 }, "Glide", juce::NormalisableRange(0.0f, 100.0f, 0.01f), 0.0f)); - layout.add(std::make_unique(juce::ParameterID { "globalAttack", 1 }, "Global Attack", juce::NormalisableRange(-100.0f, 100.0f, 0.1f), 0.0f)); + layout.add(std::make_unique(juce::ParameterID { "globalAttack", 1 }, "Global Attack", juce::NormalisableRange(-100.0f, 100.0f, 0.01f), 0.01f)); - layout.add(std::make_unique(juce::ParameterID { "globalDecay", 1 }, "Global Decay", juce::NormalisableRange(-100.0f, 100.0f, 0.1f), 0.0f)); + layout.add(std::make_unique(juce::ParameterID { "globalDecay", 1 }, "Global Decay", juce::NormalisableRange(-100.0f, 100.0f, 0.01f), 0.2f)); - layout.add(std::make_unique(juce::ParameterID { "globalSustain", 1 }, "Global Sustain", juce::NormalisableRange(-100.0f, 100.0f, 0.1f), 0.0f)); + layout.add(std::make_unique(juce::ParameterID { "globalSustain", 1 }, "Global Sustain", juce::NormalisableRange(-100.0f, 100.0f, 0.01f), 80.0f)); - layout.add(std::make_unique(juce::ParameterID { "globalRelease", 1 }, "Global Release", juce::NormalisableRange(-100.0f, 100.0f, 0.1f), 0.0f)); + layout.add(std::make_unique(juce::ParameterID { "globalRelease", 1 }, "Global Release", juce::NormalisableRange(-100.0f, 100.0f, 0.1f), 1.0f)); for (int oper = 0; oper < 4; oper++) { @@ -242,12 +246,12 @@ juce::AudioProcessorValueTreeState::ParameterLayout FledgeAudioProcessor::create juce::String attackID = "attack" + juce::String(oper); juce::String attackName = "Attack " + juce::String(oper); - layout.add(std::make_unique(juce::ParameterID { attackID, 1 }, attackName, juce::NormalisableRange(0.0f, 20.0f, 0.1f, 0.5f), 1.0f)); + layout.add(std::make_unique(juce::ParameterID { attackID, 1 }, attackName, juce::NormalisableRange(0.0f, 20.0f, 0.01f, 0.5f), .01f)); juce::String decayID = "decay" + juce::String(oper); juce::String decayName = "Decay " + juce::String(oper); - layout.add(std::make_unique(juce::ParameterID { decayID, 1 }, decayName, juce::NormalisableRange(0.0f, 20.0f, 0.1f, 0.5f), 1.0f)); + layout.add(std::make_unique(juce::ParameterID { decayID, 1 }, decayName, juce::NormalisableRange(0.0f, 20.0f, 0.01f, 0.5f), .2f)); juce::String sustainID = "sustain" + juce::String(oper); juce::String sustainName = "Sustain " + juce::String(oper); @@ -257,7 +261,7 @@ juce::AudioProcessorValueTreeState::ParameterLayout FledgeAudioProcessor::create juce::String releaseID = "release" + juce::String(oper); juce::String releaseName = "Release " + juce::String(oper); - layout.add(std::make_unique(juce::ParameterID { releaseID, 1 }, releaseName, juce::NormalisableRange(0.0f, 20.0f, 0.1f, 0.5f), 1.0f)); + layout.add(std::make_unique(juce::ParameterID { releaseID, 1 }, releaseName, juce::NormalisableRange(0.0f, 20.0f, 0.01f, 0.5f), 1.0f)); //******** Ratio and FM Amount ********// juce::String ratioID = "ratio" + juce::String(oper); diff --git a/Source/UserInterface.cpp b/Source/UserInterface.cpp index 8ad3549..a8ee391 100644 --- a/Source/UserInterface.cpp +++ b/Source/UserInterface.cpp @@ -66,7 +66,6 @@ void OperatorInterface::paint(juce::Graphics &g) g.fillPath(boundsPath); g.setColour(juce::Colour(35, 37, 36)); g.strokePath(boundsPath, juce::PathStrokeType(2.0f)); - } @@ -92,10 +91,25 @@ void OperatorInterface::resized() opGraphics.setBounds(x + 100, y + height * 0.125f, sliderSize * 2, height * 0.75f); - attackLabel.setBounds(x + width * 0.8f, y + height * 0.1f, width * 0.035f, height * 0.2f); - decayLabel.setBounds(x + width * 0.8f, y + height * 0.3f, width * 0.035f, height * 0.2f); - sustainLabel.setBounds(x + width * 0.8f, y + height * 0.5f, width * 0.035f, height * 0.2f); - releaseLabel.setBounds(x + width * 0.8f, y + height * 0.7f, width * 0.035f, height * 0.2f); + attackLabel.setBounds(x + width * 0.8f, + y + height * 0.1f, + width * 0.035f, + height * 0.2f); + + decayLabel.setBounds(x + width * 0.8f, + y + height * 0.3f, + width * 0.035f, + height * 0.2f); + + sustainLabel.setBounds(x + width * 0.8f, + y + height * 0.5f, + width * 0.035f, + height * 0.2f); + + releaseLabel.setBounds(x + width * 0.8f, + y + height * 0.7f, + width * 0.035f, + height * 0.2f); attackSlider->setBounds(x + width * 0.835f, y + height * 0.1f, width * 0.165f, height * 0.2f); decaySlider->setBounds(x + width * 0.835f, y + height * 0.3f, width * 0.165f, height * 0.2f); @@ -127,9 +141,9 @@ void OperatorInterface::timerCallback() opGraphics.setRatioAndAmplitude(ratio, fixed, modIndex, opMode); float attack = audioProcessor.apvts.getRawParameterValue("attack" + juce::String(index))->load(); - float decay = audioProcessor.apvts.getRawParameterValue("release" + juce::String(index))->load(); + float decay = audioProcessor.apvts.getRawParameterValue("decay" + juce::String(index))->load(); float sustain = audioProcessor.apvts.getRawParameterValue("sustain" + juce::String(index))->load(); - bool release = audioProcessor.apvts.getRawParameterValue("release" + juce::String(index))->load(); + float release = audioProcessor.apvts.getRawParameterValue("release" + juce::String(index))->load(); envGraphics.setEnvelope(attack, decay, sustain, release); } diff --git a/Source/UserInterface.h b/Source/UserInterface.h index 801f575..5ab1b88 100644 --- a/Source/UserInterface.h +++ b/Source/UserInterface.h @@ -81,6 +81,7 @@ class AlgorithmSelectInterface : public juce::Component } } + void paint(juce::Graphics& g) override {} void resized() override @@ -105,7 +106,29 @@ class AlgorithmSelectInterface : public juce::Component blockHeight); } } + +/* + void buttonClicked(juce::Button* buttonClicked) override + { + if (buttonClicked == &algorithm[0]){ + } else if (buttonClicked == &algorithm[1]) { + + } else if (buttonClicked == &algorithm[2]) { + + } else if (buttonClicked == &algorithm[3]) { + + } else if (buttonClicked == &algorithm[4]) { + + } else if (buttonClicked == &algorithm[5]) { + + } else if (buttonClicked == &algorithm[6]) { + + } else if (buttonClicked == &algorithm[7]) { + + } + } +*/ private: std::array algorithmGraphics; diff --git a/Source/VoiceProcessor.h b/Source/VoiceProcessor.h index 531d7cf..a777afc 100644 --- a/Source/VoiceProcessor.h +++ b/Source/VoiceProcessor.h @@ -29,8 +29,6 @@ class SynthVoice : public juce::SynthesiserVoice { op[i].prepareToPlay(sampleRate, samplesPerBlock, numChannels); } - - setOperatorGain(); // move this elsewhere } bool canPlaySound (juce::SynthesiserSound* sound) override @@ -79,37 +77,31 @@ class SynthVoice : public juce::SynthesiserVoice void controllerMoved(int controllerNumber, int newControllerValue) override {} void renderNextBlock(juce::AudioBuffer &outputBuffer, int startSample, int numSamples) override { - setOperatorGain(); // move elsewehre later for (int sample = 0; sample < outputBuffer.getNumSamples(); ++sample) { op3 = op[3].processOperator(op0 * op3Gain[0], op1 * op3Gain[1], op2 * op3Gain[2], - op3 * op3Gain[3], - feedback * op3Gain[4]); + op3 * op3Gain[3]); op2 = op[2].processOperator(op0 * op2Gain[0], op1 * op2Gain[1], op2 * op2Gain[2], - op3 * op2Gain[3], - feedback * op2Gain[4]); + op3 * op2Gain[3]); op1 = op[1].processOperator(op0 * op1Gain[0], op1 * op1Gain[1], op2 * op1Gain[2], - op3 * op1Gain[3], - feedback * op1Gain[4]); + op3 * op1Gain[3]); op0 = op[0].processOperator(op0 * op0Gain[0], op1 * op0Gain[1], op2 * op0Gain[2], - op3 * op0Gain[3], - feedback * op0Gain[4]); + op3 * op0Gain[3]); float output = op0 * outputGain[0] + op1 * outputGain[1] + op2 * outputGain[2] + - op3 * outputGain[3] + - feedback * outputGain[4]; + op3 * outputGain[3]; for (int channel = 0; channel < outputBuffer.getNumChannels(); ++channel) { // outputBuffer.setSample(channel, sample, output); @@ -119,27 +111,44 @@ class SynthVoice : public juce::SynthesiserVoice } } - void setOperatorGain() + void setOperatorGain(int index, int gainIndex) { - // potentially replace with a different data structure than an array? - op3Gain = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; // no feedback atm - op2Gain = { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f }; - op1Gain = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }; - op0Gain = { 0.0f, 1.0f, 0.0f, 0.0f, 0.0f }; - outputGain = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f }; // op1, op2, op3, op4, feedback - + switch(index){ + case 0: + outputGain = toBinary4(gainIndex); + break; + case 1: + op0Gain = toBinary4(gainIndex); + break; + case 2: + op1Gain = toBinary4(gainIndex); + break; + case 3: + op2Gain = toBinary4(gainIndex); + break; + case 4: + op3Gain = toBinary4(gainIndex); + break; + } } - private: + std::array toBinary4(int input) + { + std::array bits; + for (int i = 0; i < 4; ++i) + bits[i] = (input >> i) & 1; + return bits; + } + double sampleRate; float op0 = 0.0f, op1 = 0.0f, op2 = 0.0f, op3 = 0.0f, feedback = 0.0f; // unit delays for algorithm - std::array op3Gain = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; - std::array op2Gain = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; - std::array op1Gain = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; - std::array op0Gain = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; - std::array outputGain = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; + std::array op3Gain = { 0.0f, 0.0f, 0.0f, 0.0f }; + std::array op2Gain = { 0.0f, 0.0f, 0.0f, 0.0f }; + std::array op1Gain = { 0.0f, 0.0f, 0.0f, 0.0f }; + std::array op0Gain = { 0.0f, 0.0f, 0.0f, 0.0f }; + std::array outputGain = { 0.0f, 0.0f, 0.0f, 0.0f }; std::array op; From f041d4206f7085b24358c9b63d10f199fc7819be Mon Sep 17 00:00:00 2001 From: Repairer of Reputations Date: Thu, 24 Jul 2025 17:20:20 -0400 Subject: [PATCH 4/5] Add files via upload --- Source/AlgorithmGraphics.h | 291 ++++++++++++------------ Source/ButtonLookAndFeel.cpp | 11 + Source/ButtonLookAndFeel.h | 207 +++++++++++++++++ Source/ComboBoxLookAndFeel.cpp | 11 + Source/ComboBoxLookAndFeel.h | 68 ++++++ Source/DialLookAndFeel.h | 58 ++--- Source/Graphics.h | 263 ++++++++++++++++------ Source/LevelMeter.cpp | 86 +++++++ Source/LevelMeter.h | 59 +++++ Source/LookAndFeel.h | 52 ++++- Source/Measurement.h | 26 +++ Source/Operator.cpp | 16 +- Source/Operator.h | 5 +- Source/PluginEditor.cpp | 68 ++++-- Source/PluginEditor.h | 33 ++- Source/PluginProcessor.cpp | 54 ++++- Source/PluginProcessor.h | 13 +- Source/UserInterface.cpp | 395 ++++++++++++++++++++++++++------- Source/UserInterface.h | 206 +++++++---------- Source/VoiceProcessor.h | 92 ++++---- 20 files changed, 1447 insertions(+), 567 deletions(-) create mode 100644 Source/ButtonLookAndFeel.cpp create mode 100644 Source/ButtonLookAndFeel.h create mode 100644 Source/ComboBoxLookAndFeel.cpp create mode 100644 Source/ComboBoxLookAndFeel.h create mode 100644 Source/LevelMeter.cpp create mode 100644 Source/LevelMeter.h create mode 100644 Source/Measurement.h diff --git a/Source/AlgorithmGraphics.h b/Source/AlgorithmGraphics.h index a15e7fa..fda1bca 100644 --- a/Source/AlgorithmGraphics.h +++ b/Source/AlgorithmGraphics.h @@ -10,6 +10,7 @@ #pragma once #include +#include "LookAndFeel.h" class PatchCable : public juce::Component @@ -141,26 +142,37 @@ class OperatorBlock : public juce::Component { public: + void setIsOutput(bool isOutput) + { + this->isOutput = isOutput; + } + void paint(juce::Graphics& g) override { auto bounds = getLocalBounds().toFloat(); bounds.reduce(5, 5); blockSize = bounds.getWidth() * 0.2f; - blockRectangle = { blockCenterCoords.x - blockSize/2, blockCenterCoords.y - blockSize/2, - blockSize, blockSize - }; - + blockSize, blockSize }; juce::Path frontPath, leftSidePath, rightSidePath, botSidePath, topSidePath; calculatePerspective(); - drawBlockBackground(g); - drawBlockPoint(g, blockRectangle.getCentreX(), blockRectangle.getY() - 8.0f); - drawBlockPoint(g, blockRectangle.getCentreX(), blockRectangle.getY() + blockRectangle.getHeight() + 8.0f); - drawBlockForeground(g); + if (!isOutput){ + drawBlockBackground(g); + drawBlockPoint(g, blockRectangle.getCentreX(), blockRectangle.getY() - 8.0f); + drawBlockPoint(g, blockRectangle.getCentreX(), blockRectangle.getY() + blockRectangle.getHeight() + 8.0f); + drawBlockForeground(g); + g.setColour(juce::Colour(120, 120, 120)); + g.drawText(juce::String(operatorIndex), blockRectangle, juce::Justification::centred); + + } else { + drawBlockPoint(g, blockRectangle.getCentreX(), blockRectangle.getY() - 8.0f); + g.setColour(juce::Colour(120, 120, 120)); + g.drawText("Output", blockRectangle.getX(), blockRectangle.getY() - 16.0f, blockRectangle.getWidth(), blockRectangle.getHeight(), juce::Justification::centred); + } } @@ -198,14 +210,14 @@ class OperatorBlock : public juce::Component { juce::Path pointPath, outlinePath; - pointPath.addCentredArc(x, y, 2.0f, 2.0f, 0.0f, 0.0f, 6.28f, true); - g.setColour(juce::Colour(70, 204, 164)); + pointPath.addCentredArc(x, y, 2.5f, 2.5f, 0.0f, 0.0f, 6.28f, true); + g.setColour(Colors::mainColors[4]); g.fillPath(pointPath); if (pointInFocus) { - outlinePath.addCentredArc(x, y, 6.0f, 6.0f, 0.0f, 0.0f, 6.28f, true); - g.setColour(juce::Colour(50, 184, 144)); + outlinePath.addCentredArc(x, y, 8.0f, 8.0f, 0.0f, 0.0f, 6.28f, true); + g.setColour(Colors::mainColors[4]); g.strokePath(outlinePath, juce::PathStrokeType(1.0f)); } @@ -227,7 +239,7 @@ class OperatorBlock : public juce::Component topSidePath = createSidePath(blockRectangle.getTopLeft(), blockRectangle.getTopRight(), perspectiveTopRight, perspectiveTopLeft); topSidePath = topSidePath.createPathWithRoundedCorners(1.0f); - g.setColour(juce::Colour(30, 154, 114)); + g.setColour(Colors::mainColors[operatorIndex]); g.strokePath(leftSidePath, juce::PathStrokeType(1.0f)); g.strokePath(rightSidePath, juce::PathStrokeType(1.0f)); g.strokePath(topSidePath, juce::PathStrokeType(1.0f)); @@ -250,7 +262,7 @@ class OperatorBlock : public juce::Component graphicPath.addRectangle(blockRectangle); graphicPath = graphicPath.createPathWithRoundedCorners(1.0f); - g.setColour(juce::Colour(90, 224, 184)); + g.setColour(Colors::mainColors[operatorIndex]); g.strokePath(graphicPath, juce::PathStrokeType(1.0f)); if (!blockInFocus) @@ -279,7 +291,7 @@ class OperatorBlock : public juce::Component { juce::Path graphicPath; graphicPath.addCentredArc(x, y, 4.0f, 4.0f, 0.0f, 0.0f, 6.28f, true); - g.setColour(juce::Colour(120, 120, 120)); + g.setColour(Colors::mainColors[4]); g.strokePath(graphicPath, juce::PathStrokeType(1.0f)); } @@ -298,8 +310,9 @@ class OperatorBlock : public juce::Component juce::Rectangle blockRectangle = { blockCenterCoords.x - blockSize/2, blockCenterCoords.y - blockSize/2, blockSize, blockSize }; - - return blockRectangle.contains(mouse); + + if (!isOutput){ return blockRectangle.contains(mouse); } + else { return false; } } bool isOverOutputPoint(juce::Point mouse) @@ -387,7 +400,6 @@ class OperatorBlock : public juce::Component return numCableAvailable; } - private: float width, x, y; @@ -398,6 +410,7 @@ class OperatorBlock : public juce::Component // drawing state bool blockInFocus; bool pointInFocus; + bool isOutput; float blockSize; float perspective = 0.5f; @@ -411,19 +424,29 @@ class OperatorBlock : public juce::Component perspectiveBotRight; }; - - class AlgorithmGraphics : public juce::Component { public: AlgorithmGraphics() { + addAndMakeVisible(op[4]); + op[4].setOperatorIndex(4); + op[4].setInterceptsMouseClicks(false, false); + op[4].setIsOutput(true); + for (int i = 0; i < 4; i++) { addAndMakeVisible(op[i]); op[i].setInterceptsMouseClicks(false, false); op[i].setOperatorIndex(i); + op[i].setIsOutput(false); + + for (int j = 0; j < 4; j++){ + addAndMakeVisible(cable[i][j]); + cable[i][j].setInterceptsMouseClicks(false, false); + cable[i][j].setAlwaysOnTop(true); + } } for (int i = 0; i < 16; i++) @@ -443,54 +466,34 @@ class AlgorithmGraphics : public juce::Component auto bounds = getLocalBounds().toFloat(); calculateCoordinates(bounds); - // draw background fill - juce::Path boundsPath; - boundsPath.addRoundedRectangle(bounds, 10, 10); - g.setColour(juce::Colour(40, 42, 41)); - g.fillPath(boundsPath); - g.setColour(juce::Colour(30, 32, 31)); - g.strokePath(boundsPath, juce::PathStrokeType(2.0f)); - - // persepctive - juce::Path perspectivePath; - perspectivePath.startNewSubPath(x, y); - perspectivePath.lineTo(x + bounds.getWidth(), y + bounds.getHeight()); - perspectivePath.startNewSubPath(x + bounds.getWidth(), y); - perspectivePath.lineTo(x, y + bounds.getHeight()); - g.setColour(juce::Colour(90, 90, 90)); - g.strokePath(perspectivePath, juce::PathStrokeType(2)); - - juce::Point vp = bounds.getCentre(); op[0].setVanishingPoint(vp, 0.1f); op[1].setVanishingPoint(vp, 0.1f); op[2].setVanishingPoint(vp, 0.1f); op[3].setVanishingPoint(vp, 0.1f); - - } void resized() override { - auto bounds = getLocalBounds(); - + auto bounds = getBounds(); + juce::Point vp = bounds.getCentre().toFloat(); calculateCoordinates(bounds.toFloat()); - - op[0].setBounds(bounds); - op[1].setBounds(bounds); - op[2].setBounds(bounds); - op[3].setBounds(bounds); - - op[0].setBlockCenter(x + blockIncr * 2, y); - op[1].setBlockCenter(x + blockIncr, y + blockIncr); - op[2].setBlockCenter(x + blockIncr * 2, y + blockIncr * 2); - op[3].setBlockCenter(x + blockIncr * 3, y + blockIncr * 3); - for (int i = 0; i < 16; i++) - { - int j = i % 4; - int k = i / 4; - cable[k][j].setBounds(bounds); + op[0].setBlockCenter(x + blockIncr * 3, y + blockIncr * 3); + op[1].setBlockCenter(x + blockIncr * 2, y); + op[2].setBlockCenter(x + blockIncr, y + blockIncr); + op[3].setBlockCenter(x + blockIncr * 2, y + blockIncr * 2); + + op[4].setBlockCenter(x + blockIncr * 2, y + blockIncr * 5); // output + + for (int i = 0; i <= 4; i++){ + op[i].setBounds(bounds); + op[i].setVanishingPoint(vp, 0.1f); + + for (int j = 0; j < 4; j++) + { + cable[i][j].setBounds(bounds); + } } } @@ -534,7 +537,7 @@ class AlgorithmGraphics : public juce::Component int blk = *currentOutputBlockIndex; int cbl = *currentCableIndex; - for (int i = 0; i < 4; i++) + for (int i = 0; i <= 4; i++) { auto mouse = m.getEventRelativeTo(&op[i]).getPosition().toFloat(); auto globalMouse = op[i].getLocalPoint(this, m.getPosition()); @@ -542,36 +545,41 @@ class AlgorithmGraphics : public juce::Component op[i].setBlockInFocus(false); op[i].setPointInFocus(false); - - if (op[i].isOverBlock(mouse) && *dragState == 1) + // REFRESH CABLE POSITION WITH BLOCK + for (int j = 0; j < 4; j++) { - // DRAGGING BLOCK - op[i].setBlockInFocus(true); - op[i].setBlockCenter(globalMouse.x, globalMouse.y); + // iterating all cables for every block. + bool inUseAndConnected = cable[i][j].getIsInUse() && cable[i][j].getIsConnected(); + int outputIndex = cable[i][j].getCableOutputIndex(); // cable origin + int inputIndex = cable[i][j].getCableInputIndex(); // cable dest + + if (outputIndex == blk && outputIndex != -1 && inUseAndConnected) + { + DBG("outputs associated with block input: " << outputIndex); + auto outputPoint = op[outputIndex].getOutputPoint(); + cable[i][j].setOutputPoint(outputPoint); + } - // REFRESH CABLE POSITION WITH BLOCK - for (int j = 0; j < 4; j++) + if (inputIndex == i && inputIndex != -1 && inUseAndConnected) { - bool inUse = cable[i][j].getIsInUse(); - bool isConnected = cable[i][j].getIsConnected(); - int outputIndex = cable[i][j].getCableOutputIndex(); - int inputIndex = cable[i][j].getCableInputIndex(); - - - if (outputIndex == blk && outputIndex != -1 && inUse && isConnected) - { - auto outputPoint = op[blk].getOutputPoint(); - cable[i][j].setOutputPoint(outputPoint); - } - - if (inputIndex == i && inputIndex != -1 && inUse && isConnected) - { - auto inputPoint = op[i].getInputPoint(); - cable[i][j].setInputPoint(inputPoint); - } + + DBG("inputs associated with block output: " << inputIndex); + + auto inputPoint = op[inputIndex].getInputPoint(); + cable[i][j].setInputPoint(inputPoint); + } + + if (op[i].isOverBlock(mouse) && *dragState == 1) + { + DBG("current block being dragged: " << i); + // DRAGGING BLOCK + op[i].setBlockInFocus(true); + op[i].setBlockCenter(globalMouse.x, globalMouse.y); + } } + if (currentCableIndex.has_value() && *dragState == 2) { auto outputPoint = op[blk].getOutputPoint(); @@ -605,7 +613,7 @@ class AlgorithmGraphics : public juce::Component int blk = *currentOutputBlockIndex; int cbl = *currentCableIndex; - for (int i = 0; i < 4; i++) + for (int i = 0; i <= 4; i++) { auto mouse = m.getEventRelativeTo(&op[i]).getPosition().toFloat(); if (currentCableIndex.has_value() && op[i].isOverInputPoint(mouse) && *dragState == 2) @@ -621,8 +629,10 @@ class AlgorithmGraphics : public juce::Component op[outputIndex].setNumCableAvailable(-1); op[i].setInput(blk, 1.0f); - - } else if (currentCableIndex.has_value() && !op[i].isOverInputPoint(mouse) && *dragState == 2) + break; + } + + if (currentCableIndex.has_value() && !op[i].isOverInputPoint(mouse) && *dragState == 2) { cable[blk][cbl].setIsInUse(false); cable[blk][cbl].setIsConnected(false); @@ -633,7 +643,7 @@ class AlgorithmGraphics : public juce::Component currentOutputBlockIndex.reset(); dragState.reset(); - for (int i = 0; i < 4; i++) + for (int i = 0; i <= 4; i++) { op[i].setBlockInFocus(false); op[i].setPointInFocus(false); @@ -680,14 +690,13 @@ class AlgorithmGraphics : public juce::Component } return result; } - juce::Rectangle bounds; float x, y, width, widthMargin, height, heightMargin, blockIncr; - std::array op; - std::array, 4> cable; + std::array op; + std::array, 5> cable; std::optional currentCableIndex, currentOutputBlockIndex; std::optional dragState; // 0 = out of bounds, 1 = dragging block, 2 = dragging cable }; @@ -706,75 +715,68 @@ class BlockDiagrams : public juce::LookAndFeel_V4 void drawToggleButton (juce::Graphics& g, juce::ToggleButton& button, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override { auto bounds = button.getLocalBounds().toFloat(); - drawAlgorithm(g, bounds.getX(), bounds.getY(), bounds.getWidth()); + drawAlgorithm(g, bounds.getX(), bounds.getY(), bounds.getWidth(), shouldDrawButtonAsDown); } void selectAlgorithm() { + switch(graphicIndex){ case 0: - block.blockToUse = { 4, 7, 10, 11 }; + block.blockToUse = { 1, 5, 9, 13 }; block.connectValue = { DOWN, DOWN, DOWN, DOWN }; - block.label = { "3","2", "1", "N" }; + block.label = { "4", "3", "2", "1" }; break; case 1: - block.blockToUse = { 6, 9, 10, 11 }; - block.connectValue = { RIGHTDOWN, DOWN, DOWN, DOWN }; - block.label = { "3","1", "2", "N" }; + block.blockToUse = { 7, 9, 11, 14 }; + block.connectValue = { DOWN, DOWNRIGHT, DOWNLEFT, DOWN }; + block.label = { "4", "3", "2", "1" }; break; case 2: - block.blockToUse = { 7, 8, 9, 10 }; - block.connectValue = { DOWN, DOWNLEFT, DOWN, DOWN }; - block.label = { "3","2", "N", "1" }; - + block.blockToUse = { 9, 10, 11, 14 }; + block.connectValue = { DOWNRIGHT, DOWN, DOWNLEFT, DOWN }; + block.label = { "4", "3", "2", "1" }; break; case 3: - block.blockToUse = { 4, 6, 7, 10 }; - block.connectValue = { DOWN, DOWNRIGHT, DOWN, DOWN }; - block.label = { "3","N", "2", "1" }; + block.blockToUse = { 6, 9, 11, 14 }; + block.connectValue = { DOWNLEFTRIGHT, DOWNRIGHT, DOWNLEFT, DOWN }; + block.label = { "4", "3", "2", "1" }; break; case 4: - block.blockToUse = { 6, 7, 10, 11 }; - block.connectValue = { DOWNRIGHT, RIGHTDOWN, DOWN, DOWN }; - block.label = { "N","3", "1", "2" }; + block.blockToUse = { 9, 10, 13, 14 }; + block.connectValue = { DOWN, DOWN, DOWN, DOWN }; + block.label = { "2", "4", "1", "3" }; break; case 5: - block.blockToUse = { 6, 7, 8, 10 }; - block.connectValue = { DOWNRIGHT, DOWN, DOWNLEFT, DOWN }; - block.label = { "N","3", "2", "1" }; + block.blockToUse = { 5, 9, 12, 14 }; + block.connectValue = { DOWN, DOWNLEFTRIGHT, DOWN, DOWN }; + block.label = { "4", "3", "1", "2" }; break; case 6: - block.blockToUse = { 3, 5, 7, 10 }; - block.connectValue = { DOWNRIGHT, DOWNLEFT, DOWN, DOWN }; - block.label = { "N","3", "2", "1" }; + block.blockToUse = { 10, 12, 13, 14 }; + block.connectValue = { DOWN, DOWNLEFT, DOWN, DOWN }; + block.label = { "4", "1", "2", "3" }; break; case 7: - block.blockToUse = { 4, 7, 10, 11 }; + block.blockToUse = { 12, 13, 14, 15 }; block.connectValue = { DOWN, DOWN, DOWN, DOWN }; - block.label = { "N","3", "1", "2" }; - break; - - case 8: - block.blockToUse = { 4, 7, 8, 10 }; - block.connectValue = { DOWN, DOWN, DOWNLEFT, DOWN }; - block.label = { "N","3", "2", "1" }; + block.label = { "1", "2", "3", "4" }; break; } - } - void drawAlgorithm(juce::Graphics& g, float x, float y, float size) + void drawAlgorithm(juce::Graphics& g, float x, float y, float size, bool mouseDown) { - float graphicSize = size * 0.9f; - float margin = size * 0.15; + float graphicSize = size * 0.8f; + float margin = size * 0.2f; float blockSize = (graphicSize/4) * 0.7f; float blockMargin = (graphicSize/4) * 0.15f; @@ -789,11 +791,12 @@ class BlockDiagrams : public juce::LookAndFeel_V4 for (int j = 0; j < 4; j++) { if (i != block.blockToUse[j]) { - g.setColour(juce::Colour(30, 154, 114)); + g.setColour(juce::Colour(50, 50, 50)); g.strokePath(blockPath, juce::PathStrokeType(1.0f)); } else { - g.setColour(juce::Colour(90, 224, 184)); + auto fillColor = mouseDown ? Colors::mainHoverColors[j] : Colors::mainColors[j]; + g.setColour(fillColor); g.fillPath(blockPath); g.strokePath(blockPath, juce::PathStrokeType(1.0f)); break; @@ -833,9 +836,16 @@ class BlockDiagrams : public juce::LookAndFeel_V4 linePath.lineTo(xIncr + (blockSize + blockMargin) * 1.5f, yIncr + blockSize/2); linePath.lineTo(xIncr + (blockSize + blockMargin) * 1.5f, yIncr + (blockSize + blockMargin) * 1.5f); - } else if (block.connectValue[j] == NONE) { - - } + } else if (block.connectValue[j] == DOWNLEFTRIGHT) { + linePath.startNewSubPath(xIncr + blockSize * 0.425f, yIncr + blockSize/2); + linePath.lineTo(xIncr + blockSize * 0.425f, yIncr + (blockSize + blockMargin) * 1.5f); + linePath.lineTo(xIncr - (blockSize + blockMargin), yIncr + (blockSize + blockMargin) * 1.5f); + + linePath.startNewSubPath(xIncr + blockSize * 0.575f, yIncr + blockSize/2); + linePath.lineTo(xIncr + blockSize * 0.575f, yIncr + (blockSize + blockMargin) * 1.5f); + linePath.lineTo(xIncr + (blockSize + blockMargin) * 1.5f, yIncr + (blockSize + blockMargin) * 1.5f); + + } else if (block.connectValue[j] == NONE) {} } } @@ -846,18 +856,19 @@ class BlockDiagrams : public juce::LookAndFeel_V4 g.strokePath(linePath, juce::PathStrokeType(strokeType)); } - for (int i = 0; i < 16; i++){ // column - float xIncr = x + margin + (graphicSize/4) * (i % 4); - float yIncr = y + margin + (graphicSize/4) * (i / 4); - g.setColour(juce::Colour(150, 150, 150)); - g.setFont(11.0f); - - for (int j = 0; j < 4; j++) { - if (i == block.blockToUse[j]) { - g.drawText(block.label[j], xIncr + 0.75f, yIncr + 0.5f, blockSize, blockSize,juce::Justification::centred); - } + + for (int i = 0; i < 16; i++){ // column + float xIncr = x + margin + (graphicSize/4) * (i % 4); + float yIncr = y + margin + (graphicSize/4) * (i / 4); + g.setColour(juce::Colour(40, 40, 40)); + g.setFont(9.0f); + + for (int j = 0; j < 4; j++) { + if (i == block.blockToUse[j]) { + g.drawText(block.label[j], xIncr + 0.75f, yIncr + 0.5f, blockSize, blockSize,juce::Justification::centred); } } + } } @@ -869,9 +880,9 @@ class BlockDiagrams : public juce::LookAndFeel_V4 { std::array blockToUse; std::array connectValue; - std::array label = { "3","2", "1", "4" }; + std::array label = { "3", "2", "1", "4" }; }; - enum blockConnect { NONE, DOWN, DOWNLEFT, DOWNRIGHT, LEFTDOWN, RIGHTDOWN }; + enum blockConnect { NONE, DOWN, DOWNLEFT, DOWNRIGHT, LEFTDOWN, RIGHTDOWN, DOWNLEFTRIGHT }; blockValues block; }; diff --git a/Source/ButtonLookAndFeel.cpp b/Source/ButtonLookAndFeel.cpp new file mode 100644 index 0000000..9d06eec --- /dev/null +++ b/Source/ButtonLookAndFeel.cpp @@ -0,0 +1,11 @@ +/* + ============================================================================== + + ButtonLookAndFeel.cpp + Created: 19 Jul 2025 3:22:03pm + Author: Takuma Matsui + + ============================================================================== +*/ + +#include "ButtonLookAndFeel.h" diff --git a/Source/ButtonLookAndFeel.h b/Source/ButtonLookAndFeel.h new file mode 100644 index 0000000..8bd42b5 --- /dev/null +++ b/Source/ButtonLookAndFeel.h @@ -0,0 +1,207 @@ +/* + ============================================================================== + + ButtonLookAndFeel.h + Created: 19 Jul 2025 3:22:03pm + Author: Takuma Matsui + + ============================================================================== +*/ + +#pragma once +#include +#include "LookAndFeel.h" + +class ButtonLookAndFeel : public juce::LookAndFeel_V4 +{ +public: + ButtonLookAndFeel(int graphicIndex) + { + this->graphicIndex = graphicIndex; + } + + void drawButtonBackground (juce::Graphics &g, juce::Button &button, const juce::Colour &backgroundColour, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override + { + auto bounds = button.getLocalBounds().toFloat(); + float xPos = bounds.getX(); + float yPos = bounds.getY(); + float size = bounds.getHeight(); + float graphicWidth = bounds.getWidth(); + + // fill background + juce::Path bgPath; + bgPath.addRoundedRectangle(bounds, 5.0f, 5.0f); + g.setColour(juce::Colour(40, 42, 41)); + g.fillPath(bgPath); + + if (graphicIndex == 0) + { + drawSaveButton(g, xPos, yPos, size, size); + + } else if (graphicIndex == 1) + { + drawArrowButton(g, xPos, yPos, size, size, false); + + } else if (graphicIndex == 2) + { + drawArrowButton(g, xPos, yPos, size, size, true); + + } else if (graphicIndex == 3) + { + drawWaveIcon(g, xPos + (graphicWidth/2) - (size/2), yPos, size, shouldDrawButtonAsDown); + + } else if (graphicIndex == 4) + { + drawBlockIcon(g, xPos + (graphicWidth/2) - (size/2), yPos, size, shouldDrawButtonAsDown); + + } else if (graphicIndex == 5) + { + drawMacroIcon(g, xPos + (graphicWidth/2) - (size/2), yPos, size, shouldDrawButtonAsDown); + } + } + + void drawSaveButton(juce::Graphics& g, float x, float y, float width, float height) + { + float graphicMargin = width * 0.3f; + float graphicSize = width * 0.4f; + x = x + graphicMargin; + y = y + graphicMargin; + + juce::Point topLeft { x, y }; + juce::Point slopeStart { x + graphicSize * 0.75f, y }; + juce::Point slopeEnd { x + graphicSize, y + graphicSize * 0.25f}; + juce::Point botRight { x + graphicSize, y + graphicSize }; + juce::Point botLeft { x, y + graphicSize }; + + juce::Path bodyPath; + bodyPath.startNewSubPath(topLeft); + bodyPath.lineTo(slopeStart); + bodyPath.lineTo(slopeEnd); + bodyPath.lineTo(botRight); + bodyPath.lineTo(botLeft); + bodyPath.closeSubPath(); + bodyPath = bodyPath.createPathWithRoundedCorners(1.0f); + g.setColour(Colors::mainColors[0]); + g.fillPath(bodyPath); + } + + + void drawArrowButton(juce::Graphics& g, float x, float y, float width, float height, bool isLeftArrow) + { + float graphicMargin = width * 0.4f; + float graphicSize = width * 0.2f; + x = x + graphicMargin; + y = y + graphicMargin; + + // coordinates + juce::Point topLeft { x, y }; + juce::Point botLeft { x, y + graphicSize }; + juce::Point middleRight { x + graphicSize, y + graphicSize/2 }; + juce::Point topRight { x + graphicSize, y }; + juce::Point middleLeft { x, y + graphicSize/2 }; + juce::Point botRight { x + graphicSize, y + graphicSize }; + + // drawing + juce::Path arrowPath; + if (isLeftArrow){ + arrowPath.startNewSubPath(topLeft); + arrowPath.lineTo(botLeft); + arrowPath.lineTo(middleRight); + arrowPath.closeSubPath(); + } else { + arrowPath.startNewSubPath(topRight); + arrowPath.lineTo(botRight); + arrowPath.lineTo(middleLeft); + arrowPath.closeSubPath(); + } + + arrowPath = arrowPath.createPathWithRoundedCorners(1.0f); + g.setColour(Colors::mainColors[0]); + g.fillPath(arrowPath); + } + + + void drawBlockIcon(juce::Graphics &g, float x, float y, float size, bool buttonDown) + { + juce::Path blockPath, linePath; + float offset = size * 0.175f; + float blockSize = size * 0.225f; + + juce::Point centerCoords = {x + size/2, y + size/2}; + juce::Point topCoords = {centerCoords.x, centerCoords.y - offset}; + juce::Point botLeftCoords = {centerCoords.x - offset, centerCoords.y + offset}; + juce::Point botRightCoords = {centerCoords.x + offset, centerCoords.y + offset}; + + linePath.startNewSubPath(topCoords.x, topCoords.y + blockSize/2); + linePath.lineTo(botLeftCoords.x, botLeftCoords.y - blockSize/2); + + linePath.startNewSubPath(topCoords.x, topCoords.y + blockSize/2); + linePath.lineTo(botRightCoords.x, botLeftCoords.y - blockSize/2); + g.setColour(juce::Colour(70, 204, 164)); + g.strokePath(linePath, juce::PathStrokeType(1.0f)); + + + blockPath.addRoundedRectangle(topCoords.x - blockSize/2, topCoords.y - blockSize/2, blockSize, blockSize, 2.0f); + blockPath.addRoundedRectangle(botLeftCoords.x - blockSize/2, botLeftCoords.y - blockSize/2, blockSize, blockSize, 2.0f); + blockPath.addRoundedRectangle(botRightCoords.x - blockSize/2, botRightCoords.y - blockSize/2, blockSize, blockSize, 2.0f); + + g.setColour(Colors::mainColors[1]); // different color here + g.fillPath(blockPath); + g.setColour(Colors::mainColors[1]); + g.strokePath(blockPath, juce::PathStrokeType(1.0f)); + } + + void drawWaveIcon(juce::Graphics &g, float x, float y, float size, bool buttonDown) + { + juce::Path linePath; + for (int i = 0; i < 3; i++) + { + float yIncr = (size/6) * (i + 3); + float xIncr = (size/5) * (i + 1); + float height = size * 0.25f; + juce::Point leftCoords = {x + size * 0.05f, y + yIncr }; + juce::Point rightCoords = {x + size * 0.9f, y + yIncr }; + + juce::Point midCoords = {leftCoords.x + xIncr, + y + yIncr - height }; + + linePath.startNewSubPath(leftCoords); + linePath.lineTo(midCoords.x - 2.0f, leftCoords.y); // maybe dont hard code margins + linePath.lineTo(midCoords); + linePath.lineTo(midCoords.x + 2.0f, rightCoords.y); + linePath.lineTo(rightCoords); + linePath = linePath.createPathWithRoundedCorners(4.0f); + g.setColour(Colors::mainColors[0]); + g.strokePath(linePath, juce::PathStrokeType(1.0f)); + } + } + + void drawMacroIcon(juce::Graphics &g, float x, float y, float size, bool buttonDown) + { + + juce::Path outlinePath, bodyPath, dotPath; + juce::Point centerCoords = { x + size/2, y + size/2 }; + float pi = juce::MathConstants::pi; + float outlineRadius = size * 0.3f; + float bodyRadius = size * 0.25f; + float dotRadius = size * 0.125f; + + outlinePath.addCentredArc(centerCoords.x, centerCoords.y, outlineRadius, outlineRadius, 0.0f, 1.25f * pi, 2.75f * pi, true); + g.setColour(Colors::mainColors[2]); + g.strokePath(outlinePath, juce::PathStrokeType(1.0f)); + + bodyPath.addCentredArc(centerCoords.x, centerCoords.y, bodyRadius, bodyRadius, 0.0f, 0.0f, 6.28f, true); + g.setColour(Colors::mainColors[2]); + g.fillPath(bodyPath); + + dotPath.addCentredArc(centerCoords.x + dotRadius, centerCoords.y - dotRadius, 1.5f, 1.5f, 0.0f, 0.0f, 6.28f, true); + g.setColour(juce::Colour(50, 50, 50)); + g.fillPath(dotPath); + + } + + +private: + int graphicIndex; + +}; diff --git a/Source/ComboBoxLookAndFeel.cpp b/Source/ComboBoxLookAndFeel.cpp new file mode 100644 index 0000000..44ede5c --- /dev/null +++ b/Source/ComboBoxLookAndFeel.cpp @@ -0,0 +1,11 @@ +/* + ============================================================================== + + ComboBoxLookAndFeel.cpp + Created: 22 Jul 2025 3:25:32pm + Author: Takuma Matsui + + ============================================================================== +*/ + +#include "ComboBoxLookAndFeel.h" diff --git a/Source/ComboBoxLookAndFeel.h b/Source/ComboBoxLookAndFeel.h new file mode 100644 index 0000000..421f40f --- /dev/null +++ b/Source/ComboBoxLookAndFeel.h @@ -0,0 +1,68 @@ +/* + ============================================================================== + + ComboBoxLookAndFeel.h + Created: 22 Jul 2025 3:25:32pm + Author: Takuma Matsui + + ============================================================================== +*/ + +#pragma once +#include +#include "LookAndFeel.h" + +class ComboBoxGraphics : public juce::LookAndFeel_V4 +{ +public: + ComboBoxGraphics() + { + setColour(juce::ComboBox::textColourId, juce::Colours::transparentBlack); + + } + + void drawComboBox(juce::Graphics& g, int width, int height, bool isButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, juce::ComboBox& comboBox) override + { + auto bounds = juce::Rectangle(width, height).toFloat(); + bounds.reduce(5, 5); + juce::Path comboBoxPath, trianglePath, buttonPath; + + comboBoxPath.addRoundedRectangle(bounds, 5.0f); + g.setColour(juce::Colour(40, 42, 41)); + g.fillPath(comboBoxPath); + + auto buttonBounds = juce::Rectangle(buttonX, buttonY, buttonW, buttonH); + buttonPath.addRoundedRectangle(buttonBounds, 3.0f); + + + g.setFont(12.0f); + g.setColour(Colors::textColor); + g.drawText(comboBox.getText(), bounds, juce::Justification::centred, false); + } + + void drawPopupMenuItem(juce::Graphics& g, const juce::Rectangle& area, bool isSeparator, bool isActive, bool isHighlighted, bool isTicked, bool hasSubMenu, const juce::String& text, const juce::String& shortcutKeyText, const juce::Drawable* icon, const juce::Colour* textColour) override + { + juce::Path menuPath; + menuPath.addRectangle(area); + + juce::Colour menuColor; + menuColor = isHighlighted ? juce::Colour(40, 42, 41) : juce::Colour(40, 42, 41); + g.setColour(menuColor); + g.fillPath(menuPath); + + g.setColour(Colors::textColor); + g.setFont(12.0f); + g.drawText(text, area, juce::Justification::centred, false); + } + + void drawPopupMenuBackground(juce::Graphics& g, int width, int height) override + { + juce::Path menuPath; + menuPath.addRectangle(0, 0, width, height); + g.setColour(juce::Colour(40, 42, 41)); + g.fillPath(menuPath); + } + + void drawResizableFrame(juce::Graphics& g, int w, int h, const juce::BorderSize& b) override {} +}; + diff --git a/Source/DialLookAndFeel.h b/Source/DialLookAndFeel.h index 944835a..0c99fe7 100644 --- a/Source/DialLookAndFeel.h +++ b/Source/DialLookAndFeel.h @@ -11,29 +11,21 @@ #pragma once #include #include "PluginProcessor.h" +#include "LookAndFeel.h" class DialLookAndFeel : public juce::LookAndFeel_V4 { public: - void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, juce::Slider &slider) override { auto bounds = slider.getLocalBounds().toFloat(); float xPos = bounds.getX(); float yPos = bounds.getY(); - float graphicWidth = bounds.getWidth(); - float graphicHeight = bounds.getHeight(); + float size = bounds.getWidth(); - displayText(g, xPos, yPos, graphicWidth, graphicHeight, juce::String(slider.getValue())); + drawRoundDial(g, xPos, yPos, size, sliderPosProportional); } - void displayText(juce::Graphics &g, float x, float y, float width, float height, juce::String value) - { - g.setFont(juce::FontOptions(40.0f, juce::Font::plain)); - g.setColour(juce::Colour(255, 255, 255)); - g.drawText(value, x, y, width, height, juce::Justification::centredLeft); - } - void drawRoundDial(juce::Graphics &g, float x, float y, float size, float position) { //============================================================================== @@ -48,15 +40,15 @@ class DialLookAndFeel : public juce::LookAndFeel_V4 juce::Path dialBodyPath, dialDotPath, dialOutlinePath, dialSelectPath, tensionLeftPath, tensionRightPath; float dialOutlineRadius = (size * 0.8f)/2; - float dialBodyRadius = (size * 0.7f)/2; - float dialDotRadius = (size * 0.5f)/2; + float dialBodyRadius = (size * 0.65f)/2; + float dialDotRadius = (size * 0.45f)/2; dialOutlinePath.addCentredArc(x + size/2, x + size/2, dialOutlineRadius, dialOutlineRadius, 0.0f, dialStart, dialEnd, true); - g.setColour(juce::Colour(200, 200, 200)); // + g.setColour(Colors::mainColors[0]); // - juce::PathStrokeType strokeType(2.0f, juce::PathStrokeType::curved, juce::PathStrokeType::rounded); + juce::PathStrokeType strokeType(1.0f, juce::PathStrokeType::curved, juce::PathStrokeType::rounded); g.strokePath(dialOutlinePath, juce::PathStrokeType(strokeType)); //============================================================================== @@ -65,9 +57,11 @@ class DialLookAndFeel : public juce::LookAndFeel_V4 dialBodyPath.addCentredArc(x + size/2, y + size/2, dialBodyRadius, dialBodyRadius, 0.0f, 0.0f, 6.28f, true); - g.setColour(juce::Colour(200, 200, 200)); // + g.setColour(Colors::mainColors[0]); // + g.strokePath(dialBodyPath, juce::PathStrokeType(strokeType)); + g.setColour(Colors::mainColors[0].withAlpha((float)0.35f)); // g.fillPath(dialBodyPath); - + //============================================================================== // dial dot @@ -76,15 +70,12 @@ class DialLookAndFeel : public juce::LookAndFeel_V4 dialDotPath.addCentredArc(outlineCoords.x, outlineCoords.y, 1.5, 1.5, 0.0f, 0.0f, pi * 2, true); - - g.setColour(juce::Colour(20, 20, 20)); // + g.setColour(juce::Colour(200, 200, 200)); g.fillPath(dialDotPath); } - -private: - }; + class EditableTextBoxSlider : public juce::Component , juce::AudioProcessorParameter::Listener, juce::AsyncUpdater, juce::Label::Listener { public: @@ -112,6 +103,7 @@ class EditableTextBoxSlider : public juce::Component , juce::AudioProcessorParam const auto params = audioProcessor.getParameters(); for (auto param : params){ param->addListener(this); + } } @@ -130,23 +122,7 @@ class EditableTextBoxSlider : public juce::Component , juce::AudioProcessorParam textBox.setBounds(bounds); } - - /* void mouseDown(const juce::MouseEvent& m) override - { - auto mousePoint = m.getPosition().toFloat(); - dragStartPoint.y = mousePoint.y; - } - - void mouseDrag(const juce::MouseEvent& m) override - { - auto mousePoint = m.getPosition().toFloat(); - float deltaY = std::abs(mousePoint.y - dragStartPoint.y); - - float value = juce::jlimit(0.0f, 1.0f, deltaY/100.0f); // clamp this - textValueToParamValue(value); - } - */ - + void mouseDown(const juce::MouseEvent& m) override { auto mousePoint = m.getPosition().toFloat(); @@ -161,13 +137,11 @@ class EditableTextBoxSlider : public juce::Component , juce::AudioProcessorParam auto mousePoint = m.getPosition().toFloat(); float deltaY = mousePoint.y - dragStartPoint.y; // Remove std::abs to allow bidirectional dragging - // Modify the initial normalized value based on drag distance - float sensitivity = 0.01f; // Adjust this value to change drag sensitivity + float sensitivity = 0.01f; float newValue = juce::jlimit(0.0f, 1.0f, initialParamValue + (-deltaY * sensitivity)); textValueToParamValue(newValue); } - void mouseUp(const juce::MouseEvent& m) override { auto mousePoint = m.getPosition().toFloat(); diff --git a/Source/Graphics.h b/Source/Graphics.h index ca02cff..39b2ad6 100644 --- a/Source/Graphics.h +++ b/Source/Graphics.h @@ -1,13 +1,37 @@ #pragma once #include #include +#include "PluginProcessor.h" #include "LookAndFeel.h" //Takuma your waveforms look like a butt + +class LevelMeterGraphics : public juce::Component +{ + void paint(juce::Graphics &g) override + { + + } + + void resized() override {} + + void setLevel(float level) + { + this->level = level; + } + +private: + float level = 0.0f; +}; + class OperatorDisplayGraphics : public juce::Component { public: + void setIndex(int index) + { + this->index = index; + } void paint(juce::Graphics &g) override { @@ -16,7 +40,6 @@ class OperatorDisplayGraphics : public juce::Component juce::Path bgFill; bgFill.addRoundedRectangle(bounds, 5.0f); g.setColour(juce::Colour(12, 10, 11)); - // g.fillPath(bgFill); float x = bounds.getX(); float y = bounds.getY(); @@ -39,7 +62,7 @@ class OperatorDisplayGraphics : public juce::Component juce::Path fgWaveform = waveformPath(g, x + width * 0.05f, y + height * 0.125f, width * 0.9f, height * 0.75f, ratio, fgAmpScale); - g.setColour(juce::Colour(90, 224, 184)); + g.setColour(Colors::mainColors[index]); g.strokePath(fgWaveform, strokeType); } @@ -73,6 +96,7 @@ class OperatorDisplayGraphics : public juce::Component } private: + int index; float ratio, fixed, modIndex; }; @@ -81,8 +105,15 @@ class OperatorDisplayGraphics : public juce::Component class EnvelopeDisplayGraphics : public juce::Component { public: - EnvelopeDisplayGraphics() {} + EnvelopeDisplayGraphics(FledgeAudioProcessor &p, int index) : audioProcessor(p) + { + this->index = index; + } + void setIndex(int index) + { + } + void paint(juce::Graphics &g) override { auto bounds = getLocalBounds().toFloat(); @@ -95,26 +126,32 @@ class EnvelopeDisplayGraphics : public juce::Component float heightMargin = bounds.getHeight() * 0.05f; calculateSegment(); - drawSegment(g, x + widthMargin, y + heightMargin, width, height); + // unadjusted background + drawSegment(g, x + widthMargin, y + heightMargin, width, height, false); + + // adjusted foreground + drawSegment(g, x + widthMargin, y + heightMargin, width, height, true); - for (int i = 0; i < 8; i++) + for (int i = 0; i < 5; i++) { - points[i].drawHandles(g); + pointsGlobalAdjusted[i].drawHandles(g); } } void resized() override { calculateSegment(); - - points[0].yAdjustOnly = true; - points[1].yAdjustOnly = true; - points[2].yAdjustOnly = false; - points[3].yAdjustOnly = true; - points[4].yAdjustOnly = false; - points[5].yAdjustOnly = false; - points[6].yAdjustOnly = true; - points[7].yAdjustOnly = false; + pointsNoAdjusted[0].yAdjustOnly = false; + pointsNoAdjusted[1].yAdjustOnly = false; + pointsNoAdjusted[2].yAdjustOnly = false; + pointsNoAdjusted[3].yAdjustOnly = true; + pointsNoAdjusted[4].yAdjustOnly = false; + + pointsGlobalAdjusted[0].yAdjustOnly = false; + pointsGlobalAdjusted[1].yAdjustOnly = false; + pointsGlobalAdjusted[2].yAdjustOnly = false; + pointsGlobalAdjusted[3].yAdjustOnly = true; + pointsGlobalAdjusted[4].yAdjustOnly = false; } @@ -139,83 +176,130 @@ class EnvelopeDisplayGraphics : public juce::Component float widthMargin = bounds.getWidth() * 0.05f; float heightMargin = bounds.getHeight() * 0.05f; - points[0].coords = { x + widthMargin, y + height + heightMargin }; // Bottom (0) - points[1].coords = { x + widthMargin + width * attackPct, y + heightMargin }; // Top (1.0) - points[2].coords = { x + widthMargin + width * (attackPct + decayPct), y + heightMargin + height * (1.0f - sustain) }; // Sustain level - points[3].coords = { x + widthMargin + width * (attackPct + decayPct + sustainPct), y + heightMargin + height * (1.0f - sustain) }; // Same sustain level - points[4].coords = { x + widthMargin + width, y + height + heightMargin }; // Bottom (0) + pointsNoAdjusted[0].coords = { x + widthMargin, y + height + heightMargin }; // Bottom (0) + pointsNoAdjusted[1].coords = { x + widthMargin + width * attackPct, y + heightMargin }; // Top (1.0) + pointsNoAdjusted[2].coords = { x + widthMargin + width * (attackPct + decayPct), y + heightMargin + height * (1.0f - sustain) }; // Sustain level + pointsNoAdjusted[3].coords = { x + widthMargin + width * (attackPct + decayPct + sustainPct), y + heightMargin + height * (1.0f - sustain) }; // Same sustain level + pointsNoAdjusted[4].coords = { x + widthMargin + width, y + height + heightMargin }; // Bottom (0) + repaint(); + + + pointsGlobalAdjusted[0].coords = { x + widthMargin, y + height + heightMargin }; // Bottom (0) + pointsGlobalAdjusted[1].coords = { x + widthMargin + width * attackAdjPct, y + heightMargin }; // Top (1.0) + pointsGlobalAdjusted[2].coords = { x + widthMargin + width * (attackAdjPct + decayAdjPct), y + heightMargin + height * (1.0f - sustainLevelAdjusted) }; // Sustain level + pointsGlobalAdjusted[3].coords = { x + widthMargin + width * (attackAdjPct + decayAdjPct + sustainPct), y + heightMargin + height * (1.0f - sustainLevelAdjusted) }; // Same sustain level + pointsGlobalAdjusted[4].coords = { x + widthMargin + width, y + height + heightMargin }; // Bottom (0) repaint(); } -void setEnvelope(float attack, float decay, float sustain, float release) -{ - this->attack = attack; - this->decay = decay; - this->sustain = sustain / 100.0f; - this->release = release; + void setEnvelope(float attack, float decay, float sustain, float release, float attackAdj, float decayAdj, float sustainAdj, float releaseAdj) + { + this->attack = attack; + this->decay = decay; + this->sustain = sustain / 100.0f; + this->release = release; + + float attackAdjusted = attack * std::pow(2.0f, attackAdj / 100.0f); + float decayAdjusted = decay * std::pow(2.0f, decayAdj / 100.0f); + float releaseAdjusted = release * std::pow(2.0f, releaseAdj / 100.0f); + sustainLevelAdjusted = (sustain / 100.0) * std::pow(2.0f, sustainAdj / 100.0f); + sustainLevelAdjusted = juce::jlimit(0.0f, 1.0f, sustainLevelAdjusted); + // Sustain always takes 25% of width + sustainPct = 0.25f; + + // Calculate A+D+R proportions for remaining 75% + float adrSum = attack + decay + release; + float adrAdjSum = attackAdjusted * decayAdjusted * releaseAdjusted; - // Sustain always takes 25% of width - sustainPct = 0.25f; - - // Calculate A+D+R proportions for remaining 75% - float adrSum = attack + decay + release; - - if (adrSum > 0.0f) { - attackPct = (attack / adrSum) * 0.75f; - decayPct = (decay / adrSum) * 0.75f; - releasePct = (release / adrSum) * 0.75f; - } else { - attackPct = 0.25f; - decayPct = 0.25f; - releasePct = 0.25f; + if (adrSum > 0.0f) { + attackPct = (attack / adrSum) * 0.75f; + decayPct = (decay / adrSum) * 0.75f; + releasePct = (release / adrSum) * 0.75f; + + } else { + attackPct = 0.25f; + decayPct = 0.25f; + releasePct = 0.25f; + } + + if (adrAdjSum > 0.0f) + { + attackAdjPct = (attackAdjusted / adrAdjSum) * 0.75f; + decayAdjPct = (decayAdjusted / adrAdjSum) * 0.75f; + releaseAdjPct = (releaseAdjusted / adrAdjSum) * 0.75f; + } else { + attackAdjPct = 0.25f; + decayAdjPct = 0.25f; + releaseAdjPct = 0.25f; + } + + calculateSegment(); } - calculateSegment(); -} - - void drawSegment(juce::Graphics &g, float x, float y, float width, float height) + void drawSegment(juce::Graphics &g, float x, float y, float width, float height, bool drawAdjusted) { + auto points = drawAdjusted ? pointsGlobalAdjusted : pointsNoAdjusted; + auto envelopeColor = drawAdjusted ? Colors::mainColors[index] : juce::Colour(130, 130, 130); + juce::Path envelopePath; envelopePath.startNewSubPath(points[0].coords); envelopePath.lineTo(points[1].coords); - envelopePath.lineTo(points[2].coords); + envelopePath.cubicTo(points[1].coords.x, points[2].coords.y, + points[1].coords.x + width * decayPct * 0.5f, points[2].coords.y, + points[2].coords.x, points[2].coords.y); envelopePath.lineTo(points[3].coords); - envelopePath.lineTo(points[4].coords); - // envelopePath.lineTo(points[5].coords); - // envelopePath.lineTo(points[6].coords); - // envelopePath.lineTo(points[7].coords); - - - g.setColour(juce::Colour(90, 224, 184)); + envelopePath.cubicTo(points[3].coords.x, points[4].coords.y, + points[3].coords.x + width * releasePct * 0.5f, points[4].coords.y, + points[4].coords.x, points[4].coords.y); + + if (drawAdjusted){ + g.setColour(Colors::mainColors[index].withAlpha((float)0.15f)); + g.fillPath(envelopePath); + } + + g.setColour(envelopeColor); juce::PathStrokeType strokeType(1.5f, juce::PathStrokeType::curved, juce::PathStrokeType::rounded); g.strokePath(envelopePath, strokeType); + } void mouseDown(const juce::MouseEvent &m) override { auto mouse = m.getPosition().toFloat(); - for (int i = 0; i < 8; i++) + for (int i = 1; i < 5; i++) // ignore init segment { - if (points[i].isOver(mouse)) + if (pointsGlobalAdjusted[i].isOver(mouse)) { dragIndex = i; + dragStartPoint = mouse; + + auto segmentParameterID = (getSegmentParamID(*dragIndex)); + initialParamValue = audioProcessor.apvts.getParameter(segmentParameterID)->getValue(); } } } void mouseDrag(const juce::MouseEvent &m) override { - auto mouse = m.getPosition().toFloat(); + auto mousePoint = m.getPosition().toFloat(); + float sensitivity = 0.01f; + if (dragIndex.has_value()) { - if (!points[*dragIndex].yAdjustOnly) + if (pointsGlobalAdjusted[*dragIndex].yAdjustOnly) { - points[*dragIndex].coords = { mouse.x, mouse.y }; + float deltaY = mousePoint.y - dragStartPoint.y; + float newValue = juce::jlimit(0.0f, 1.0f, initialParamValue + (-deltaY * sensitivity)); + + setEnvelopeParam(*dragIndex, newValue); } else { - points[*dragIndex].coords.y = mouse.y; - } + float deltaX = mousePoint.x - dragStartPoint.x; + float newValue = juce::jlimit(0.0f, 1.0f, initialParamValue + (-deltaX * sensitivity)); + + setEnvelopeParam(*dragIndex, newValue); + } repaint(); } } @@ -225,10 +309,45 @@ void setEnvelope(float attack, float decay, float sustain, float release) dragIndex.reset(); } + juce::String getSegmentParamID(int segmentDragged) + { + juce::String segmentParameterID; + switch(segmentDragged) + { + case 1: + segmentParameterID = "attack" + juce::String(index); + break; + case 2: + segmentParameterID = "decay" + juce::String(index); + break; + case 3: + segmentParameterID = "sustain" + juce::String(index); + break; + case 4: + segmentParameterID = "release" + juce::String(index); + break; + } + return segmentParameterID; + } + + void setEnvelopeParam(int segmentDragged, float adjustAmount) + { + auto segmentParameterID = getSegmentParamID(segmentDragged); + auto paramRange = audioProcessor.apvts.getParameterRange(segmentParameterID); + + audioProcessor.apvts.getParameter(segmentParameterID)->setValueNotifyingHost(adjustAmount); + } + + private: + int index; float attackPct, decayPct, releasePct, sustainPct; float attack, decay, sustain, release; - float attackSegment, decaySegment, sustainSegment, releaseSegment; + float attackAdjPct, decayAdjPct, releaseAdjPct; + float sustainLevelAdjusted; + + juce::Point dragStartPoint; + float initialParamValue; struct Handle { @@ -251,9 +370,11 @@ void setEnvelope(float attack, float decay, float sustain, float release) } }; - std::array points; - // initial, attack, peak, decay, sustain start, sustain end, release, final + std::array pointsNoAdjusted; + std::array pointsGlobalAdjusted; + std::optional dragIndex; + FledgeAudioProcessor& audioProcessor; }; @@ -269,13 +390,6 @@ class WaveformDisplayGraphics : public juce::Component { auto bounds = getLocalBounds().toFloat(); - juce::Path boundsPath; - boundsPath.addRoundedRectangle(bounds, 10, 10); - g.setColour(juce::Colour(40, 42, 41)); - g.fillPath(boundsPath); - g.setColour(juce::Colour(30, 32, 31)); - g.strokePath(boundsPath, juce::PathStrokeType(2.0f)); - float x = bounds.getX(); float y = bounds.getY(); float height = bounds.getHeight(); @@ -297,7 +411,7 @@ class WaveformDisplayGraphics : public juce::Component float amp2 = op[2].generateAmplitude(k); float amp1 = op[1].generateAmplitude(k); float amp0 = op[0].generateAmplitude(k); - + float heightScaled = y + heightMargin + heightIncrement * j; float sin0Phase = fmodf(((0/40.7f) * op[0].ratio * 2.0f), 6.28318f); @@ -305,9 +419,8 @@ class WaveformDisplayGraphics : public juce::Component graphicLines.startNewSubPath(x + widthMargin, (heightScaled + height/envelopeSegments) + sin * height * 0.005f); - for (int i = 0; i < domainResolution; i++) + for (int i = 1; i <= domainResolution; i++) { - // float sin2 = amp2 * op[1].modIndex * fastSin.sin((i/40.7f) * op[2].ratio); float sin2Phase = fmodf(((i/40.7f) * op[1].ratio * 2.0f) + 0.0f, 6.28318f); float sin2 = amp2 * op[1].modIndex * fastSin.sin(sin2Phase); @@ -320,16 +433,16 @@ class WaveformDisplayGraphics : public juce::Component graphicLines.lineTo(x + widthMargin + widthIncrement * i, (heightScaled + height/envelopeSegments) + sin * height * 0.005f); + } } graphicLines = graphicLines.createPathWithRoundedCorners(10.0f); - g.setColour(juce::Colour(90, 224, 184)); + g.setColour(Colors::mainColors[0]); juce::PathStrokeType strokeType(1.0f, juce::PathStrokeType::curved, juce::PathStrokeType::rounded); g.strokePath(graphicLines, strokeType); } - void resized() override {}; void setEnvelope(int index, float attack, float decay, float sustain, float release) @@ -338,6 +451,7 @@ class WaveformDisplayGraphics : public juce::Component op[index].decay = decay; op[index].sustain = sustain/10.0f; op[index].release = release; + calculateEnvelopeSegments(); repaint(); } @@ -397,6 +511,7 @@ class WaveformDisplayGraphics : public juce::Component else if (segmentIndex > attackSegment + decaySegment + releaseSegment) { amplitude = 0.0f; + } else { diff --git a/Source/LevelMeter.cpp b/Source/LevelMeter.cpp new file mode 100644 index 0000000..d08ae4e --- /dev/null +++ b/Source/LevelMeter.cpp @@ -0,0 +1,86 @@ +#include +#include "LevelMeter.h" +#include "LookAndFeel.h" + +LevelMeter::LevelMeter(Measurement& measurementL_, Measurement& measurementR_) + : measurementL(measurementL_), measurementR(measurementR_), + dbLevelL(clampdB), dbLevelR(clampdB) +{ + setOpaque(true); + startTimerHz(refreshRate); + decay = 1.0f - std::exp(-1.0f / (float(refreshRate) * 0.2f)); +} + +LevelMeter::~LevelMeter() +{ +} + +void LevelMeter::paint (juce::Graphics& g) +{ + const auto bounds = getLocalBounds(); + + // Fill background first + g.fillAll(Colors::LevelMeter::background); + + // Draw levels horizontally - top and bottom channels + drawLevel(g, dbLevelL, 0, bounds.getHeight() / 2 - 1); // Top channel + drawLevel(g, dbLevelR, bounds.getHeight() / 2 + 1, bounds.getHeight() / 2 - 1); // Bottom channel +} + +void LevelMeter::resized() +{ + maxPos = 4.0f; // Left margin (start position) + minPos = float(getWidth()) - 4.0f; // Right margin (end position) +} + +void LevelMeter::timerCallback() +{ + updateLevel(measurementL.readAndReset(), levelL, dbLevelL); + updateLevel(measurementR.readAndReset(), levelR, dbLevelR); + + repaint(); +} + +void LevelMeter::drawLevel(juce::Graphics& g, float level, int y, int height) +{ + // Fix the position calculation - higher levels should go further right + int levelPos = positionForLevel(level); + int zeroPos = positionForLevel(0.0f); // Position of 0dB mark + + if (level > 0.0f) { + // Level is above 0dB - show clipping + + // Draw normal level from start to 0dB in green + g.setColour(Colors::LevelMeter::levelOK); + g.fillRect(int(maxPos), y, zeroPos - int(maxPos), height); + + // Draw clipping level from 0dB to current level in red + g.setColour(Colors::LevelMeter::tooLoud); + g.fillRect(zeroPos, y, levelPos - zeroPos, height); + + } else if (levelPos > maxPos) { + // Level is below 0dB - normal operation + g.setColour(Colors::LevelMeter::levelOK); + g.fillRect(int(maxPos), y, levelPos - int(maxPos), height); + } + + // Optional: Draw 0dB marker line + g.setColour(juce::Colours::white.withAlpha(0.5f)); + g.drawVerticalLine(zeroPos, float(y), float(y + height)); +} + +void LevelMeter::updateLevel(float newLevel, float& smoothedLevel, float& leveldB) const +{ + if (newLevel > smoothedLevel) { + smoothedLevel = newLevel; // instantaneous attack + } else { + smoothedLevel += (newLevel - smoothedLevel) * decay; + } + + // Convert to dB, but don't clamp too aggressively for clipping detection + if (smoothedLevel > 0.000001f) { // Very small threshold to catch more levels + leveldB = juce::Decibels::gainToDecibels(smoothedLevel); + } else { + leveldB = clampdB; + } +} \ No newline at end of file diff --git a/Source/LevelMeter.h b/Source/LevelMeter.h new file mode 100644 index 0000000..0591688 --- /dev/null +++ b/Source/LevelMeter.h @@ -0,0 +1,59 @@ +/* + ============================================================================== + + LevelMeter.h + Created: 6 Jul 2025 10:41:44am + Author: Ryan Page + + ============================================================================== +*/ + +#pragma once + +#include +#include "Measurement.h" + +class LevelMeter : public juce::Component, private juce::Timer +{ +public: + LevelMeter(Measurement& measurementL, Measurement& measurementR); + ~LevelMeter() override; + + void paint (juce::Graphics&) override; + void resized() override; + +private: + void timerCallback() override; + + int positionForLevel(float dbLevel) const noexcept + { + // Fixed: Map higher dB levels to higher x positions (left to right) + return int(std::round(juce::jmap(dbLevel, mindB, maxdB, maxPos, minPos))); + } + + void drawLevel(juce::Graphics& g, float level, int y, int height); + void updateLevel(float newLevel, float& smoothedLevel, float& leveldB) const; + + Measurement& measurementL; + Measurement& measurementR; + + static constexpr float maxdB = 6.0f; + static constexpr float mindB = -60.0f; + + float maxPos = 0.0f; + float minPos = 0.0f; + + static constexpr float clampdB = -120.0f; + static constexpr float clampLevel = 0.000001f; // -120 dB + + float dbLevelL; + float dbLevelR; + + static constexpr int refreshRate = 60; + + float decay = 0.0f; + float levelL = clampLevel; + float levelR = clampLevel; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LevelMeter) +}; \ No newline at end of file diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index d92a981..254690c 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -1,12 +1,40 @@ -/* - ============================================================================== - - LookAndFeel.h - Created: 30 May 2025 11:28:06am - Author: Takuma Matsui - - ============================================================================== -*/ -#include -#pragma once - +/* + ============================================================================== + + LookAndFeel.h + Created: 30 May 2025 11:28:06am + Author: Takuma Matsui + + ============================================================================== +*/ +#include +#pragma once + +namespace Colors +{ + inline juce::Colour textColor = juce::Colour(130, 130, 130); + + inline std::array mainColors { + juce::Colour(47, 222, 227), + juce::Colour(64, 229, 210), + juce::Colour(57, 229, 192), + juce::Colour(69, 227, 179), + juce::Colour(241, 241, 241), + }; + + inline std::array mainHoverColors { + juce::Colour(67, 242, 247), + juce::Colour(84, 249, 230), + juce::Colour(77, 249, 212), + juce::Colour(89, 247, 199), + juce::Colour(251, 251, 251), + }; + + // Add LevelMeter colors + namespace LevelMeter + { + inline juce::Colour background = juce::Colour(30, 30, 30); // Dark background + inline juce::Colour levelOK = juce::Colour(69, 227, 179); // Green for normal levels (using one of your main colors) + inline juce::Colour tooLoud = juce::Colour(255, 80, 80); // Red for levels above 0dB + } +}; \ No newline at end of file diff --git a/Source/Measurement.h b/Source/Measurement.h new file mode 100644 index 0000000..2ffb679 --- /dev/null +++ b/Source/Measurement.h @@ -0,0 +1,26 @@ + + +#pragma once + +#include + +struct Measurement +{ + void reset() noexcept + { + value.store(0.0f); + } + + void updateIfGreater(float newValue) noexcept + { + auto oldValue = value.load(); + while (newValue > oldValue && !value.compare_exchange_weak(oldValue, newValue)); + } + + float readAndReset() noexcept + { + return value.exchange(0.0f); + } + + std::atomic value; +}; diff --git a/Source/Operator.cpp b/Source/Operator.cpp index fc72f84..b230edc 100644 --- a/Source/Operator.cpp +++ b/Source/Operator.cpp @@ -34,7 +34,6 @@ void FMOperator::stopNote() } - void FMOperator::setEnvelope(float attackInMs, float decayInMs, float sustainInFloat, float releaseInMs, bool isLooping) { envParameters.attack = attackInMs; @@ -60,25 +59,18 @@ void FMOperator::setOperator(float ratio, float fixed, bool isFixed, float modIn float FMOperator::processOperator(float phase1, float phase2, float phase3, float phase4) { - - currentFrequency += frequencySmoothingCoeff * (targetFrequency - currentFrequency); - frequency = currentFrequency * ratioSmoothed.getNextValue(); - if (isFixed) frequency = fixedSmoothed.getNextValue(); - + frequency = noteFrequency * ratioSmoothed.getNextValue(); + if (isFixed) frequency = fixedSmoothed.getNextValue(); operatorAngle = frequency/sampleRate; - float modulatorPhase = phase1 + phase2 + phase3 + phase4; float twopi = juce::MathConstants::twoPi; float envelope = ampEnvelope.getNextSample(); float waveform = std::sin(operatorPhase * twopi + (modulatorPhase * modIndexSmoothed.getNextValue())) * envelope; - - - - + // accumulate and wrap operatorPhase += operatorAngle; if (operatorPhase >= 1.0) operatorPhase -= 1.0; - + return waveform; } diff --git a/Source/Operator.h b/Source/Operator.h index f3d8649..36ca1be 100644 --- a/Source/Operator.h +++ b/Source/Operator.h @@ -15,6 +15,9 @@ class FMOperator void setOperator(float ratio, float fixed, bool isFixed, float modIndex); float processOperator(float phase1, float phase2, float phase3, float phase4); + + juce::ADSR ampEnvelope; + juce::ADSR::Parameters envParameters; private: double sampleRate; double operatorPhase = 0.0, operatorAngle = 0.0; @@ -30,6 +33,4 @@ class FMOperator juce::SmoothedValue ratioSmoothed, fixedSmoothed, modIndexSmoothed; - juce::ADSR ampEnvelope; - juce::ADSR::Parameters envParameters; }; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 409ab3f..d0016fc 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -23,22 +23,46 @@ FledgeAudioProcessorEditor::FledgeAudioProcessorEditor (FledgeAudioProcessor& p) addAndMakeVisible(showWaveformButton); showWaveformButton.addListener(this); + showWaveformButton.setLookAndFeel(&showWaveLAF); + addAndMakeVisible(showAlgorithmButton); showAlgorithmButton.addListener(this); + showAlgorithmButton.setLookAndFeel(&showAlgoLAF); + + addAndMakeVisible(showMacrosButton); + showMacrosButton.addListener(this); + showMacrosButton.setLookAndFeel(&showMacroLAF); + addAndMakeVisible(waveformDisplay); + waveformDisplay.setVisible(true); + + addAndMakeVisible(algorithmGraphics); + + + algorithmSelector = std::make_unique(audioProcessor); + addAndMakeVisible(*algorithmSelector); + + macroInterface = std::make_unique(audioProcessor); + addAndMakeVisible(*macroInterface); + + algorithmGraphics.setVisible(false); + algorithmSelector->setVisible(false); + macroInterface->setVisible(false); + + outputLevelMeter = std::make_unique + ( + audioProcessor.getOutputLevelL(), + audioProcessor.getOutputLevelR() + ); + addAndMakeVisible(*outputLevelMeter); + const auto params = audioProcessor.getParameters(); for (auto param : params){ param->addListener(this); } - - addAndMakeVisible(algorithmGraphics); - addAndMakeVisible(algorithmSelector); - - addAndMakeVisible(practiceSlider); - - setSize (800, 800); + setSize (765, 700); } FledgeAudioProcessorEditor::~FledgeAudioProcessorEditor() @@ -49,31 +73,41 @@ FledgeAudioProcessorEditor::~FledgeAudioProcessorEditor() } showWaveformButton.removeListener(this); showAlgorithmButton.removeListener(this); + showMacrosButton.removeListener(this); - } //============================================================================== void FledgeAudioProcessorEditor::paint (juce::Graphics& g) { // (Our component is opaque, so we must completely fill the background with a solid colour) - g.fillAll(juce::Colour(60, 62, 61)); - + g.setColour(juce::Colour(35, 37, 36)); + g.fillAll(); + + juce::Path leftBounds; + leftBounds.addRoundedRectangle(5, 60, 280, 490, 5.0f); + g.setColour(juce::Colour(40, 42, 41)); + g.fillPath(leftBounds); } void FledgeAudioProcessorEditor::resized() { for (int oper = 0; oper < 4; oper++) { - opInterface[oper]->setBounds(300, oper * 125 + 70, 500, 125); + opInterface[oper]->setBounds(285, oper * 125 + 55, 480, 125); } - presetInterface->setBounds(20, 10, 800, 50); - waveformDisplay.setBounds(20, 70, 280, 500); - algorithmGraphics.setBounds(20, 70, 280, 330); - algorithmSelector.setBounds(20, 390, 280, 150); + presetInterface->setBounds(5, 5, 760, 50); + waveformDisplay.setBounds(5, 60, 280, 490); + algorithmGraphics.setBounds(5, 30, 280, 380); + algorithmSelector->setBounds(5, 400, 280, 150); + macroInterface->setBounds(5, 60, 280, 490); + + showWaveformButton.setBounds(5, 555, 94, 40); + showAlgorithmButton.setBounds(100, 555, 94, 40); + showMacrosButton.setBounds(200, 555, 94, 40); - showWaveformButton.setBounds(20, 570, 140, 40); - showAlgorithmButton.setBounds(160, 570, 140, 40); + outputLevelMeter->setBounds(285, 605, 480, 25); + } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index b1ecde6..21fa210 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -12,7 +12,9 @@ #include "PluginProcessor.h" #include "UserInterface.h" #include "AlgorithmGraphics.h" +#include "ButtonLookAndFeel.h" #include "LookAndFeel.h" +#include "LevelMeter.h" //============================================================================== /** @@ -58,30 +60,39 @@ class FledgeAudioProcessorEditor : public juce::AudioProcessorEditor, juce::Aud { waveformDisplay.setVisible(true); algorithmGraphics.setVisible(false); - algorithmSelector.setVisible(false); + algorithmSelector->setVisible(false); + macroInterface->setVisible(false); } else if (buttonClicked == &showAlgorithmButton) { waveformDisplay.setVisible(false); algorithmGraphics.setVisible(true); - algorithmSelector.setVisible(true); + algorithmSelector->setVisible(true); + macroInterface->setVisible(false); + + } else if (buttonClicked == &showMacrosButton) { + waveformDisplay.setVisible(false); + algorithmGraphics.setVisible(false); + algorithmSelector->setVisible(false); + macroInterface->setVisible(true); } } - private: - // TextBoxSlider laf; - PracticeDialGraphics practiceLookAndFeel; - juce::Slider practiceSlider; - + ButtonLookAndFeel showWaveLAF { 3 }, showAlgoLAF { 4 }, showMacroLAF { 5 }; + + std::array, 4> opInterface; std::unique_ptr presetInterface; - - juce::TextButton showWaveformButton, showAlgorithmButton; + std::unique_ptr macroInterface; + + juce::TextButton showWaveformButton, showAlgorithmButton, showMacrosButton; WaveformDisplayGraphics waveformDisplay; AlgorithmGraphics algorithmGraphics; - AlgorithmSelectInterface algorithmSelector; + std::unique_ptr algorithmSelector; + FledgeAudioProcessor& audioProcessor; - + + std::unique_ptr outputLevelMeter; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FledgeAudioProcessorEditor) }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index ee4c6e1..38fec05 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -156,6 +156,8 @@ void FledgeAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce: float globalSustain = apvts.getRawParameterValue("globalSustain")->load(); float globalRelease = apvts.getRawParameterValue("globalRelease")->load(); + int outputRouting = apvts.getRawParameterValue("outputRouting")->load(); + for (int oper = 0; oper < 4; oper++){ juce::String attackID = "attack" + juce::String(oper); juce::String decayID = "decay" + juce::String(oper); @@ -184,13 +186,44 @@ void FledgeAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce: voice->setEnvelope(oper, attack, decay, sustain/100.0f, release, globalAttack, globalDecay, globalSustain, globalRelease); voice->setFMParameters(oper, ratio, fixed, false, modIndex); - voice->setOperatorGain(oper, routing); + voice->setOperatorGain(oper, routing, outputRouting); } } } - synth.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples()); + + // Measure output levels +const int numChannels = buffer.getNumChannels(); +const int numSamples = buffer.getNumSamples(); + +if (numChannels >= 1) { + auto* leftData = buffer.getReadPointer(0); + float maxL = 0.0f; + for (int i = 0; i < numSamples; ++i) { + maxL = juce::jmax(maxL, std::abs(leftData[i])); + } + outputLevelL.updateIfGreater(maxL); +} + +if (numChannels >= 2) { + auto* rightData = buffer.getReadPointer(1); + float maxR = 0.0f; + for (int i = 0; i < numSamples; ++i) { + maxR = juce::jmax(maxR, std::abs(rightData[i])); + } + outputLevelR.updateIfGreater(maxR); +} else if (numChannels >= 1) { + auto* leftData = buffer.getReadPointer(0); + float maxL = 0.0f; + for (int i = 0; i < numSamples; ++i) { + maxL = juce::jmax(maxL, std::abs(leftData[i])); + } + outputLevelR.updateIfGreater(maxL); +} + + + } //============================================================================== @@ -232,13 +265,15 @@ juce::AudioProcessorValueTreeState::ParameterLayout FledgeAudioProcessor::create layout.add(std::make_unique(juce::ParameterID { "port", 1 }, "Glide", juce::NormalisableRange(0.0f, 100.0f, 0.01f), 0.0f)); - layout.add(std::make_unique(juce::ParameterID { "globalAttack", 1 }, "Global Attack", juce::NormalisableRange(-100.0f, 100.0f, 0.01f), 0.01f)); + layout.add(std::make_unique(juce::ParameterID { "globalAttack", 1 }, "Global Attack", juce::NormalisableRange(50.0f, 200.0f, 0.01f), 100.0f)); - layout.add(std::make_unique(juce::ParameterID { "globalDecay", 1 }, "Global Decay", juce::NormalisableRange(-100.0f, 100.0f, 0.01f), 0.2f)); + layout.add(std::make_unique(juce::ParameterID { "globalDecay", 1 }, "Global Decay", juce::NormalisableRange(50.0f, 200.0f, 0.01f), 100.0f)); - layout.add(std::make_unique(juce::ParameterID { "globalSustain", 1 }, "Global Sustain", juce::NormalisableRange(-100.0f, 100.0f, 0.01f), 80.0f)); + layout.add(std::make_unique(juce::ParameterID { "globalSustain", 1 }, "Global Sustain", juce::NormalisableRange(50.0f, 200.0f, 0.01f), 100.0f)); - layout.add(std::make_unique(juce::ParameterID { "globalRelease", 1 }, "Global Release", juce::NormalisableRange(-100.0f, 100.0f, 0.1f), 1.0f)); + layout.add(std::make_unique(juce::ParameterID { "globalRelease", 1 }, "Global Release", juce::NormalisableRange(50.0f, 200.0f, 0.1f), 100.0f)); + + layout.add(std::make_unique(juce::ParameterID { "outputRouting", 1 }, "Output Routing", 0, 15, 1)); for (int oper = 0; oper < 4; oper++) { @@ -277,7 +312,9 @@ juce::AudioProcessorValueTreeState::ParameterLayout FledgeAudioProcessor::create juce::String opModeID = "opMode" + juce::String(oper); juce::String opModeName = "Mode " + juce::String(oper); - layout.add(std::make_unique(juce::ParameterID { opModeID, 1 }, opModeName, juce::NormalisableRange(20.0f, 20000.0f), 20.0f)); + layout.add(std::make_unique(juce::ParameterID { opModeID, 1}, + opModeName, + false)); juce::String modIndexID = "amplitude" + juce::String(oper); juce::String modIndexName = "Modulation Amount " + juce::String(oper); @@ -288,11 +325,10 @@ juce::AudioProcessorValueTreeState::ParameterLayout FledgeAudioProcessor::create juce::String operatorRoutingID = "operator" + juce::String(oper) + "Routing"; juce::String operatorRoutingName = "Operator " + juce::String(oper) + " Routing"; - layout.add(std::make_unique(juce::ParameterID { operatorRoutingID, 1 }, operatorRoutingName, 0, 15, 0)); + layout.add(std::make_unique(juce::ParameterID { operatorRoutingID, 1 }, operatorRoutingName, 0, 15, oper + 1 )); } - layout.add(std::make_unique(juce::ParameterID { "outputRouting", 1 }, "Output Routing", 0, 15, 0)); return layout; } diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 480aa13..ae7586e 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -10,6 +10,7 @@ #include #include "VoiceProcessor.h" +#include "Measurement.h" //============================================================================== /** @@ -17,6 +18,11 @@ class FledgeAudioProcessor : public juce::AudioProcessor, juce::AudioProcessorParameter::Listener { public: + + + Measurement& getOutputLevelL() { return outputLevelL; } + Measurement& getOutputLevelR() { return outputLevelR; } + //============================================================================== FledgeAudioProcessor(); ~FledgeAudioProcessor() override; @@ -113,9 +119,14 @@ class FledgeAudioProcessor : public juce::AudioProcessor, juce::AudioProcessorP private: - + float outputLevel; + std::atomic levelAtomic; juce::Synthesiser synth; + + Measurement outputLevelL; + Measurement outputLevelR; + //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FledgeAudioProcessor) }; diff --git a/Source/UserInterface.cpp b/Source/UserInterface.cpp index a8ee391..0149499 100644 --- a/Source/UserInterface.cpp +++ b/Source/UserInterface.cpp @@ -10,11 +10,99 @@ #include "UserInterface.h" +PresetInterface::PresetInterface(FledgeAudioProcessor& p, juce::AudioProcessorValueTreeState& apvts) : presetManager(apvts), audioProcessor(p) +{ + juce::FontOptions font { 12.0f, juce::Font::plain }; + + addAndMakeVisible(saveButton); + saveButton.addListener(this); + saveButton.setLookAndFeel(&saveLAF); + + addAndMakeVisible(nextButton); + nextButton.addListener(this); + nextButton.setLookAndFeel(&nextLAF); + + addAndMakeVisible(prevButton); + prevButton.addListener(this); + prevButton.setLookAndFeel(&prevLAF); + + addAndMakeVisible(presetComboBox); + presetComboBox.addListener(this); + presetComboBox.setLookAndFeel(&presetComboBoxLAF); + + // refresh presets + loadPresetList(); +} + +PresetInterface::~PresetInterface() +{ + saveButton.removeListener(this); + nextButton.removeListener(this); + prevButton.removeListener(this); + presetComboBox.removeListener(this); +} + +void PresetInterface::resized() +{ + auto bounds = getLocalBounds().toFloat(); + float x = bounds.getX(); + float y = bounds.getY(); + float height = bounds.getHeight(); + + rateLabel.setBounds(600, y + height * 0.25f, 164, height * 0.35f); + rateValueLabel.setBounds(605, y + height * 0.25f, 164, height * 0.35f); + + saveButton.setBounds(x, y, height, height); + prevButton.setBounds(x + height, y, height, height); + nextButton.setBounds(x + height * 9.0f, y, height, height); + presetComboBox.setBounds(x + height * 2.0f, bounds.getY(), height * 7.0f, height); +} + +void PresetInterface::comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) +{ + if (comboBoxThatHasChanged == &presetComboBox) + presetManager.loadPreset(presetComboBox.getItemText(presetComboBox.getSelectedItemIndex())); +} + +void PresetInterface::buttonClicked(juce::Button* buttonClicked) +{ + if (buttonClicked == &saveButton){ + fileChooser = std::make_unique( + "Enter Preset Name", + presetManager.defaultDirectory, + "*." + presetManager.extension); + + fileChooser->launchAsync(juce::FileBrowserComponent::saveMode, [&](const juce::FileChooser& chooser) + { + const auto resultFile = chooser.getResult(); + presetManager.savePreset(resultFile.getFileNameWithoutExtension()); + loadPresetList(); + }); + + } else if (buttonClicked == &nextButton){ + presetManager.loadNextPreset(); + loadPresetList(); + + } else if (buttonClicked == &prevButton){ + presetManager.loadPreviousPreset(); + loadPresetList(); + } +} + +void PresetInterface::loadPresetList() +{ + presetComboBox.clear(juce::dontSendNotification); + const auto allPresets = presetManager.getAllPreset(); + const auto currentPreset = presetManager.getCurrentPreset(); + presetComboBox.addItemList(allPresets, 1); + presetComboBox.setTitle(currentPreset); + presetComboBox.setSelectedItemIndex(allPresets.indexOf(currentPreset), juce::dontSendNotification); +} + OperatorInterface::OperatorInterface(FledgeAudioProcessor& p, int index) : audioProcessor(p) { this->index = index; - setLabel(ratioLabel, "Ratio", 12.0f); ratioSlider = std::make_unique(audioProcessor, "ratio" + juce::String(index), ""); addAndMakeVisible(*ratioSlider); @@ -50,8 +138,12 @@ OperatorInterface::OperatorInterface(FledgeAudioProcessor& p, int index) : audio addAndMakeVisible(*releaseSlider); releaseSlider->setFontSize(12.0f); - addAndMakeVisible(envGraphics); + envGraphics = std::make_unique(audioProcessor, index); + addAndMakeVisible(*envGraphics); + addAndMakeVisible(opGraphics); + opGraphics.setIndex(index); + startTimerHz(30); } @@ -64,8 +156,6 @@ void OperatorInterface::paint(juce::Graphics &g) boundsPath.addRoundedRectangle(bounds, 5, 5); g.setColour(juce::Colour(40, 42, 41)); g.fillPath(boundsPath); - g.setColour(juce::Colour(35, 37, 36)); - g.strokePath(boundsPath, juce::PathStrokeType(2.0f)); } @@ -89,7 +179,7 @@ void OperatorInterface::resized() amplitudeLabel.setBounds(x + width * 0.025f, height * 0.6f, width * 0.15f, height * 0.1f); amplitudeSlider->setBounds(x + width * 0.025f, height * 0.6f + labelHeight, width * 0.15f, height * 0.25f); - opGraphics.setBounds(x + 100, y + height * 0.125f, sliderSize * 2, height * 0.75f); + opGraphics.setBounds(x + width * 0.2f, y + height * 0.125f, width * 0.275f, height * 0.75f); attackLabel.setBounds(x + width * 0.8f, y + height * 0.1f, @@ -111,12 +201,18 @@ void OperatorInterface::resized() width * 0.035f, height * 0.2f); - attackSlider->setBounds(x + width * 0.835f, y + height * 0.1f, width * 0.165f, height * 0.2f); - decaySlider->setBounds(x + width * 0.835f, y + height * 0.3f, width * 0.165f, height * 0.2f); + attackSlider->setBounds(x + width * 0.835f, + y + height * 0.1f, + width * 0.165f, + height * 0.2f); + decaySlider->setBounds(x + width * 0.835f, + y + height * 0.3f, + width * 0.165f, + height * 0.2f); sustainSlider->setBounds(x + width * 0.835f, y + height * 0.5f, width * 0.165f, height * 0.2f); releaseSlider->setBounds(x + width * 0.835f, y + height * 0.7f, width * 0.165f, height * 0.2f); - envGraphics.setBounds(x + sliderSize * 3.5 , y + height * 0.125f, sliderSize * 2, height * 0.75f); + envGraphics->setBounds(x + width * 0.5f, y + height * 0.125f, width * 0.275f, height * 0.75f); } void OperatorInterface::setLabel(juce::Label &l, juce::String labelText, float size) @@ -144,96 +240,243 @@ void OperatorInterface::timerCallback() float decay = audioProcessor.apvts.getRawParameterValue("decay" + juce::String(index))->load(); float sustain = audioProcessor.apvts.getRawParameterValue("sustain" + juce::String(index))->load(); float release = audioProcessor.apvts.getRawParameterValue("release" + juce::String(index))->load(); - envGraphics.setEnvelope(attack, decay, sustain, release); + + float globalAttack = audioProcessor.apvts.getRawParameterValue("globalAttack")->load(); + float globalDecay = audioProcessor.apvts.getRawParameterValue("globalDecay")->load(); + float globalSustain = audioProcessor.apvts.getRawParameterValue("globalSustain")->load(); + float globalRelease = audioProcessor.apvts.getRawParameterValue("globalRelease")->load(); -} + envGraphics->setEnvelope(attack, decay, sustain, release, globalAttack, globalDecay, globalSustain, globalRelease); +} -PresetInterface::PresetInterface(FledgeAudioProcessor& p, juce::AudioProcessorValueTreeState& apvts) : presetManager(apvts), audioProcessor(p) +AlgorithmSelectInterface::AlgorithmSelectInterface(FledgeAudioProcessor& p) : audioProcessor(p) { - juce::FontOptions font { 12.0f, juce::Font::plain }; - - addAndMakeVisible(saveButton); - saveButton.addListener(this); - // saveButton.setLookAndFeel(&saveLAF); + for(int i = 0; i < 8; i++) + { + addAndMakeVisible(algorithm[i]); + algorithmGraphics[i].setIndex(i); + algorithm[i].setLookAndFeel(&algorithmGraphics[i]); + algorithm[i].addListener(this); - addAndMakeVisible(nextButton); - nextButton.addListener(this); -// nextButton.setLookAndFeel(&nextLAF); - - addAndMakeVisible(prevButton); - prevButton.addListener(this); - // prevButton.setLookAndFeel(&prevLAF); - - addAndMakeVisible(presetComboBox); - presetComboBox.addListener(this); - // presetComboBox.setLookAndFeel(&comboBoxLAF); - - // refresh presets - loadPresetList(); + } } - -PresetInterface::~PresetInterface() + +AlgorithmSelectInterface::~AlgorithmSelectInterface() { - saveButton.removeListener(this); - nextButton.removeListener(this); - prevButton.removeListener(this); - presetComboBox.removeListener(this); -} + for(int i = 0; i < 8; i++) + { + algorithm[i].setLookAndFeel(nullptr); + algorithm[i].removeListener(this); -void PresetInterface::resized() + } +} + +void AlgorithmSelectInterface::resized() { auto bounds = getLocalBounds().toFloat(); float x = bounds.getX(); float y = bounds.getY(); - float height = bounds.getHeight(); + + float width = bounds.getWidth() * 0.95f; + float height = bounds.getHeight() * 0.8f; + float widthMargin = bounds.getWidth() * 0.025f; + float heightMargin = bounds.getHeight() * 0.1f; + + float blockWidth = width * 0.25f; + float blockHeight = height * 0.5f; + + for(int i = 0; i < 8; i++) + { + algorithm[i].setBounds(x + widthMargin + blockWidth * (i % 4), + y + heightMargin + blockWidth * (i / 4), + blockHeight, + blockHeight); + } +} + - rateLabel.setBounds(600, y + height * 0.25f, 164, height * 0.35f); - rateValueLabel.setBounds(605, y + height * 0.25f, 164, height * 0.35f); +void AlgorithmSelectInterface::buttonClicked(juce::Button* button) +{ + if (button == &algorithm[0]){ + // op3 { 0.0, 0.0, 0.0, 0.0 }; + // op2 { 0.0, 0.0, 0.0, 1.0 }; + // op1 { 0.0, 0.0, 1.0, 0.0 }; + // op0 { 0.0, 1.0, 0.0, 0.0 }; + // output { 1.0f, 0.0f, 0.0f, 0.0f }; + setOperatorParam(3, 1); + setOperatorParam(2, 8); + setOperatorParam(1, 4); + setOperatorParam(0, 2); + setOutputParam(1); + } else if (button == &algorithm[1]) { + // op3 { 0.0, 0.0, 0.0, 0.0 }; + // op2 { 0.0, 0.0, 0.0, 1.0 }; + // op1 { 0.0, 0.0, 0.0, 0.0 }; + // op0 { 0.0, 1.0, 1.0, 0.0 }; + // output { 1.0f, 0.0f, 0.0f, 0.0f }; + setOperatorParam(3, 0); + setOperatorParam(2, 8); + setOperatorParam(1, 0); + setOperatorParam(0, 6); + setOutputParam(1); - saveButton.setBounds(x, y, height, height); - prevButton.setBounds(x + height, y, height, height); - nextButton.setBounds(x + height * 9.0f, y, height, height); - presetComboBox.setBounds(x + height * 2.0f, bounds.getY(), height * 7.0f, height); + + } else if (button == &algorithm[2]) { + // op3 { 0.0, 0.0, 0.0, 0.0 }; + // op2 { 0.0, 0.0, 0.0, 0.0 }; + // op1 { 0.0, 0.0, 0.0, 0.0 }; + // op0 { 0.0, 1.0, 1.0, 1.0 }; + // output { 1.0f, 0.0f, 0.0f, 0.0f }; + setOperatorParam(3, 0); + setOperatorParam(2, 0); + setOperatorParam(1, 0); + setOperatorParam(0, 14); + setOutputParam(1); + + } else if (button == &algorithm[3]) { + // op3 { 0.0, 0.0, 0.0, 0.0 }; + // op2 { 0.0, 0.0, 0.0, 1.0 }; + // op1 { 0.0, 0.0, 0.0, 1.0 }; + // op0 { 0.0, 1.0, 1.0, 0.0 }; + // output { 1.0f, 0.0f, 0.0f, 0.0f }; + setOperatorParam(3, 0); + setOperatorParam(2, 8); + setOperatorParam(1, 8); + setOperatorParam(0, 6); + setOutputParam(1); + + } else if (button == &algorithm[4]) { + // op3 { 0.0, 0.0, 0.0, 0.0 }; + // op2 { 0.0, 0.0, 0.0, 1.0 }; + // op1 { 0.0, 0.0, 0.0, 0.0 }; + // op0 { 0.0, 1.0, 0.0, 0.0 }; + // output { 1.0f, 1.0f, 0.0f, 0.0f }; + setOperatorParam(3, 0); + setOperatorParam(2, 8); + setOperatorParam(1, 0); + setOperatorParam(0, 2); + setOutputParam(1); // output + + } else if (button == &algorithm[5]) { + setOperatorParam(3, 0); + setOperatorParam(2, 8); + setOperatorParam(1, 4); + setOperatorParam(0, 4); + setOutputParam(3); // output + + } else if (button == &algorithm[6]) { + setOperatorParam(3, 0); + setOperatorParam(2, 8); + setOperatorParam(1, 0); + setOperatorParam(0, 0); + setOutputParam(7); // output + + } else if (button == &algorithm[7]) { + setOperatorParam(3, 0); + setOperatorParam(2, 0); + setOperatorParam(1, 0); + setOperatorParam(0, 0); + setOutputParam(15); // output + + } } -void PresetInterface::comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) +void AlgorithmSelectInterface::setOperatorParam(int index, int gainIndex) { - if (comboBoxThatHasChanged == &presetComboBox) - presetManager.loadPreset(presetComboBox.getItemText(presetComboBox.getSelectedItemIndex())); + auto paramRange = audioProcessor.apvts.getParameterRange("operator0Routing"); + float valueScaled = paramRange.convertTo0to1(gainIndex); + + juce::String parameterID = "operator" + juce::String(index) + "Routing"; + audioProcessor.apvts.getParameter(parameterID)->setValueNotifyingHost(valueScaled); + } -void PresetInterface::buttonClicked(juce::Button* buttonClicked) +void AlgorithmSelectInterface::setOutputParam(int gainIndex) { - if (buttonClicked == &saveButton){ - fileChooser = std::make_unique( - "Enter Preset Name", - presetManager.defaultDirectory, - "*." + presetManager.extension); - - fileChooser->launchAsync(juce::FileBrowserComponent::saveMode, [&](const juce::FileChooser& chooser) - { - const auto resultFile = chooser.getResult(); - presetManager.savePreset(resultFile.getFileNameWithoutExtension()); - loadPresetList(); - }); + auto paramRange = audioProcessor.apvts.getParameterRange("outputRouting"); + float valueScaled = paramRange.convertTo0to1(gainIndex); + + audioProcessor.apvts.getParameter("outputRouting")->setValueNotifyingHost(valueScaled); - } else if (buttonClicked == &nextButton){ - presetManager.loadNextPreset(); - loadPresetList(); - - } else if (buttonClicked == &prevButton){ - presetManager.loadPreviousPreset(); - loadPresetList(); - } } -void PresetInterface::loadPresetList() + +MacroControlsInterface::MacroControlsInterface(FledgeAudioProcessor& p) : audioProcessor(p) { - presetComboBox.clear(juce::dontSendNotification); - const auto allPresets = presetManager.getAllPreset(); - const auto currentPreset = presetManager.getCurrentPreset(); - presetComboBox.addItemList(allPresets, 1); - presetComboBox.setTitle(currentPreset); - presetComboBox.setSelectedItemIndex(allPresets.indexOf(currentPreset), juce::dontSendNotification); + setSliderAndLabel(globalFreqSlider, globalFreqLabel, dialLAF, "Global Freq", ""); + setSliderAndLabel(globalModIndexSlider, globalModIndexLabel, dialLAF, "Global Freq", ""); + + setSliderAndLabel(globalAttackSlider, globalAttackLabel, dialLAF, "Global Attack", "%"); + globalAttackAttachment = std::make_unique(audioProcessor.apvts, "globalAttack", globalAttackSlider); + + setSliderAndLabel(globalDecaySlider, globalDecayLabel, dialLAF, "Global Decay", "%"); + globalDecayAttachment = std::make_unique(audioProcessor.apvts, "globalDecay", globalDecaySlider); + + setSliderAndLabel(globalSustainSlider, globalSustainLabel, dialLAF, "Global Sustain", "%"); + globalSustainAttachment = std::make_unique(audioProcessor.apvts, "globalSustain", globalSustainSlider); + + setSliderAndLabel(globalReleaseSlider, globalReleaseLabel, dialLAF, "Global Release", "%"); + globalReleaseAttachment = std::make_unique(audioProcessor.apvts, "globalRelease", globalReleaseSlider); + +} + +void MacroControlsInterface::resized() +{ + auto bounds = getLocalBounds().toFloat(); + float x = bounds.getX(); + float y = bounds.getY(); + float width = bounds.getWidth(); + float height = bounds.getHeight(); + + globalFreqSlider.setBounds(x + width * 0.15f, + y + height * 0.05f, + height * 0.15f, + height * 0.2f); + + globalModIndexSlider.setBounds(x + width * 0.55f, + y + height * 0.05f, + height * 0.15f, + height * 0.2f); + + globalAttackSlider.setBounds(x + width * 0.15f, + y + height * 0.375f, + height * 0.15f, + height * 0.2f); + + globalDecaySlider.setBounds(x + width * 0.55f, + y + height * 0.375f, + height * 0.15f, + height * 0.2f); + + globalSustainSlider.setBounds(x + width * 0.15f, + y + height * 0.6f, + height * 0.15f, + height * 0.2f); + + globalReleaseSlider.setBounds(x + width * 0.55f, + y + height * 0.6f, + height * 0.15f, + height * 0.2f); + + globalAttackLabel.setBounds(x + width * 0.15f, + y + height * 0.25f, + height * 0.2f, + height * 0.1f); + + globalDecayLabel.setBounds(x + width * 0.55f, + y + height * 0.25f, + height * 0.2f, + height * 0.1f); + + globalSustainLabel.setBounds(x + width * 0.15f, + y + height * 0.45f, + height * 0.2f, + height * 0.1f); + + globalReleaseLabel.setBounds(x + width * 0.55f, + y + height * 0.45f, + height * 0.2f, + height * 0.1f); + } diff --git a/Source/UserInterface.h b/Source/UserInterface.h index 5ab1b88..a63ae55 100644 --- a/Source/UserInterface.h +++ b/Source/UserInterface.h @@ -10,38 +10,55 @@ #pragma once #include -#include "Graphics.h" #include "PluginProcessor.h" -#include "LookAndFeel.h" -#include "DialLookAndFeel.h" -#include "AlgorithmGraphics.h" #include "Presets.h" +#include "Graphics.h" +#include "AlgorithmGraphics.h" +#include "DialLookAndFeel.h" +#include "ButtonLookAndFeel.h" +#include "ComboBoxLookAndFeel.h" +#include "LookAndFeel.h" -class MainInterface : public juce::Component +class PresetInterface : public juce::Component, juce::ComboBox::Listener, juce::Button::Listener { public: + PresetInterface(FledgeAudioProcessor& p, juce::AudioProcessorValueTreeState& apvts); + ~PresetInterface(); + void paint(juce::Graphics& g) override {} + + void resized() override; + void comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) override; + void buttonClicked(juce::Button* buttonClicked) override; + void loadPresetList(); private: - juce::Slider modIndexSlider, attackSlider, decaySlider, sustainSlider, releaseSlider; -}; + ButtonLookAndFeel saveLAF { 0 }, prevLAF{ 1 }, nextLAF { 2 }; + ComboBoxGraphics presetComboBoxLAF; + juce::TextButton saveButton, nextButton, prevButton; + juce::ComboBox presetComboBox; + juce::Label rateLabel, rateValueLabel; + + std::unique_ptr fileChooser; + PresetManager presetManager; + FledgeAudioProcessor& audioProcessor; +}; class OperatorInterface : public juce::Component, juce::Timer { public: OperatorInterface(FledgeAudioProcessor& p, int index); - + void setIndex(int index); + void paint(juce::Graphics &g) override; void resized() override; - void setLabel(juce::Label &l, juce::String labelText, float size); - - void setIndex(int index); - - void timerCallback() override; private: + void setLabel(juce::Label &l, juce::String labelText, float size); + void timerCallback() override; + int index; juce::Label ratioLabel, @@ -63,142 +80,73 @@ class OperatorInterface : public juce::Component, juce::Timer releaseSlider; OperatorDisplayGraphics opGraphics; - EnvelopeDisplayGraphics envGraphics; + std::unique_ptr envGraphics; FledgeAudioProcessor& audioProcessor; }; - -class AlgorithmSelectInterface : public juce::Component +class AlgorithmSelectInterface : public juce::Component, public juce::Button::Listener { public: - AlgorithmSelectInterface() - { - for(int i = 0; i < 8; i++) - { - addAndMakeVisible(algorithm[i]); - algorithmGraphics[i].setIndex(i); - algorithm[i].setLookAndFeel(&algorithmGraphics[i]); - } - } - - + AlgorithmSelectInterface(FledgeAudioProcessor& p); + ~AlgorithmSelectInterface(); void paint(juce::Graphics& g) override {} - - void resized() override - { - auto bounds = getLocalBounds().toFloat(); - float x = bounds.getX(); - float y = bounds.getY(); - - float width = bounds.getWidth() * 0.8f; - float height = bounds.getHeight() * 0.8f; - float widthMargin = bounds.getWidth() * 0.1f; - float heightMargin = bounds.getHeight() * 0.1f; - - float blockWidth = width * 0.2f; - float blockHeight = height/2; - - for(int i = 0; i < 8; i++) - { - algorithm[i].setBounds(x + widthMargin + blockWidth * (i % 4), - y + heightMargin + blockWidth * (i / 4), - blockWidth, - blockHeight); - } - } - -/* - void buttonClicked(juce::Button* buttonClicked) override - { - if (buttonClicked == &algorithm[0]){ - - } else if (buttonClicked == &algorithm[1]) { + void resized() override; + void buttonClicked(juce::Button* button) override; + void setOperatorParam(int index, int gainIndex); + void setOutputParam(int gainIndex); - } else if (buttonClicked == &algorithm[2]) { - - } else if (buttonClicked == &algorithm[3]) { - - } else if (buttonClicked == &algorithm[4]) { - - } else if (buttonClicked == &algorithm[5]) { - - } else if (buttonClicked == &algorithm[6]) { - - } else if (buttonClicked == &algorithm[7]) { - - } - } -*/ - private: std::array algorithmGraphics; std::array algorithm; + + FledgeAudioProcessor& audioProcessor; }; - -class PresetInterface : public juce::Component, juce::ComboBox::Listener, juce::Button::Listener +class MacroControlsInterface : public juce::Component { public: - PresetInterface(FledgeAudioProcessor& p, juce::AudioProcessorValueTreeState& apvts); - ~PresetInterface(); - + MacroControlsInterface(FledgeAudioProcessor& p); void paint(juce::Graphics& g) override {} - void resized() override; - void comboBoxChanged(juce::ComboBox *comboBoxThatHasChanged) override; - void buttonClicked(juce::Button* buttonClicked) override; - void loadPresetList(); - -private: - juce::TextButton saveButton, nextButton, prevButton; - juce::ComboBox presetComboBox; - juce::Label rateLabel, rateValueLabel; - std::unique_ptr fileChooser; - - PresetManager presetManager; - FledgeAudioProcessor& audioProcessor; -}; - - - - - - - - -class PracticeDialGraphics : public juce::LookAndFeel_V4 -{ -public: - void drawRotarySlider(juce::Graphics& g, int x, int y, int width, int height, float sliderPosProportional, float rotaryStartAngle, float rotaryEndAngle, juce::Slider& slider) override + void setSliderAndLabel(juce::Slider &s, juce::Label &l, DialLookAndFeel &lookAndFeel, juce::String labelText, juce::String suffix) { - g.fillAll(juce::Colour(255, 255, 255)); + addAndMakeVisible(l); + l.setText(labelText, juce::dontSendNotification); + l.setJustificationType(juce::Justification::centred); + l.setColour(juce::Label::textColourId, Colors::textColor); - for (int i = 0 ; i < 6; i++) - { - float lineIncr = (height/6) * sliderPosProportional * i; - - juce::Point leftCoords = { (float)x, (float)y + lineIncr }; - juce::Point rightCoords = { (float)x + width, (float)y + lineIncr }; - - juce::Path linePath; - linePath.startNewSubPath(leftCoords); - linePath.lineTo(rightCoords); - - g.setColour(juce::Colour(155, 155, 155)); - - if (slider.isMouseOverOrDragging()) - { - g.setColour(juce::Colour(200, 200, 200)); - } - - g.strokePath(linePath, juce::PathStrokeType(1.0f)); - } + addAndMakeVisible(s); + s.setSliderStyle(juce::Slider::RotaryHorizontalVerticalDrag); + s.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 40, 20); + s.setLookAndFeel(&lookAndFeel); + s.setTextValueSuffix(suffix); } - -private: +private: + // look and feel + DialLookAndFeel dialLAF; + + juce::Slider globalFreqSlider, + globalModIndexSlider, + globalAttackSlider, + globalDecaySlider, + globalSustainSlider, + globalReleaseSlider; + + juce::Label globalFreqLabel, + globalModIndexLabel, + globalAttackLabel, + globalDecayLabel, + globalSustainLabel, + globalReleaseLabel; + + std::unique_ptr globalFreqAttachment, + globalModIndexAttachment, + globalAttackAttachment, + globalDecayAttachment, + globalSustainAttachment, + globalReleaseAttachment; + FledgeAudioProcessor& audioProcessor; }; - - diff --git a/Source/VoiceProcessor.h b/Source/VoiceProcessor.h index a777afc..bd88ab6 100644 --- a/Source/VoiceProcessor.h +++ b/Source/VoiceProcessor.h @@ -72,66 +72,75 @@ class SynthVoice : public juce::SynthesiserVoice op[index].setOperator(ratio, fixed, isFixed, modIndex); } - void pitchWheelMoved(int newPitchWheelValue) override {} void controllerMoved(int controllerNumber, int newControllerValue) override {} + void renderNextBlock(juce::AudioBuffer &outputBuffer, int startSample, int numSamples) override { - for (int sample = 0; sample < outputBuffer.getNumSamples(); ++sample) { - op3 = op[3].processOperator(op0 * op3Gain[0], - op1 * op3Gain[1], - op2 * op3Gain[2], - op3 * op3Gain[3]); - - op2 = op[2].processOperator(op0 * op2Gain[0], - op1 * op2Gain[1], - op2 * op2Gain[2], - op3 * op2Gain[3]); - - op1 = op[1].processOperator(op0 * op1Gain[0], - op1 * op1Gain[1], - op2 * op1Gain[2], - op3 * op1Gain[3]); - - op0 = op[0].processOperator(op0 * op0Gain[0], - op1 * op0Gain[1], - op2 * op0Gain[2], - op3 * op0Gain[3]); - - float output = op0 * outputGain[0] + - op1 * outputGain[1] + - op2 * outputGain[2] + - op3 * outputGain[3]; - - for (int channel = 0; channel < outputBuffer.getNumChannels(); ++channel) { - // outputBuffer.setSample(channel, sample, output); - outputBuffer.addSample(channel, sample, output); - + if (op[0].ampEnvelope.isActive()) + { + for (int sample = 0; sample < outputBuffer.getNumSamples(); ++sample) { + op3 = op[3].processOperator(op0 * op3Gain[0], + op1 * op3Gain[1], + op2 * op3Gain[2], + op3 * op3Gain[3]); + + op2 = op[2].processOperator(op0 * op2Gain[0], + op1 * op2Gain[1], + op2 * op2Gain[2], + op3 * op2Gain[3]); + + op1 = op[1].processOperator(op0 * op1Gain[0], + op1 * op1Gain[1], + op2 * op1Gain[2], + op3 * op1Gain[3]); + + op0 = op[0].processOperator(op0 * op0Gain[0], + op1 * op0Gain[1], + op2 * op0Gain[2], + op3 * op0Gain[3]); + + // DBG(op0Gain[0] << op0Gain[1] << op0Gain[2] << op0Gain[3]); + + float output = op0 * outputGain[0] + + op1 * outputGain[1] + + op2 * outputGain[2] + + op3 * outputGain[3]; + + outputSample = output * 0.25f; + for (int channel = 0; channel < outputBuffer.getNumChannels(); ++channel) + { + outputBuffer.addSample(channel, sample, outputSample); + } } } } - void setOperatorGain(int index, int gainIndex) + void setOperatorGain(int index, int gainIndex, int outputGainIndex) { + outputGain = toBinary4(outputGainIndex); + switch(index){ case 0: - outputGain = toBinary4(gainIndex); - break; - case 1: op0Gain = toBinary4(gainIndex); break; - case 2: + case 1: op1Gain = toBinary4(gainIndex); break; - case 3: + case 2: op2Gain = toBinary4(gainIndex); break; - case 4: + case 3: op3Gain = toBinary4(gainIndex); break; } } + float getOutputSample() + { + return outputSample; + } + private: std::array toBinary4(int input) { @@ -142,6 +151,9 @@ class SynthVoice : public juce::SynthesiserVoice } double sampleRate; + juce::AudioBuffer synthBuffer; + float outputSample; + float op0 = 0.0f, op1 = 0.0f, op2 = 0.0f, op3 = 0.0f, feedback = 0.0f; // unit delays for algorithm std::array op3Gain = { 0.0f, 0.0f, 0.0f, 0.0f }; @@ -151,9 +163,5 @@ class SynthVoice : public juce::SynthesiserVoice std::array outputGain = { 0.0f, 0.0f, 0.0f, 0.0f }; std::array op; - - juce::AudioSampleBuffer outputWavetable; - int tableSize = 128; - float tableDelta, currentIndex, tableSizeOverSampleRate; }; From 7de65a87e3fb2e2912cb7de708b82904b213b5df Mon Sep 17 00:00:00 2001 From: Repairer of Reputations Date: Mon, 28 Jul 2025 16:07:33 -0400 Subject: [PATCH 5/5] Add files via upload --- Source/LevelMeter.cpp | 4 ++-- Source/PluginEditor.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/LevelMeter.cpp b/Source/LevelMeter.cpp index d08ae4e..ac79831 100644 --- a/Source/LevelMeter.cpp +++ b/Source/LevelMeter.cpp @@ -20,7 +20,7 @@ void LevelMeter::paint (juce::Graphics& g) const auto bounds = getLocalBounds(); // Fill background first - g.fillAll(Colors::LevelMeter::background); + g.fillAll(juce::Colour(35, 37, 36)); // Draw levels horizontally - top and bottom channels drawLevel(g, dbLevelL, 0, bounds.getHeight() / 2 - 1); // Top channel @@ -83,4 +83,4 @@ void LevelMeter::updateLevel(float newLevel, float& smoothedLevel, float& leveld } else { leveldB = clampdB; } -} \ No newline at end of file +} diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index d0016fc..b661b45 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -52,10 +52,10 @@ FledgeAudioProcessorEditor::FledgeAudioProcessorEditor (FledgeAudioProcessor& p) outputLevelMeter = std::make_unique - ( - audioProcessor.getOutputLevelL(), - audioProcessor.getOutputLevelR() - ); + ( + audioProcessor.getOutputLevelL(), + audioProcessor.getOutputLevelR() + ); addAndMakeVisible(*outputLevelMeter); const auto params = audioProcessor.getParameters(); @@ -107,7 +107,7 @@ void FledgeAudioProcessorEditor::resized() showAlgorithmButton.setBounds(100, 555, 94, 40); showMacrosButton.setBounds(200, 555, 94, 40); - outputLevelMeter->setBounds(285, 605, 480, 25); + outputLevelMeter->setBounds(555, 20, 200, 25); }