├── .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 |
--------------------------------------------------------------------------------