├── .gitignore ├── Equalizer ├── Equalizer.jucer └── Source │ ├── PluginEditor.cpp │ ├── PluginEditor.h │ ├── PluginProcessor.cpp │ └── PluginProcessor.h ├── README.md └── Unnamed.filtergraph /.gitignore: -------------------------------------------------------------------------------- 1 | **/Builds 2 | **/JuceLibraryCode 3 | **/.DS_Store 4 | # Prerequisites 5 | *.d 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | *.smod 25 | 26 | # Compiled Static libraries 27 | *.lai 28 | *.la 29 | *.a 30 | *.lib 31 | 32 | # Executables 33 | *.exe 34 | *.out 35 | *.app 36 | -------------------------------------------------------------------------------- /Equalizer/Equalizer.jucer: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 10 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Equalizer/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | void LookAndFeel::drawRotarySlider(juce::Graphics & g, 13 | int x, 14 | int y, 15 | int width, 16 | int height, 17 | float sliderPos, 18 | float rotaryStartAngle, 19 | float rotaryEndAngle, 20 | juce::Slider & slider) 21 | { 22 | auto fill = slider.findColour (juce::Slider::rotarySliderFillColourId); 23 | auto bounds = juce::Rectangle (x, y, width, height).reduced (2.0f); 24 | auto radius = juce::jmin (bounds.getWidth(), bounds.getHeight()) / 2.0f; 25 | auto toAngle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); 26 | auto lineW = radius * 0.085f; 27 | auto arcRadius = radius - lineW * 1.6f; 28 | 29 | juce::Path backgroundArc; 30 | backgroundArc.addCentredArc (bounds.getCentreX(), 31 | bounds.getCentreY(), 32 | arcRadius, 33 | arcRadius, 34 | 0.0f, 35 | rotaryStartAngle, 36 | rotaryEndAngle, 37 | true); 38 | 39 | g.setColour (juce::Colour::fromRGB(105, 105, 105)); 40 | g.strokePath (backgroundArc, juce::PathStrokeType (lineW, juce::PathStrokeType::curved, juce::PathStrokeType::rounded)); 41 | 42 | juce::Path valueArc; 43 | valueArc.addCentredArc (bounds.getCentreX(), 44 | bounds.getCentreY(), 45 | arcRadius, 46 | arcRadius, 47 | 0.0f, 48 | rotaryStartAngle, 49 | toAngle, 50 | true); 51 | 52 | g.setColour (fill); 53 | g.strokePath (valueArc, juce::PathStrokeType (lineW, juce::PathStrokeType::curved, juce::PathStrokeType::rounded)); 54 | 55 | juce::Path stick; 56 | auto stickWidth = lineW * 2.0f; 57 | 58 | stick.addRectangle (-stickWidth / 2, -stickWidth / 2, stickWidth, radius + lineW); 59 | 60 | g.setColour (juce::Colour::fromRGB(211, 211, 211)); 61 | g.fillPath (stick, juce::AffineTransform::rotation (toAngle + 3.12f).translated (bounds.getCentre())); 62 | 63 | g.fillEllipse (bounds.reduced (radius * 0.25)); 64 | 65 | if(auto *rswl = dynamic_cast(&slider)){ 66 | auto center = bounds.getCentre(); 67 | juce::Path p; 68 | juce::Rectangle r; 69 | r.setLeft(center.getX()-2); 70 | r.setRight(center.getX()+2); 71 | r.setTop(bounds.getY()); 72 | r.setBottom(center.getY()-rswl->getTextHeight()*1.5); 73 | p.addRoundedRectangle(r, 2.f); 74 | jassert(rotaryStartAngle < rotaryEndAngle); 75 | auto sliderAngRad = juce::jmap(sliderPos, 0.f, 1.f, rotaryStartAngle, rotaryEndAngle); 76 | p.applyTransform(juce::AffineTransform().rotated(sliderAngRad,center.getX(), center.getY())); 77 | g.fillPath(p); 78 | g.setFont(10); 79 | auto text = rswl->getDisplayString(); 80 | r.setSize(40, rswl->getTextHeight()); 81 | r.setCentre(bounds.getCentre()); 82 | g.setColour (juce::Colour::fromRGB(211, 211, 211)); 83 | g.fillRect(r); 84 | g.setColour(juce::Colour::fromRGB(34, 34, 34)); 85 | g.drawFittedText(text, r.toNearestInt(), juce::Justification::centred, 1); 86 | } 87 | } 88 | 89 | void LookAndFeel::drawToggleButton(juce::Graphics &g, 90 | juce::ToggleButton &toggleButton, 91 | bool shouldDrawButtonAsHighlighted, 92 | bool shouldDrawButtonAsDown) 93 | { 94 | using namespace juce; 95 | 96 | Path powerButton; 97 | 98 | auto bounds = toggleButton.getLocalBounds(); 99 | 100 | auto size = jmin(bounds.getWidth(), bounds.getHeight()) - 6; 101 | auto r = bounds.withSizeKeepingCentre(size, size).toFloat(); 102 | 103 | float ang = 30.f; //30.f; 104 | 105 | size -= 6; 106 | 107 | powerButton.addCentredArc(r.getCentreX(), 108 | r.getCentreY(), 109 | size * 0.5, 110 | size * 0.5, 111 | 0.f, 112 | degreesToRadians(ang), 113 | degreesToRadians(360.f - ang), 114 | true); 115 | 116 | powerButton.startNewSubPath(r.getCentreX(), r.getY()); 117 | powerButton.lineTo(r.getCentre()); 118 | 119 | PathStrokeType pst(2.f, PathStrokeType::JointStyle::curved); 120 | 121 | auto color = toggleButton.getToggleState() ? Colours::dimgrey : Colours::darkgrey; 122 | 123 | g.setColour(color); 124 | g.strokePath(powerButton, pst); 125 | g.drawEllipse(r, 2); 126 | } 127 | void RotarySliderWithLabels::paint(juce::Graphics &g) 128 | { 129 | using namespace juce; 130 | 131 | auto startAng = degreesToRadians(180.f + 45.f); 132 | auto endAng = degreesToRadians(180.f - 45.f) + MathConstants::twoPi; 133 | 134 | auto range = getRange(); 135 | 136 | auto sliderBounds = getSliderBounds(); 137 | 138 | // g.setColour(Colours::red); 139 | // g.drawRect(getLocalBounds()); 140 | // g.drawRect(sliderBounds); 141 | 142 | getLookAndFeel().drawRotarySlider(g, 143 | sliderBounds.getX(), 144 | sliderBounds.getY(), 145 | sliderBounds.getWidth(), 146 | sliderBounds.getHeight(), 147 | jmap(getValue(), range.getStart(), range.getEnd(), 0.0, 1.0), 148 | startAng, 149 | endAng, 150 | *this); 151 | 152 | auto center = sliderBounds.toFloat().getCentre(); 153 | auto radius = sliderBounds.getWidth() * 0.5f; 154 | g.setColour(juce::Colour::fromRGB(0,0,0)); 155 | g.setFont(10); 156 | auto numChoices = labels.size(); 157 | for( int i = 0; i < numChoices; ++i ){ 158 | auto pos = labels[i].pos; 159 | jassert(0.f <= pos); 160 | jassert(pos <= 1.f); 161 | auto ang = jmap(pos, 0.f, 1.f, startAng, endAng); 162 | auto c = center.getPointOnCircumference(radius + getTextHeight() * 0.5f + 1, ang); 163 | Rectangle r; 164 | auto str = labels[i].label; 165 | r.setSize(g.getCurrentFont().getStringWidth(str), getTextHeight()); 166 | r.setCentre(c); 167 | r.setY(r.getY() + getTextHeight()); 168 | g.drawFittedText(str, r.toNearestInt(), juce::Justification::centred,1); 169 | 170 | } 171 | 172 | } 173 | 174 | juce::Rectangle RotarySliderWithLabels::getSliderBounds() const 175 | { 176 | auto bounds = getLocalBounds(); 177 | auto size = juce::jmin(bounds.getWidth(), bounds.getHeight()); 178 | size -= getTextHeight() * 2; 179 | juce::Rectangle r; 180 | r.setSize(size, size); 181 | r.setCentre(bounds.getCentreX(), 0); 182 | r.setY(2); 183 | return r; 184 | } 185 | 186 | juce::String RotarySliderWithLabels::getDisplayString() const { 187 | if( auto *choiceParam = dynamic_cast(param)) 188 | return choiceParam->getCurrentChoiceName(); 189 | juce::String str; 190 | bool addK = false; 191 | if( auto *floatParam = dynamic_cast(param)){ 192 | float val = getValue(); 193 | if( val > 999.f ){ 194 | val /= 1000.f; 195 | addK = true; 196 | } 197 | str = juce::String(val, (addK ? 2 : 0)); 198 | } else { 199 | jassertfalse; 200 | } 201 | if ( suffix.isNotEmpty() ){ 202 | str << ""; 203 | if ( addK ) 204 | str << "k"; 205 | str <addListener(this); 222 | } 223 | 224 | updateChain(); 225 | startTimerHz(60); 226 | } 227 | 228 | ResponseCurveComponent::~ResponseCurveComponent(){ 229 | const auto ¶ms = audioProcessor.getParameters(); 230 | for (auto param : params ){ 231 | param->removeListener(this); 232 | } 233 | } 234 | 235 | 236 | void PathProducer::process(juce::Rectangle fftBounds, double sampleRate){ 237 | juce::AudioBuffer tempIncomingBuffer; 238 | 239 | while( leftChannelFifo->getNumCompleteBuffersAvailable() > 0 ) 240 | { 241 | if( leftChannelFifo->getAudioBuffer(tempIncomingBuffer) ) 242 | { 243 | auto size = tempIncomingBuffer.getNumSamples(); 244 | 245 | juce::FloatVectorOperations::copy(monoBuffer.getWritePointer(0, 0), 246 | monoBuffer.getReadPointer(0, size), 247 | monoBuffer.getNumSamples() - size); 248 | 249 | juce::FloatVectorOperations::copy(monoBuffer.getWritePointer(0, monoBuffer.getNumSamples() - size), 250 | tempIncomingBuffer.getReadPointer(0, 0), 251 | size); 252 | 253 | leftChannelFFTDataGenerator.produceFFTDataForRendering(monoBuffer, -48.f); 254 | } 255 | } 256 | 257 | /* 258 | if there are FFT data buffers to pull 259 | if we can pull a buffer 260 | generate a path 261 | */ 262 | 263 | const auto fftSize = leftChannelFFTDataGenerator.getFFTSize(); 264 | 265 | /* 266 | 48000 / 2048 = 23hz <- this is the bin width 267 | */ 268 | const auto binWidth = sampleRate / (double)fftSize; 269 | 270 | while( leftChannelFFTDataGenerator.getNumAvailableFFTDataBlocks() > 0 ) 271 | { 272 | std::vector fftData; 273 | if( leftChannelFFTDataGenerator.getFFTData(fftData) ) 274 | { 275 | pathProducer.generatePath(fftData, fftBounds, fftSize, binWidth, -48.f); 276 | } 277 | } 278 | 279 | /* 280 | while there are paths that can be pull 281 | pull as many as we can 282 | display the most recent path 283 | */ 284 | 285 | while (pathProducer.getNumPathsAvailable() ) 286 | { 287 | pathProducer.getPath(leftChannelFFTPath); 288 | } 289 | } 290 | 291 | void ResponseCurveComponent::timerCallback() 292 | { 293 | 294 | auto fftBounds = getAnalysisArea().toFloat(); 295 | auto sampleRate = audioProcessor.getSampleRate(); 296 | 297 | leftPathProducer.process(fftBounds, sampleRate); 298 | rightPathProducer.process(fftBounds, sampleRate); 299 | 300 | if( parametersChanged.compareAndSetBool(false, true) ) 301 | { 302 | updateChain(); 303 | } 304 | repaint(); 305 | } 306 | 307 | void ResponseCurveComponent::updateChain(){ 308 | 309 | auto chainSettings = getChainSettings(audioProcessor.apvts); 310 | monoChain.setBypassed(chainSettings.lowCutBypassed); 311 | monoChain.setBypassed(chainSettings.peakBypassed); 312 | monoChain.setBypassed(chainSettings.highCutBypassed); 313 | auto peakCoefficients = makePeakFilter(chainSettings, audioProcessor.getSampleRate()); 314 | updateCoefficients(monoChain.get().coefficients, peakCoefficients); 315 | 316 | auto lowCutCoefficients = makeLowCutFilter(chainSettings, audioProcessor.getSampleRate()); 317 | auto highCutCoefficients = makeHighCutFilter(chainSettings, audioProcessor.getSampleRate()); 318 | 319 | updateCutFilter(monoChain.get(), lowCutCoefficients, chainSettings.lowCutSlope); 320 | updateCutFilter(monoChain.get(), highCutCoefficients, chainSettings.highCutSlope); 321 | } 322 | void ResponseCurveComponent::paint (juce::Graphics& g) 323 | { 324 | 325 | using namespace juce; 326 | g.fillAll (Colours::white); 327 | g.drawImage(background, getLocalBounds().toFloat()); 328 | auto responseArea = getAnalysisArea(); 329 | auto w = responseArea.getWidth(); 330 | 331 | auto &lowcut = monoChain.get(); 332 | auto &peak = monoChain.get(); 333 | auto &highcut = monoChain.get(); 334 | 335 | auto sampleRate = audioProcessor.getSampleRate(); 336 | 337 | std::vector mags; 338 | mags.resize(w); 339 | 340 | for( int i = 0; i < w; ++i ){ 341 | double mag = 1.f; 342 | auto freq = mapToLog10(double(i) / double(w), 20.0, 20000.0); 343 | if( ! monoChain.isBypassed() ) 344 | mag *= peak.coefficients->getMagnitudeForFrequency(freq, sampleRate); 345 | if( !monoChain.isBypassed() ) 346 | { 347 | if( !lowcut.isBypassed<0>() ) 348 | mag *= lowcut.get<0>().coefficients->getMagnitudeForFrequency(freq, sampleRate); 349 | if( !lowcut.isBypassed<1>() ) 350 | mag *= lowcut.get<1>().coefficients->getMagnitudeForFrequency(freq, sampleRate); 351 | if( !lowcut.isBypassed<2>() ) 352 | mag *= lowcut.get<2>().coefficients->getMagnitudeForFrequency(freq, sampleRate); 353 | if( !lowcut.isBypassed<3>() ) 354 | mag *= lowcut.get<3>().coefficients->getMagnitudeForFrequency(freq, sampleRate); 355 | } 356 | 357 | if( !monoChain.isBypassed() ) 358 | { 359 | if( !highcut.isBypassed<0>() ) 360 | mag *= highcut.get<0>().coefficients->getMagnitudeForFrequency(freq, sampleRate); 361 | if( !highcut.isBypassed<1>() ) 362 | mag *= highcut.get<1>().coefficients->getMagnitudeForFrequency(freq, sampleRate); 363 | if( !highcut.isBypassed<2>() ) 364 | mag *= highcut.get<2>().coefficients->getMagnitudeForFrequency(freq, sampleRate); 365 | if( !highcut.isBypassed<3>() ) 366 | mag *= highcut.get<3>().coefficients->getMagnitudeForFrequency(freq, sampleRate); 367 | } 368 | mags[i] = Decibels::gainToDecibels(mag); 369 | } 370 | 371 | Path responseCurve; 372 | 373 | const double outputMin = responseArea.getBottom(); 374 | const double outputMax = responseArea.getY(); 375 | auto map = [outputMin, outputMax](double input){ 376 | return jmap(input, -24.0, 24.0, outputMin, outputMax); 377 | }; 378 | 379 | responseCurve.startNewSubPath(responseArea.getX(), map(mags.front())); 380 | 381 | for( size_t i = 1; i < mags.size(); ++i){ 382 | responseCurve.lineTo(responseArea.getX() + i, map(mags[i])); 383 | } 384 | auto leftChannelFFTPath = leftPathProducer.getPath(); 385 | g.setColour(Colours::pink); 386 | leftChannelFFTPath.applyTransform(AffineTransform().translation(responseArea.getX(), responseArea.getY())); 387 | g.strokePath(leftChannelFFTPath, PathStrokeType(1.f)); 388 | auto rightChannelFFTPath = rightPathProducer.getPath(); 389 | rightChannelFFTPath.applyTransform(AffineTransform().translation(responseArea.getX(), responseArea.getY())); 390 | 391 | g.setColour(Colours::lightyellow); 392 | g.strokePath(rightChannelFFTPath, PathStrokeType(1.f)); 393 | g.setColour(Colour::fromRGB(34, 34, 34)); 394 | g.drawRoundedRectangle(getRenderArea().toFloat(),4.f, 2.f); 395 | g.setColour(Colours::black); 396 | g.strokePath(responseCurve, PathStrokeType(2.f)); 397 | 398 | } 399 | 400 | void ResponseCurveComponent::resized(){ 401 | using namespace juce; 402 | background = Image(Image::PixelFormat::RGB, getWidth(), getHeight(), true); 403 | Graphics g(background); 404 | Array freqs 405 | { 406 | 20, 50, 100, 407 | 200, 500, 1000, 408 | 2000, 5000, 10000, 409 | 20000 410 | }; 411 | 412 | auto renderArea = getAnalysisArea(); 413 | auto left = renderArea.getX(); 414 | auto right = renderArea.getRight(); 415 | auto top = renderArea.getY(); 416 | auto bottom = renderArea.getBottom(); 417 | auto width = renderArea.getWidth(); 418 | 419 | Array xs; 420 | 421 | for( auto f : freqs ){ 422 | auto normX = mapFromLog10(f, 20.f, 20000.f); 423 | xs.add(left + width * normX); 424 | } 425 | g.setColour(Colours::black); 426 | for ( auto x : xs ){ 427 | g.drawVerticalLine(x, top, bottom); 428 | } 429 | 430 | Array gain{ 431 | -24, -12, 0, 12, 24 432 | }; 433 | 434 | for( auto gDb : gain ){ 435 | auto y = jmap(gDb, -24.f, 24.f, float(bottom), float(top)); 436 | g.setColour(gDb == 0.f ? Colours::pink : Colours::darkgrey); 437 | g.drawHorizontalLine(y, left, right); 438 | } 439 | 440 | g.setColour(Colours::black); 441 | const int fontHeight = 10; 442 | g.setFont(fontHeight); 443 | for( int i = 0; i < freqs.size(); ++i ){ 444 | auto f = freqs[i]; 445 | auto x = xs[i]; 446 | bool addK = false; 447 | String str; 448 | if ( f > 999.f ){ 449 | addK = true; 450 | f /= 1000.f; 451 | } 452 | str << f; 453 | if( addK ) 454 | str << "k"; 455 | str << "Hz"; 456 | auto textWidth = g.getCurrentFont().getStringWidth(str); 457 | Rectangle r; 458 | r.setSize(textWidth, fontHeight); 459 | r.setCentre(x, 0); 460 | r.setY(1); 461 | 462 | g.drawFittedText(str, r, juce::Justification::centred, 1); 463 | } 464 | for( auto gDb : gain ){ 465 | auto y = jmap(gDb, -24.f, 24.f, float(bottom), float(top)); 466 | String str; 467 | if( gDb > 0) 468 | str << "+"; 469 | str < r; 472 | r.setSize(textWidth, fontHeight); 473 | r.setX(getWidth()-textWidth); 474 | r.setCentre(r.getCentreX(), y); 475 | g.setColour(gDb == 0.f ? Colours::pink : Colours::black); 476 | g.drawFittedText(str, r, juce::Justification::centred, 1); 477 | str.clear(); 478 | str << (gDb - 24.f); 479 | 480 | r.setX(1); 481 | textWidth = g.getCurrentFont().getStringWidth(str); 482 | r.setSize(textWidth, fontHeight); 483 | g.setColour(Colours::black); 484 | g.drawFittedText(str, r, juce::Justification::centred, 1); 485 | } 486 | } 487 | 488 | juce::Rectangle ResponseCurveComponent::getRenderArea(){ 489 | auto bounds = getLocalBounds(); 490 | bounds.removeFromTop(12); 491 | bounds.removeFromBottom(2); 492 | bounds.removeFromLeft(20); 493 | bounds.removeFromRight(20); 494 | return bounds; 495 | } 496 | 497 | juce::Rectangle ResponseCurveComponent::getAnalysisArea(){ 498 | auto bounds = getRenderArea(); 499 | bounds.removeFromTop(4); 500 | bounds.removeFromBottom(4); 501 | return bounds; 502 | } 503 | //============================================================================== 504 | EqualizerAudioProcessorEditor::EqualizerAudioProcessorEditor (EqualizerAudioProcessor& p) 505 | : AudioProcessorEditor (&p), audioProcessor (p), 506 | peakFreqSlider(*audioProcessor.apvts.getParameter("Peak Freq"),"Hz"), 507 | peakGainSlider(*audioProcessor.apvts.getParameter("Peak Gain"), "dB"), 508 | peakQualitySlider(*audioProcessor.apvts.getParameter("Peak Quality"),""), 509 | lowCutFreqSlider(*audioProcessor.apvts.getParameter("LowCut Freq"), "Hz"), 510 | highCutFreqSlider(*audioProcessor.apvts.getParameter("HighCut Freq"), "Hz"), 511 | lowCutSlopeSlider(*audioProcessor.apvts.getParameter("LowCut Slope"), "db/Oct"), 512 | highCutSlopeSlider(*audioProcessor.apvts.getParameter("HighCut Slope"), "db/Oct"), 513 | responseCurveComponent(audioProcessor), 514 | peakFreqSliderAttachment(audioProcessor.apvts, "Peak Freq", peakFreqSlider), 515 | peakGainSliderAttachment(audioProcessor.apvts, "Peak Gain", peakGainSlider), 516 | peakQualitySliderAttachment(audioProcessor.apvts, "Peak Quality", peakQualitySlider), 517 | lowCutFreqSliderAttachment(audioProcessor.apvts, "LowCut Freq", lowCutFreqSlider), 518 | highCutFreqSliderAttachment(audioProcessor.apvts, "HighCut Freq", highCutFreqSlider), 519 | lowCutSlopeSliderAttachment(audioProcessor.apvts, "LowCut Slope", lowCutSlopeSlider), 520 | highCutSlopeSliderAttachment(audioProcessor.apvts, "HighCut Slope", highCutSlopeSlider), 521 | 522 | lowcutBypassButtonAttachment(audioProcessor.apvts, "LowCut Bypassed", lowcutBypassButton), 523 | peakBypassButtonAttachment(audioProcessor.apvts, "Peak Bypassed", peakBypassButton), 524 | highcutBypassButtonAttachment(audioProcessor.apvts, "HighCut Bypassed", highcutBypassButton), 525 | analyzerEnabledButtonAttachment(audioProcessor.apvts, "Analyzer Enabled", analyzerEnabledButton) 526 | { 527 | 528 | peakFreqSlider.labels.add({0.f, "20Hz"}); 529 | peakFreqSlider.labels.add({1.f, "20KHz"}); 530 | peakGainSlider.labels.add({0.f, "-24dB"}); 531 | peakGainSlider.labels.add({1.f, "+24dB"}); 532 | peakQualitySlider.labels.add({0.f, "0.1"}); 533 | peakQualitySlider.labels.add({1.f, "10.0"}); 534 | lowCutFreqSlider.labels.add({0.f, "20Hz"}); 535 | lowCutFreqSlider.labels.add({1.f, "20kHz"}); 536 | highCutFreqSlider.labels.add({0.f, "20Hz"}); 537 | highCutFreqSlider.labels.add({1.f, "20kHz"}); 538 | lowCutSlopeSlider.labels.add({0.f, "12"}); 539 | lowCutSlopeSlider.labels.add({1.f, "48"}); 540 | highCutSlopeSlider.labels.add({0.f, "12"}); 541 | highCutSlopeSlider.labels.add({1.f,"48"}); 542 | 543 | for (auto* comp : getComps()){ 544 | addAndMakeVisible(comp); 545 | } 546 | peakBypassButton.setLookAndFeel(&lnf); 547 | lowcutBypassButton.setLookAndFeel(&lnf); 548 | highcutBypassButton.setLookAndFeel(&lnf); 549 | setSize (600, 480); 550 | 551 | } 552 | 553 | EqualizerAudioProcessorEditor::~EqualizerAudioProcessorEditor(){ 554 | 555 | peakBypassButton.setLookAndFeel(nullptr); 556 | lowcutBypassButton.setLookAndFeel(nullptr); 557 | highcutBypassButton.setLookAndFeel(nullptr); 558 | 559 | } 560 | 561 | //============================================================================== 562 | void EqualizerAudioProcessorEditor::paint (juce::Graphics& g) 563 | { 564 | 565 | using namespace juce; 566 | g.fillAll (Colours::white); 567 | } 568 | 569 | 570 | void EqualizerAudioProcessorEditor::resized() 571 | { 572 | auto bounds = getLocalBounds(); 573 | auto responseArea = bounds.removeFromTop(bounds.getHeight() * 0.33); 574 | 575 | responseCurveComponent.setBounds(responseArea); 576 | 577 | auto lowCutArea = bounds.removeFromLeft(bounds.getWidth() * 0.33); 578 | auto highCutArea = bounds.removeFromRight(bounds.getWidth() * 0.5); 579 | lowcutBypassButton.setBounds(lowCutArea.removeFromTop(25)); 580 | lowCutFreqSlider.setBounds(lowCutArea.removeFromTop(lowCutArea.getHeight() * 0.5)); 581 | lowCutSlopeSlider.setBounds(lowCutArea); 582 | highcutBypassButton.setBounds(highCutArea.removeFromTop(25)); 583 | highCutFreqSlider.setBounds(highCutArea.removeFromTop(highCutArea.getHeight() * 0.5)); 584 | highCutSlopeSlider.setBounds(highCutArea); 585 | 586 | peakBypassButton.setBounds(bounds.removeFromTop(25)); 587 | peakFreqSlider.setBounds(bounds.removeFromTop(bounds.getHeight() * 0.33)); 588 | peakGainSlider.setBounds(bounds.removeFromTop(bounds.getHeight() * 0.5)); 589 | peakQualitySlider.setBounds(bounds); 590 | } 591 | 592 | 593 | 594 | 595 | std::vector EqualizerAudioProcessorEditor::getComps() 596 | { 597 | return 598 | { 599 | &peakFreqSlider, 600 | &peakGainSlider, 601 | &peakQualitySlider, 602 | &lowCutFreqSlider, 603 | &highCutFreqSlider, 604 | &lowCutSlopeSlider, 605 | &highCutSlopeSlider, 606 | &responseCurveComponent, 607 | &lowcutBypassButton, 608 | &peakBypassButton, 609 | &highcutBypassButton, 610 | &analyzerEnabledButton 611 | }; 612 | } 613 | -------------------------------------------------------------------------------- /Equalizer/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin editor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include "PluginProcessor.h" 13 | 14 | enum FFTOrder 15 | { 16 | order2048 = 11, 17 | order4096 = 12, 18 | order8192 = 13 19 | }; 20 | 21 | template 22 | struct FFTDataGenerator 23 | { 24 | /** 25 | produces the FFT data from an audio buffer. 26 | */ 27 | void produceFFTDataForRendering(const juce::AudioBuffer& audioData, const float negativeInfinity) 28 | { 29 | const auto fftSize = getFFTSize(); 30 | 31 | fftData.assign(fftData.size(), 0); 32 | auto* readIndex = audioData.getReadPointer(0); 33 | std::copy(readIndex, readIndex + fftSize, fftData.begin()); 34 | 35 | // first apply a windowing function to our data 36 | window->multiplyWithWindowingTable (fftData.data(), fftSize); // [1] 37 | 38 | // then render our FFT data.. 39 | forwardFFT->performFrequencyOnlyForwardTransform (fftData.data()); // [2] 40 | 41 | int numBins = (int)fftSize / 2; 42 | 43 | //normalize the fft values. 44 | for( int i = 0; i < numBins; ++i ) 45 | { 46 | fftData[i] /= (float) numBins; 47 | } 48 | 49 | //convert them to decibels 50 | for( int i = 0; i < numBins; ++i ) 51 | { 52 | fftData[i] = juce::Decibels::gainToDecibels(fftData[i], negativeInfinity); 53 | } 54 | 55 | fftDataFifo.push(fftData); 56 | } 57 | 58 | void changeOrder(FFTOrder newOrder) 59 | { 60 | //when you change order, recreate the window, forwardFFT, fifo, fftData 61 | //also reset the fifoIndex 62 | //things that need recreating should be created on the heap via std::make_unique<> 63 | 64 | order = newOrder; 65 | auto fftSize = getFFTSize(); 66 | 67 | forwardFFT = std::make_unique(order); 68 | window = std::make_unique>(fftSize, juce::dsp::WindowingFunction::blackmanHarris); 69 | 70 | fftData.clear(); 71 | fftData.resize(fftSize * 2, 0); 72 | 73 | fftDataFifo.prepare(fftData.size()); 74 | } 75 | //============================================================================== 76 | int getFFTSize() const { return 1 << order; } 77 | int getNumAvailableFFTDataBlocks() const { return fftDataFifo.getNumAvailableForReading(); } 78 | //============================================================================== 79 | bool getFFTData(BlockType& fftData) { return fftDataFifo.pull(fftData); } 80 | private: 81 | FFTOrder order; 82 | BlockType fftData; 83 | std::unique_ptr forwardFFT; 84 | std::unique_ptr> window; 85 | 86 | Fifo fftDataFifo; 87 | }; 88 | 89 | template 90 | 91 | struct AnalyzerPathGenerator 92 | { 93 | /* 94 | converts 'renderData[]' into a juce::Path 95 | */ 96 | void generatePath(const std::vector& renderData, 97 | juce::Rectangle fftBounds, 98 | int fftSize, 99 | float binWidth, 100 | float negativeInfinity) 101 | { 102 | auto top = fftBounds.getY(); 103 | auto bottom = fftBounds.getHeight(); 104 | auto width = fftBounds.getWidth(); 105 | 106 | int numBins = (int)fftSize / 2; 107 | 108 | PathType p; 109 | p.preallocateSpace(3 * (int)fftBounds.getWidth()); 110 | 111 | auto map = [bottom, top, negativeInfinity](float v) 112 | { 113 | return juce::jmap(v, 114 | negativeInfinity, 0.f, 115 | float(bottom), top); 116 | }; 117 | 118 | auto y = map(renderData[0]); 119 | 120 | jassert( !std::isnan(y) && !std::isinf(y) ); 121 | 122 | p.startNewSubPath(0, y); 123 | 124 | const int pathResolution = 2; //you can draw line-to's every 'pathResolution' pixels. 125 | 126 | for( int binNum = 1; binNum < numBins; binNum += pathResolution ) 127 | { 128 | y = map(renderData[binNum]); 129 | 130 | jassert( !std::isnan(y) && !std::isinf(y) ); 131 | 132 | if( !std::isnan(y) && !std::isinf(y) ) 133 | { 134 | auto binFreq = binNum * binWidth; 135 | auto normalizedBinX = juce::mapFromLog10(binFreq, 20.f, 20000.f); 136 | int binX = std::floor(normalizedBinX * width); 137 | p.lineTo(binX, y); 138 | } 139 | } 140 | 141 | pathFifo.push(p); 142 | } 143 | 144 | int getNumPathsAvailable() const 145 | { 146 | return pathFifo.getNumAvailableForReading(); 147 | } 148 | 149 | bool getPath(PathType& path) 150 | { 151 | return pathFifo.pull(path); 152 | } 153 | private: 154 | Fifo pathFifo; 155 | }; 156 | 157 | struct LookAndFeel : juce::LookAndFeel_V4{ 158 | void drawRotarySlider (juce::Graphics&, 159 | int x, int y, int width, int height, 160 | float sliderPosProportional, 161 | float rotaryStartAngle, 162 | float rotaryEndAngle, 163 | juce::Slider&) override; 164 | void drawToggleButton (juce::Graphics &g, 165 | juce::ToggleButton & toggleButton, 166 | bool shouldDrawButtonAsHighlighted, 167 | bool shouldDrawButtonAsDown) override; 168 | }; 169 | 170 | struct RotarySliderWithLabels: juce::Slider { 171 | RotarySliderWithLabels(juce::RangedAudioParameter &rap, const juce::String &unitSuffix) : 172 | juce::Slider(juce::Slider::SliderStyle::RotaryVerticalDrag,juce::Slider::TextEntryBoxPosition::NoTextBox), 173 | param(&rap), 174 | suffix(unitSuffix) 175 | { 176 | setLookAndFeel(&lnf); 177 | } 178 | ~RotarySliderWithLabels() 179 | { 180 | setLookAndFeel(nullptr); 181 | } 182 | struct LabelPos 183 | { 184 | float pos; 185 | juce::String label; 186 | }; 187 | juce::Array labels; 188 | void paint(juce::Graphics& g) override; 189 | juce::Rectangle getSliderBounds() const; 190 | int getTextHeight() const { return 14; } 191 | juce::String getDisplayString() const; 192 | private: 193 | LookAndFeel lnf; 194 | juce::RangedAudioParameter* param; 195 | juce::String suffix; 196 | }; 197 | 198 | struct PathProducer 199 | { 200 | PathProducer(SingleChannelSampleFifo& scsf) : 201 | leftChannelFifo(&scsf) 202 | { 203 | leftChannelFFTDataGenerator.changeOrder(FFTOrder::order2048); 204 | monoBuffer.setSize(1, leftChannelFFTDataGenerator.getFFTSize()); 205 | } 206 | void process(juce::Rectangle fftBounds, double sampleRate); 207 | juce::Path getPath() { return leftChannelFFTPath; } 208 | private: 209 | SingleChannelSampleFifo* leftChannelFifo; 210 | 211 | juce::AudioBuffer monoBuffer; 212 | 213 | FFTDataGenerator> leftChannelFFTDataGenerator; 214 | 215 | AnalyzerPathGenerator pathProducer; 216 | 217 | juce::Path leftChannelFFTPath; 218 | }; 219 | 220 | struct ResponseCurveComponent: juce::Component, 221 | juce::AudioProcessorParameter::Listener, 222 | juce::Timer 223 | { 224 | ResponseCurveComponent(EqualizerAudioProcessor&); 225 | ~ResponseCurveComponent(); 226 | 227 | void parameterValueChanged (int parameterIndex, float newValue) override; 228 | 229 | void parameterGestureChanged (int parameterIndex, bool gestureIsStarting) override { } 230 | 231 | void timerCallback() override; 232 | 233 | void paint(juce::Graphics &g) override; 234 | 235 | void resized() override; 236 | 237 | private: 238 | EqualizerAudioProcessor& audioProcessor; 239 | juce::Atomic parametersChanged { false }; 240 | 241 | MonoChain monoChain; 242 | void updateChain(); 243 | juce::Image background; 244 | juce::Rectangle getRenderArea(); 245 | juce::Rectangle getAnalysisArea(); 246 | PathProducer leftPathProducer, rightPathProducer; 247 | }; 248 | 249 | class EqualizerAudioProcessorEditor : public juce::AudioProcessorEditor{ 250 | public: 251 | EqualizerAudioProcessorEditor (EqualizerAudioProcessor&); 252 | ~EqualizerAudioProcessorEditor() override; 253 | void paint (juce::Graphics&) override; 254 | void resized() override; 255 | private: 256 | EqualizerAudioProcessor& audioProcessor; 257 | RotarySliderWithLabels peakFreqSlider, 258 | peakGainSlider, 259 | peakQualitySlider, 260 | lowCutFreqSlider, 261 | highCutFreqSlider, 262 | lowCutSlopeSlider, 263 | highCutSlopeSlider; 264 | 265 | ResponseCurveComponent responseCurveComponent; 266 | 267 | using APVTS = juce::AudioProcessorValueTreeState; 268 | using Attachment = APVTS::SliderAttachment; 269 | 270 | Attachment peakFreqSliderAttachment, 271 | peakGainSliderAttachment, 272 | peakQualitySliderAttachment, 273 | lowCutFreqSliderAttachment, 274 | highCutFreqSliderAttachment, 275 | lowCutSlopeSliderAttachment, 276 | highCutSlopeSliderAttachment; 277 | juce::ToggleButton lowcutBypassButton, peakBypassButton, highcutBypassButton, analyzerEnabledButton; 278 | 279 | using ButtonAttachment = APVTS::ButtonAttachment; 280 | ButtonAttachment lowcutBypassButtonAttachment, 281 | peakBypassButtonAttachment, 282 | highcutBypassButtonAttachment, 283 | analyzerEnabledButtonAttachment; 284 | 285 | std::vector getComps(); 286 | LookAndFeel lnf; 287 | 288 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EqualizerAudioProcessorEditor) 289 | }; 290 | -------------------------------------------------------------------------------- /Equalizer/Source/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "PluginEditor.h" 11 | 12 | //============================================================================== 13 | EqualizerAudioProcessor::EqualizerAudioProcessor() 14 | #ifndef JucePlugin_PreferredChannelConfigurations 15 | : AudioProcessor (BusesProperties() 16 | #if ! JucePlugin_IsMidiEffect 17 | #if ! JucePlugin_IsSynth 18 | .withInput ("Input", juce::AudioChannelSet::stereo(), true) 19 | #endif 20 | .withOutput ("Output", juce::AudioChannelSet::stereo(), true) 21 | #endif 22 | ) 23 | #endif 24 | { 25 | } 26 | 27 | EqualizerAudioProcessor::~EqualizerAudioProcessor() 28 | { 29 | } 30 | 31 | //============================================================================== 32 | const juce::String EqualizerAudioProcessor::getName() const 33 | { 34 | return JucePlugin_Name; 35 | } 36 | 37 | bool EqualizerAudioProcessor::acceptsMidi() const 38 | { 39 | #if JucePlugin_WantsMidiInput 40 | return true; 41 | #else 42 | return false; 43 | #endif 44 | } 45 | 46 | bool EqualizerAudioProcessor::producesMidi() const 47 | { 48 | #if JucePlugin_ProducesMidiOutput 49 | return true; 50 | #else 51 | return false; 52 | #endif 53 | } 54 | 55 | bool EqualizerAudioProcessor::isMidiEffect() const 56 | { 57 | #if JucePlugin_IsMidiEffect 58 | return true; 59 | #else 60 | return false; 61 | #endif 62 | } 63 | 64 | double EqualizerAudioProcessor::getTailLengthSeconds() const 65 | { 66 | return 0.0; 67 | } 68 | 69 | int EqualizerAudioProcessor::getNumPrograms() 70 | { 71 | return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, 72 | // so this should be at least 1, even if you're not really implementing programs. 73 | } 74 | 75 | int EqualizerAudioProcessor::getCurrentProgram() 76 | { 77 | return 0; 78 | } 79 | 80 | void EqualizerAudioProcessor::setCurrentProgram (int index) 81 | { 82 | } 83 | 84 | const juce::String EqualizerAudioProcessor::getProgramName (int index) 85 | { 86 | return {}; 87 | } 88 | 89 | void EqualizerAudioProcessor::changeProgramName (int index, const juce::String& newName) 90 | { 91 | } 92 | 93 | //============================================================================== 94 | void EqualizerAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) 95 | { 96 | juce::dsp::ProcessSpec spec; 97 | spec.maximumBlockSize = samplesPerBlock; 98 | spec.numChannels = 1; 99 | spec.sampleRate = sampleRate; 100 | leftChain.prepare(spec); 101 | rightChain.prepare(spec); 102 | updateFilters(); 103 | leftChannelFifo.prepare(samplesPerBlock); 104 | rightChannelFifo.prepare(samplesPerBlock); 105 | osc.initialise([](float x) { return std::sin(x); }); 106 | 107 | spec.numChannels = getTotalNumOutputChannels(); 108 | osc.prepare(spec); 109 | osc.setFrequency(440); 110 | 111 | } 112 | 113 | void EqualizerAudioProcessor::releaseResources() 114 | { 115 | // When playback stops, you can use this as an opportunity to free up any 116 | // spare memory, etc. 117 | } 118 | 119 | #ifndef JucePlugin_PreferredChannelConfigurations 120 | bool EqualizerAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const 121 | { 122 | #if JucePlugin_IsMidiEffect 123 | juce::ignoreUnused (layouts); 124 | return true; 125 | #else 126 | // This is the place where you check if the layout is supported. 127 | // In this template code we only support mono or stereo. 128 | // Some plugin hosts, such as certain GarageBand versions, will only 129 | // load plugins that support stereo bus layouts. 130 | if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() 131 | && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) 132 | return false; 133 | 134 | // This checks if the input layout matches the output layout 135 | #if ! JucePlugin_IsSynth 136 | if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) 137 | return false; 138 | #endif 139 | 140 | return true; 141 | #endif 142 | } 143 | #endif 144 | 145 | void EqualizerAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) 146 | { 147 | juce::ScopedNoDenormals noDenormals; 148 | auto totalNumInputChannels = getTotalNumInputChannels(); 149 | auto totalNumOutputChannels = getTotalNumOutputChannels(); 150 | for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) 151 | buffer.clear (i, 0, buffer.getNumSamples()); 152 | 153 | updateFilters(); 154 | 155 | juce::dsp::AudioBlock block(buffer); 156 | 157 | auto leftBlock = block.getSingleChannelBlock(0); 158 | auto rightBlock = block.getSingleChannelBlock(1); 159 | 160 | juce::dsp::ProcessContextReplacing leftContext(leftBlock); 161 | juce::dsp::ProcessContextReplacing rightContext(rightBlock); 162 | 163 | leftChain.process(leftContext); 164 | rightChain.process(rightContext); 165 | leftChannelFifo.update(buffer); 166 | rightChannelFifo.update(buffer); 167 | 168 | 169 | } 170 | 171 | //============================================================================== 172 | bool EqualizerAudioProcessor::hasEditor() const 173 | { 174 | return true; // (change this to false if you choose to not supply an editor) 175 | } 176 | 177 | juce::AudioProcessorEditor* EqualizerAudioProcessor::createEditor() 178 | { 179 | return new EqualizerAudioProcessorEditor (*this); 180 | //return new juce::GenericAudioProcessorEditor(*this); 181 | } 182 | 183 | //============================================================================== 184 | void EqualizerAudioProcessor::getStateInformation (juce::MemoryBlock& destData) 185 | { 186 | // You should use this method to store your parameters in the memory block. 187 | // You could do that either as raw data, or use the XML or ValueTree classes 188 | // as intermediaries to make it easy to save and load complex data. 189 | juce::MemoryOutputStream mos(destData, true); 190 | apvts.state.writeToStream(mos); 191 | } 192 | 193 | void EqualizerAudioProcessor::setStateInformation (const void* data, int sizeInBytes) 194 | { 195 | // You should use this method to restore your parameters from this memory block, 196 | // whose contents will have been created by the getStateInformation() call. 197 | auto tree = juce::ValueTree::readFromData(data, sizeInBytes); 198 | if (tree.isValid()) { 199 | apvts.replaceState(tree); 200 | updateFilters(); 201 | } 202 | } 203 | 204 | ChainSettings getChainSettings(juce::AudioProcessorValueTreeState& apvts) 205 | { 206 | ChainSettings settings; 207 | settings.lowCutFreq = apvts.getRawParameterValue("LowCut Freq")->load(); 208 | settings.highCutFreq = apvts.getRawParameterValue("HighCut Freq")->load(); 209 | settings.peakFreq = apvts.getRawParameterValue("Peak Freq")->load(); 210 | settings.peakGainInDecibels = apvts.getRawParameterValue("Peak Gain")->load(); 211 | settings.peakQuality = apvts.getRawParameterValue("Peak Quality")->load(); 212 | settings.lowCutSlope = static_cast(apvts.getRawParameterValue("LowCut Slope")->load()); 213 | settings.highCutSlope = static_cast(apvts.getRawParameterValue("HighCut Slope")->load()); 214 | settings.lowCutBypassed = apvts.getRawParameterValue("LowCut Bypassed")->load() > 0.5f; 215 | settings.peakBypassed = apvts.getRawParameterValue("Peak Bypassed")->load() > 0.5f; 216 | settings.highCutBypassed = apvts.getRawParameterValue("HighCut Bypassed")->load() > 0.5f; 217 | return settings; 218 | } 219 | 220 | Coefficients makePeakFilter(const ChainSettings &chainSettings, double sampleRate){ 221 | return juce::dsp::IIR::Coefficients::makePeakFilter(sampleRate, chainSettings.peakFreq, chainSettings.peakQuality, juce::Decibels::decibelsToGain(chainSettings.peakGainInDecibels)); 222 | } 223 | 224 | void EqualizerAudioProcessor::updatePeakFilter(const ChainSettings &chainSettings){ 225 | auto peakCoefficients = makePeakFilter(chainSettings, getSampleRate()); 226 | leftChain.setBypassed(chainSettings.peakBypassed); 227 | rightChain.setBypassed(chainSettings.peakBypassed); 228 | updateCoefficients(leftChain.get().coefficients, peakCoefficients); 229 | updateCoefficients(rightChain.get().coefficients, peakCoefficients); 230 | 231 | } 232 | 233 | void updateCoefficients(Coefficients &old, const Coefficients &replacements){ 234 | 235 | *old = *replacements; 236 | } 237 | 238 | void EqualizerAudioProcessor::updateLowCutFilters(const ChainSettings &chainSettings){ 239 | auto cutCoefficients = makeLowCutFilter(chainSettings, getSampleRate()); 240 | auto& leftLowCut = leftChain.get(); 241 | auto& rightLowCut = rightChain.get(); 242 | leftChain.setBypassed(chainSettings.lowCutBypassed); 243 | rightChain.setBypassed(chainSettings.lowCutBypassed); 244 | updateCutFilter(leftLowCut, cutCoefficients, chainSettings.lowCutSlope); 245 | updateCutFilter(rightLowCut, cutCoefficients, chainSettings.lowCutSlope); 246 | } 247 | 248 | void EqualizerAudioProcessor::updateHighCutFilters(const ChainSettings &chainSettings){ 249 | auto highCutCoefficients = makeHighCutFilter(chainSettings, getSampleRate()); 250 | auto& leftHighCut = leftChain.get(); 251 | auto& rightHighCut = rightChain.get(); 252 | leftChain.setBypassed(chainSettings.highCutBypassed); 253 | rightChain.setBypassed(chainSettings.highCutBypassed); 254 | updateCutFilter(leftHighCut, highCutCoefficients, chainSettings.highCutSlope); 255 | updateCutFilter(rightHighCut, highCutCoefficients, chainSettings.highCutSlope); 256 | } 257 | 258 | void EqualizerAudioProcessor::updateFilters(){ 259 | auto chainSettings = getChainSettings(apvts); 260 | updateLowCutFilters(chainSettings); 261 | updatePeakFilter(chainSettings); 262 | updateHighCutFilters(chainSettings); 263 | } 264 | juce::AudioProcessorValueTreeState::ParameterLayout EqualizerAudioProcessor::createParameterLayout() 265 | { 266 | juce::AudioProcessorValueTreeState::ParameterLayout layout; 267 | 268 | layout.add(std::make_unique("LowCut Freq", 269 | "LowCut Freq", 270 | juce::NormalisableRange(20.f, 20000.f, 1.f, 0.25f), 271 | 20.f)); 272 | 273 | layout.add(std::make_unique("HighCut Freq", 274 | "HighCut Freq", 275 | juce::NormalisableRange(20.f, 20000.f, 1.f, 0.25f), 276 | 20000.f)); 277 | 278 | layout.add(std::make_unique("Peak Freq", 279 | "Peak Freq", 280 | juce::NormalisableRange(20.f, 20000.f, 1.f, 0.25f), 281 | 750.f)); 282 | 283 | layout.add(std::make_unique("Peak Gain", 284 | "Peak Gain", 285 | juce::NormalisableRange(-24.f, 24.f, 0.5f, 1.f), 286 | 0.0f)); 287 | 288 | layout.add(std::make_unique("Peak Quality", 289 | "Peak Quality", 290 | juce::NormalisableRange(0.1f, 10.f, 0.05f, 1.f), 291 | 1.f)); 292 | 293 | juce::StringArray stringArray; 294 | for( int i = 0; i < 4; ++i ) 295 | { 296 | juce::String str; 297 | str << (12 + i*12); 298 | str << " db/Oct"; 299 | stringArray.add(str); 300 | } 301 | 302 | layout.add(std::make_unique("LowCut Slope", "LowCut Slope", stringArray, 0)); 303 | layout.add(std::make_unique("HighCut Slope", "HighCut Slope", stringArray, 0)); 304 | layout.add(std::make_unique("LowCut Bypassed", "LowCut Bypassed", false)); 305 | layout.add(std::make_unique("Peak Bypassed", "Peak Bypassed", false)); 306 | layout.add(std::make_unique("HighCut Bypassed", "HighCut Bypassed", false)); 307 | layout.add(std::make_unique("Analyzer Enabled", "Analyzer Enabled", true)); 308 | return layout; 309 | } 310 | //============================================================================== 311 | // This creates new instances of the plugin.. 312 | juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() 313 | { 314 | return new EqualizerAudioProcessor(); 315 | } 316 | -------------------------------------------------------------------------------- /Equalizer/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include 14 | template 15 | struct Fifo 16 | { 17 | void prepare(int numChannels, int numSamples) 18 | { 19 | static_assert( std::is_same_v>, 20 | "prepare(numChannels, numSamples) should only be used when the Fifo is holding juce::AudioBuffer"); 21 | for( auto& buffer : buffers) 22 | { 23 | buffer.setSize(numChannels, 24 | numSamples, 25 | false, //clear everything? 26 | true, //including the extra space? 27 | true); //avoid reallocating if you can? 28 | buffer.clear(); 29 | } 30 | } 31 | 32 | void prepare(size_t numElements) 33 | { 34 | static_assert( std::is_same_v>, 35 | "prepare(numElements) should only be used when the Fifo is holding std::vector"); 36 | for( auto& buffer : buffers ) 37 | { 38 | buffer.clear(); 39 | buffer.resize(numElements, 0); 40 | } 41 | } 42 | 43 | bool push(const T& t) 44 | { 45 | auto write = fifo.write(1); 46 | if( write.blockSize1 > 0 ) 47 | { 48 | buffers[write.startIndex1] = t; 49 | return true; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | bool pull(T& t) 56 | { 57 | auto read = fifo.read(1); 58 | if( read.blockSize1 > 0 ) 59 | { 60 | t = buffers[read.startIndex1]; 61 | return true; 62 | } 63 | 64 | return false; 65 | } 66 | 67 | int getNumAvailableForReading() const 68 | { 69 | return fifo.getNumReady(); 70 | } 71 | private: 72 | static constexpr int Capacity = 30; 73 | std::array buffers; 74 | juce::AbstractFifo fifo {Capacity}; 75 | }; 76 | 77 | enum Channel 78 | { 79 | Right, //effectively 0 80 | Left //effectively 1 81 | }; 82 | 83 | template 84 | struct SingleChannelSampleFifo 85 | { 86 | SingleChannelSampleFifo(Channel ch) : channelToUse(ch) 87 | { 88 | prepared.set(false); 89 | } 90 | 91 | void update(const BlockType& buffer) 92 | { 93 | jassert(prepared.get()); 94 | jassert(buffer.getNumChannels() > channelToUse ); 95 | auto* channelPtr = buffer.getReadPointer(channelToUse); 96 | 97 | for( int i = 0; i < buffer.getNumSamples(); ++i ) 98 | { 99 | pushNextSampleIntoFifo(channelPtr[i]); 100 | } 101 | } 102 | 103 | void prepare(int bufferSize) 104 | { 105 | prepared.set(false); 106 | size.set(bufferSize); 107 | 108 | bufferToFill.setSize(1, //channel 109 | bufferSize, //num samples 110 | false, //keepExistingContent 111 | true, //clear extra space 112 | true); //avoid reallocating 113 | audioBufferFifo.prepare(1, bufferSize); 114 | fifoIndex = 0; 115 | prepared.set(true); 116 | } 117 | //============================================================================== 118 | int getNumCompleteBuffersAvailable() const { return audioBufferFifo.getNumAvailableForReading(); } 119 | bool isPrepared() const { return prepared.get(); } 120 | int getSize() const { return size.get(); } 121 | //============================================================================== 122 | bool getAudioBuffer(BlockType& buf) { return audioBufferFifo.pull(buf); } 123 | private: 124 | Channel channelToUse; 125 | int fifoIndex = 0; 126 | Fifo audioBufferFifo; 127 | BlockType bufferToFill; 128 | juce::Atomic prepared = false; 129 | juce::Atomic size = 0; 130 | 131 | void pushNextSampleIntoFifo(float sample) 132 | { 133 | if (fifoIndex == bufferToFill.getNumSamples()) 134 | { 135 | auto ok = audioBufferFifo.push(bufferToFill); 136 | 137 | juce::ignoreUnused(ok); 138 | 139 | fifoIndex = 0; 140 | } 141 | 142 | bufferToFill.setSample(0, fifoIndex, sample); 143 | ++fifoIndex; 144 | } 145 | }; 146 | enum Slope 147 | { 148 | Slope_12, 149 | Slope_24, 150 | Slope_36, 151 | Slope_48 152 | }; 153 | 154 | 155 | struct ChainSettings 156 | { 157 | float peakFreq { 0 }, peakGainInDecibels { 0 }, peakQuality {1.f}; 158 | float lowCutFreq { 0 }, highCutFreq { 0 }; 159 | bool lowCutBypassed { false }, peakBypassed { false }, highCutBypassed { false }; 160 | Slope lowCutSlope { Slope::Slope_12 }, highCutSlope { Slope::Slope_12 }; 161 | }; 162 | 163 | ChainSettings getChainSettings(juce::AudioProcessorValueTreeState& apvts); 164 | 165 | using Filter = juce::dsp::IIR::Filter; 166 | using CutFilter = juce::dsp::ProcessorChain; 167 | using MonoChain = juce::dsp::ProcessorChain; 168 | 169 | enum ChainPositions { 170 | LowCut, 171 | Peak, 172 | HighCut 173 | }; 174 | 175 | using Coefficients = Filter::CoefficientsPtr; 176 | void updateCoefficients(Coefficients &old, const Coefficients& replacements); 177 | 178 | Coefficients makePeakFilter(const ChainSettings &chainSettings, double sampleRate); 179 | 180 | template 181 | void update(ChainType& chain, const CoefficientType& coefficients){ 182 | updateCoefficients(chain.template get().coefficients, coefficients[Index]); 183 | chain.template setBypassed(false); 184 | } 185 | 186 | template 187 | void updateCutFilter(ChainType& chain, 188 | const CoefficientType& coefficients, 189 | const Slope &slope) 190 | { 191 | chain.template setBypassed<0>(true); 192 | chain.template setBypassed<1>(true); 193 | chain.template setBypassed<2>(true); 194 | chain.template setBypassed<3>(true); 195 | 196 | switch( slope ) 197 | { 198 | case Slope_12: 199 | { 200 | update<0>(chain, coefficients); 201 | break; 202 | } 203 | case Slope_24: 204 | { 205 | update<1>(chain, coefficients); 206 | break; 207 | } 208 | case Slope_36: 209 | { 210 | update<2>(chain, coefficients); 211 | break; 212 | } 213 | case Slope_48: 214 | { 215 | update<3>(chain, coefficients); 216 | break; 217 | } 218 | } 219 | } 220 | 221 | inline auto makeLowCutFilter(const ChainSettings &chainSettings, double sampleRate ){ 222 | return juce::dsp::FilterDesign::designIIRHighpassHighOrderButterworthMethod(chainSettings.lowCutFreq, 223 | sampleRate, 224 | 2 * (chainSettings.lowCutSlope + 1)); 225 | } 226 | inline auto makeHighCutFilter(const ChainSettings &chainSettings, double sampleRate ){ 227 | return juce::dsp::FilterDesign::designIIRLowpassHighOrderButterworthMethod(chainSettings.highCutFreq, 228 | sampleRate, 229 | 2 * (chainSettings.highCutSlope + 1)); 230 | } 231 | 232 | //============================================================================== 233 | /** 234 | */ 235 | class EqualizerAudioProcessor : public juce::AudioProcessor 236 | { 237 | public: 238 | //============================================================================== 239 | EqualizerAudioProcessor(); 240 | ~EqualizerAudioProcessor() override; 241 | 242 | //============================================================================== 243 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 244 | void releaseResources() override; 245 | 246 | #ifndef JucePlugin_PreferredChannelConfigurations 247 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 248 | #endif 249 | 250 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 251 | 252 | //============================================================================== 253 | juce::AudioProcessorEditor* createEditor() override; 254 | bool hasEditor() const override; 255 | 256 | //============================================================================== 257 | const juce::String getName() const override; 258 | 259 | bool acceptsMidi() const override; 260 | bool producesMidi() const override; 261 | bool isMidiEffect() const override; 262 | double getTailLengthSeconds() const override; 263 | 264 | //============================================================================== 265 | int getNumPrograms() override; 266 | int getCurrentProgram() override; 267 | void setCurrentProgram (int index) override; 268 | const juce::String getProgramName (int index) override; 269 | void changeProgramName (int index, const juce::String& newName) override; 270 | 271 | //============================================================================== 272 | void getStateInformation (juce::MemoryBlock& destData) override; 273 | void setStateInformation (const void* data, int sizeInBytes) override; 274 | 275 | static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 276 | juce::AudioProcessorValueTreeState apvts {*this, nullptr, "Parameters", createParameterLayout()}; 277 | using BlockType = juce::AudioBuffer; 278 | SingleChannelSampleFifo leftChannelFifo { Channel::Left }; 279 | SingleChannelSampleFifo rightChannelFifo { Channel::Right }; 280 | 281 | private: 282 | MonoChain leftChain, rightChain; 283 | void updatePeakFilter(const ChainSettings& chainSettings); 284 | void updateLowCutFilters(const ChainSettings &chainSettings); 285 | void updateHighCutFilters(const ChainSettings &chainSettings); 286 | void updateFilters(); 287 | juce::dsp::Oscillator osc; 288 | //============================================================================== 289 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EqualizerAudioProcessor) 290 | }; 291 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C-Audio-Plugin -------------------------------------------------------------------------------- /Unnamed.filtergraph: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 0. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 0. 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 39 | 0. 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 54 | 0. 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 66 | 70 | 191.VMjLgXK....O+fWarAhckI2bo8la8HRLt.iHfTlai8FYo41Y8HRUTYTK3HxO9.BOVMEUy.Ea0cVZtMEcgQWY9vSRC8Vav8lak4Fc9XCLt3hKt3hKt3hKt3hYRUUSTEETIckVwTjQisVTTgkdEYjKAQjYPQSPWgUdMcjKAQjct3hdA4hKt3hKt3hKtnTUv.UQAslXuk0UXoWUFE0YQcEV77RRC8Vav8lak4Fc9vyKVMEUy.Ea0cVZtMEcgQWY9.. 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 82 | 86 | 541.hAGaoMGcv.C1AHv.DTfAGfPBJr.Gc3wGgvUag4VclE1XzUmbkIGUjEFcg8EDarVPUAkbkMWYzAUYxMWZyQWYtQ2TzEFcksTY4c0b0IFc4AWYWYWYxMWZu4FUzkGbk8EDOXVZrUVKxUlYkIWYtMVYyQkag0VYRDFbvwFPgxvzM3vCPDQFXYTZrUlSg0VYWIUYmk1atMmWIM2TkwVYiQWYjYTZrU1WP7xKUMWYxM2KsUWa08BQkM2Zz8FbuzVcyk1XuDDbnUFdfP0co4FHs.BVzEFatzFbyDpDTOAEUXwEXjgFdIUYmk1atQzakMGSu8FbZIUYmk1atQTXzE1WP.QRyMUYrU1XzUFYRU1Yo8laZIUYmk1at4TXsUFBOAAb..............fv......................13FED..........TUUUUUUUUUUUUUUUUUUUUU.....UUUUUA......................................fOHv...........f6WL...9b.kfVNU1cfHUYmk1atkfDgYFbrAA.RDVcm4Vzf.gUFkFakARLXUkazkFcrUFY.f..Y.fI.rB.IAPT.jE.dA.b.TG.5Avd.zG.DBPi.TI.jBf0.fM.gC.7.re.NDPFAnQ.MFfiAjY.ZFvmADZ.lFPpA.K.......f.A.........fH..................P.4B 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | --------------------------------------------------------------------------------