|
23 | 23 |
|
24 | 24 | #include <yup_dsp/yup_dsp.h> |
25 | 25 | #include <yup_audio_formats/yup_audio_formats.h> |
| 26 | +#include <yup_audio_gui/yup_audio_gui.h> |
26 | 27 |
|
27 | 28 | #include <memory> |
28 | 29 | #include <random> |
|
31 | 32 |
|
32 | 33 | //============================================================================== |
33 | 34 |
|
34 | | -class CrossoverFrequencyResponseDisplay : public yup::Component |
35 | | -{ |
36 | | -public: |
37 | | - void updateResponse (const std::vector<yup::Point<double>>& lowData, |
38 | | - const std::vector<yup::Point<double>>& highData) |
39 | | - { |
40 | | - lowPassData = lowData; |
41 | | - highPassData = highData; |
42 | | - repaint(); |
43 | | - } |
44 | | - |
45 | | - void setCrossoverFrequency (double freq) |
46 | | - { |
47 | | - crossoverFreq = freq; |
48 | | - repaint(); |
49 | | - } |
50 | | - |
51 | | -private: |
52 | | - void paint (yup::Graphics& g) override |
53 | | - { |
54 | | - auto bounds = getLocalBounds(); |
55 | | - |
56 | | - // Background |
57 | | - g.setFillColor (yup::Color (0xFF1E1E1E)); |
58 | | - g.fillRect (bounds); |
59 | | - |
60 | | - // Reserve space for labels |
61 | | - auto titleBounds = bounds.removeFromTop (25); |
62 | | - titleBounds.removeFromLeft (5); |
63 | | - auto bottomLabelSpace = bounds.removeFromBottom (20); |
64 | | - auto leftLabelSpace = bounds.removeFromLeft (50); |
65 | | - leftLabelSpace.removeFromRight (5); |
66 | | - |
67 | | - // Grid |
68 | | - g.setStrokeColor (yup::Color (0xFF333333)); |
69 | | - g.setStrokeWidth (1.0f); |
70 | | - |
71 | | - // Frequency grid lines (logarithmic) |
72 | | - for (double freq : { 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0, 2000.0, 5000.0, 10000.0, 20000.0 }) |
73 | | - { |
74 | | - float x = frequencyToX (freq, bounds); |
75 | | - g.strokeLine ({ x, bounds.getY() }, { x, bounds.getBottom() }); |
76 | | - } |
77 | | - |
78 | | - // dB grid lines |
79 | | - for (double db : { -48.0, -36.0, -24.0, -12.0, -6.0, 0.0, 6.0 }) |
80 | | - { |
81 | | - float y = dbToY (db, bounds); |
82 | | - g.strokeLine ({ bounds.getX(), y }, { bounds.getRight(), y }); |
83 | | - } |
84 | | - |
85 | | - // Zero line |
86 | | - g.setStrokeColor (yup::Color (0xFF666666)); |
87 | | - g.setStrokeWidth (2.0f); |
88 | | - float y0 = dbToY (0.0, bounds); |
89 | | - g.strokeLine ({ bounds.getX(), y0 }, { bounds.getRight(), y0 }); |
90 | | - |
91 | | - // -6dB crossover line |
92 | | - g.setStrokeColor (yup::Color (0xFF444444)); |
93 | | - g.setStrokeWidth (1.0f); |
94 | | - float y6 = dbToY (-6.0, bounds); |
95 | | - g.strokeLine ({ bounds.getX(), y6 }, { bounds.getRight(), y6 }); |
96 | | - |
97 | | - // Crossover frequency line |
98 | | - if (crossoverFreq > 0) |
99 | | - { |
100 | | - g.setStrokeColor (yup::Color (0xFF888888)); |
101 | | - g.setStrokeWidth (1.0f); |
102 | | - float xCross = frequencyToX (crossoverFreq, bounds); |
103 | | - g.strokeLine ({ xCross, bounds.getY() }, { xCross, bounds.getBottom() }); |
104 | | - } |
105 | | - |
106 | | - // Plot frequency responses |
107 | | - if (! lowPassData.empty()) |
108 | | - { |
109 | | - // Low pass in blue |
110 | | - yup::Path lowPath; |
111 | | - bool firstPoint = true; |
112 | | - |
113 | | - g.setStrokeColor (yup::Color (0xFF4488FF)); |
114 | | - g.setStrokeWidth (2.0f); |
115 | | - |
116 | | - for (const auto& point : lowPassData) |
117 | | - { |
118 | | - float x = frequencyToX (point.getX(), bounds); |
119 | | - float y = dbToY (point.getY(), bounds); |
120 | | - |
121 | | - if (firstPoint) |
122 | | - { |
123 | | - lowPath.startNewSubPath (x, y); |
124 | | - firstPoint = false; |
125 | | - } |
126 | | - else |
127 | | - { |
128 | | - lowPath.lineTo (x, y); |
129 | | - } |
130 | | - } |
131 | | - |
132 | | - g.strokePath (lowPath); |
133 | | - } |
134 | | - |
135 | | - if (! highPassData.empty()) |
136 | | - { |
137 | | - // High pass in orange |
138 | | - yup::Path highPath; |
139 | | - bool firstPoint = true; |
140 | | - |
141 | | - g.setStrokeColor (yup::Color (0xFFFF8844)); |
142 | | - g.setStrokeWidth (2.0f); |
143 | | - |
144 | | - for (const auto& point : highPassData) |
145 | | - { |
146 | | - float x = frequencyToX (point.getX(), bounds); |
147 | | - float y = dbToY (point.getY(), bounds); |
148 | | - |
149 | | - if (firstPoint) |
150 | | - { |
151 | | - highPath.startNewSubPath (x, y); |
152 | | - firstPoint = false; |
153 | | - } |
154 | | - else |
155 | | - { |
156 | | - highPath.lineTo (x, y); |
157 | | - } |
158 | | - } |
159 | | - |
160 | | - g.strokePath (highPath); |
161 | | - } |
162 | | - |
163 | | - // Labels with smaller font |
164 | | - g.setFillColor (yup::Colors::white); |
165 | | - auto font = yup::ApplicationTheme::getGlobalTheme()->getDefaultFont().withHeight (10.0f); |
166 | | - |
167 | | - // Title (centered, leaving space for legend) |
168 | | - auto titleArea = titleBounds.removeFromLeft (titleBounds.getWidth() - 120); |
169 | | - g.fillFittedText ("Crossover Frequency Response", font.withHeight (12.0f), titleArea, yup::Justification::centerLeft); |
170 | | - |
171 | | - // Frequency labels |
172 | | - for (double freq : { 100.0, 1000.0, 10000.0 }) |
173 | | - { |
174 | | - float x = frequencyToX (freq, bounds); |
175 | | - yup::String label; |
176 | | - if (freq >= 1000.0) |
177 | | - label = yup::String (freq / 1000.0, 0) + "k"; |
178 | | - else |
179 | | - label = yup::String (static_cast<int> (freq)); |
180 | | - |
181 | | - auto labelBounds = yup::Rectangle<int> (static_cast<int> (x - 20), |
182 | | - bottomLabelSpace.getY(), |
183 | | - 40, |
184 | | - bottomLabelSpace.getHeight()); |
185 | | - g.fillFittedText (label, font, labelBounds, yup::Justification::center); |
186 | | - } |
187 | | - |
188 | | - // dB labels |
189 | | - g.setFillColor (yup::Colors::gray); |
190 | | - for (double db : { -24.0, -12.0, -6.0, 0.0 }) |
191 | | - { |
192 | | - float y = dbToY (db, bounds); |
193 | | - auto label = yup::String (static_cast<int> (db)) + " dB"; |
194 | | - g.fillFittedText (label, font, leftLabelSpace.withY (static_cast<int> (y - 8)).withHeight (16), yup::Justification::right); |
195 | | - } |
196 | | - |
197 | | - // Legend (on the right side of title area) |
198 | | - auto legendBounds = titleBounds; |
199 | | - legendBounds = legendBounds.withY (legendBounds.getY() + 4); |
200 | | - |
201 | | - g.setFillColor (yup::Color (0xFF4488FF)); |
202 | | - auto lowRect = legendBounds.removeFromLeft (15).reduced (2); |
203 | | - g.fillRect (lowRect); |
204 | | - g.setFillColor (yup::Colors::white); |
205 | | - g.fillFittedText ("Low", font, legendBounds.removeFromLeft (25), yup::Justification::left); |
206 | | - |
207 | | - g.setFillColor (yup::Color (0xFFFF8844)); |
208 | | - auto highRect = legendBounds.removeFromLeft (15).reduced (2); |
209 | | - g.fillRect (highRect); |
210 | | - g.setFillColor (yup::Colors::white); |
211 | | - g.fillFittedText ("High", font, legendBounds, yup::Justification::left); |
212 | | - } |
213 | | - |
214 | | - float frequencyToX (double freq, const yup::Rectangle<float>& bounds) const |
215 | | - { |
216 | | - if (freq <= 0) |
217 | | - return bounds.getX(); |
218 | | - |
219 | | - const double minLog = std::log10 (20.0); |
220 | | - const double maxLog = std::log10 (20000.0); |
221 | | - const double logFreq = std::log10 (freq); |
222 | | - const double normalised = (logFreq - minLog) / (maxLog - minLog); |
223 | | - |
224 | | - return bounds.getX() + static_cast<float> (normalised * bounds.getWidth()); |
225 | | - } |
226 | | - |
227 | | - float dbToY (double db, const yup::Rectangle<float>& bounds) const |
228 | | - { |
229 | | - const double minDb = -48.0; |
230 | | - const double maxDb = 12.0; |
231 | | - const double normalised = 1.0 - (db - minDb) / (maxDb - minDb); |
232 | | - |
233 | | - return bounds.getY() + static_cast<float> (normalised * bounds.getHeight()); |
234 | | - } |
235 | | - |
236 | | - std::vector<yup::Point<double>> lowPassData; |
237 | | - std::vector<yup::Point<double>> highPassData; |
238 | | - double crossoverFreq = 1000.0; |
239 | | -}; |
240 | | - |
241 | 35 | //============================================================================== |
242 | 36 |
|
243 | 37 | class CrossoverDemo : public yup::Component |
@@ -518,7 +312,7 @@ class CrossoverDemo : public yup::Component |
518 | 312 | freqSlider.onValueChanged = [this] (float value) |
519 | 313 | { |
520 | 314 | crossoverFreq.setTargetValue (value); |
521 | | - frequencyDisplay.setCrossoverFrequency (value); |
| 315 | + setCrossoverFrequency (value); |
522 | 316 | }; |
523 | 317 | addAndMakeVisible (freqSlider); |
524 | 318 |
|
@@ -554,7 +348,8 @@ class CrossoverDemo : public yup::Component |
554 | 348 | }; |
555 | 349 | addAndMakeVisible (highGainSlider); |
556 | 350 |
|
557 | | - // Frequency display |
| 351 | + // Configure frequency display (CartesianPlane) |
| 352 | + setupFrequencyDisplay(); |
558 | 353 | addAndMakeVisible (frequencyDisplay); |
559 | 354 |
|
560 | 355 | // Initialize frequency response |
@@ -616,7 +411,60 @@ class CrossoverDemo : public yup::Component |
616 | 411 | highResponse.emplace_back (freq, highDb); |
617 | 412 | } |
618 | 413 |
|
619 | | - frequencyDisplay.updateResponse (lowResponse, highResponse); |
| 414 | + // Update signals on the CartesianPlane |
| 415 | + frequencyDisplay.updateSignalData (lowPassSignalIndex, lowResponse); |
| 416 | + frequencyDisplay.updateSignalData (highPassSignalIndex, highResponse); |
| 417 | + } |
| 418 | + |
| 419 | + void setupFrequencyDisplay() |
| 420 | + { |
| 421 | + // Configure the CartesianPlane for frequency response display |
| 422 | + frequencyDisplay.setTitle ("Crossover Frequency Response"); |
| 423 | + |
| 424 | + // Set logarithmic X axis (frequency) and linear Y axis (dB) |
| 425 | + frequencyDisplay.setXRange (20.0, 20000.0); |
| 426 | + frequencyDisplay.setXScaleType (yup::CartesianPlane::AxisScaleType::logarithmic); |
| 427 | + frequencyDisplay.setYRange (-48.0, 12.0); |
| 428 | + frequencyDisplay.setYScaleType (yup::CartesianPlane::AxisScaleType::linear); |
| 429 | + |
| 430 | + // Set margins |
| 431 | + frequencyDisplay.setMargins (25, 50, 20, 20); |
| 432 | + |
| 433 | + // Add vertical grid lines (frequency) |
| 434 | + frequencyDisplay.setVerticalGridLines ({ 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0, 2000.0, 5000.0, 10000.0, 20000.0 }); |
| 435 | + |
| 436 | + // Add horizontal grid lines (dB) |
| 437 | + frequencyDisplay.setHorizontalGridLines ({ -48.0, -36.0, -24.0, -12.0, -6.0, 0.0, 6.0 }); |
| 438 | + |
| 439 | + // Emphasize special lines |
| 440 | + frequencyDisplay.addHorizontalGridLine (0.0, yup::Color (0xFF666666), 2.0f, true); // 0dB line |
| 441 | + frequencyDisplay.addHorizontalGridLine (-6.0, yup::Color (0xFF444444), 1.0f, true); // -6dB crossover line |
| 442 | + |
| 443 | + // Add axis labels |
| 444 | + frequencyDisplay.setXAxisLabels ({ 100.0, 1000.0, 10000.0 }); |
| 445 | + frequencyDisplay.setYAxisLabels ({ -24.0, -12.0, -6.0, 0.0 }); |
| 446 | + |
| 447 | + // Add signals |
| 448 | + lowPassSignalIndex = frequencyDisplay.addSignal ("Low", yup::Color (0xFF4488FF), 2.0f); |
| 449 | + highPassSignalIndex = frequencyDisplay.addSignal ("High", yup::Color (0xFFFF8844), 2.0f); |
| 450 | + |
| 451 | + // Configure legend |
| 452 | + frequencyDisplay.setLegendVisible (true); |
| 453 | + frequencyDisplay.setLegendPosition ({ 0.9f, 0.1f }); |
| 454 | + |
| 455 | + // Set initial crossover frequency line |
| 456 | + setCrossoverFrequency (1000.0); |
| 457 | + } |
| 458 | + |
| 459 | + void setCrossoverFrequency (double freq) |
| 460 | + { |
| 461 | + currentCrossoverFreq = freq; |
| 462 | + |
| 463 | + // Update crossover frequency line |
| 464 | + frequencyDisplay.clearVerticalGridLines(); |
| 465 | + frequencyDisplay.setVerticalGridLines ({ 20.0, 50.0, 100.0, 200.0, 500.0, 1000.0, 2000.0, 5000.0, 10000.0, 20000.0 }); |
| 466 | + if (freq > 0) |
| 467 | + frequencyDisplay.addVerticalGridLine (freq, yup::Color (0xFF888888), 1.0f, true); |
620 | 468 | } |
621 | 469 |
|
622 | 470 | // Audio |
@@ -645,5 +493,10 @@ class CrossoverDemo : public yup::Component |
645 | 493 | yup::Slider lowGainSlider; |
646 | 494 | yup::Label highGainLabel; |
647 | 495 | yup::Slider highGainSlider; |
648 | | - CrossoverFrequencyResponseDisplay frequencyDisplay; |
| 496 | + yup::CartesianPlane frequencyDisplay; |
| 497 | + |
| 498 | + // Signal indices for CartesianPlane |
| 499 | + int lowPassSignalIndex = -1; |
| 500 | + int highPassSignalIndex = -1; |
| 501 | + double currentCrossoverFreq = 1000.0; |
649 | 502 | }; |
0 commit comments