├── .clang-format
├── .gitignore
├── Doxyfile
├── LICENSE
├── README.md
├── meter
├── sd_MeterChannel.cpp
├── sd_MeterChannel.h
├── sd_MeterFader.cpp
├── sd_MeterFader.h
├── sd_MeterHeader.cpp
├── sd_MeterHeader.h
├── sd_MeterHelpers.cpp
├── sd_MeterHelpers.h
├── sd_MeterLevel.cpp
├── sd_MeterLevel.h
├── sd_MeterSegment.cpp
├── sd_MeterSegment.h
├── sd_MetersComponent.cpp
└── sd_MetersComponent.h
├── sound_meter.cpp
└── sound_meter.h
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | BasedOnStyle: WebKit
3 | AccessModifierOffset: '-4'
4 | AlignAfterOpenBracket: Align
5 | AlignConsecutiveMacros: 'true'
6 | AlignConsecutiveAssignments: 'true'
7 | AlignConsecutiveDeclarations: 'true'
8 | AlignEscapedNewlines: Right
9 | AlignOperands: 'true'
10 | AlignTrailingComments: 'true'
11 | AllowAllConstructorInitializersOnNextLine: 'true'
12 | AllowAllParametersOfDeclarationOnNextLine: 'false'
13 | AllowShortBlocksOnASingleLine: 'false'
14 | AllowShortCaseLabelsOnASingleLine: 'true'
15 | AllowShortFunctionsOnASingleLine: Inline
16 | AllowShortIfStatementsOnASingleLine: Never
17 | AllowShortLoopsOnASingleLine: 'false'
18 | AlwaysBreakAfterDefinitionReturnType: None
19 | AlwaysBreakAfterReturnType: None
20 | AlwaysBreakBeforeMultilineStrings: 'false'
21 | AlwaysBreakTemplateDeclarations: 'Yes'
22 | BinPackArguments: 'true'
23 | BinPackParameters: 'true'
24 | BreakAfterJavaFieldAnnotations: 'false'
25 | BreakBeforeBinaryOperators: NonAssignment
26 | BreakBeforeBraces: Allman
27 | BreakBeforeTernaryOperators: 'false'
28 | BreakConstructorInitializers: BeforeColon
29 | BreakInheritanceList: BeforeComma
30 | BreakStringLiterals: 'true'
31 | ColumnLimit: '160'
32 | CompactNamespaces: 'true'
33 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
34 | ConstructorInitializerIndentWidth: '2'
35 | ContinuationIndentWidth: '2'
36 | Cpp11BracedListStyle: 'false'
37 | DerivePointerAlignment: 'false'
38 | DisableFormat: 'false'
39 | ExperimentalAutoDetectBinPacking: 'false'
40 | FixNamespaceComments: 'true'
41 | IncludeBlocks: Regroup
42 | IndentCaseLabels: 'true'
43 | IndentPPDirectives: BeforeHash
44 | IndentWidth: '4'
45 | IndentWrappedFunctionNames: 'true'
46 | KeepEmptyLinesAtTheStartOfBlocks: 'true'
47 | Language: Cpp
48 | MaxEmptyLinesToKeep: '2'
49 | NamespaceIndentation: None
50 | PenaltyBreakBeforeFirstCallParameter: '100'
51 | PenaltyBreakComment: '100'
52 | PenaltyBreakFirstLessLess: '0'
53 | PenaltyBreakString: '100'
54 | PenaltyExcessCharacter: '1'
55 | PenaltyReturnTypeOnItsOwnLine: '20'
56 | PointerAlignment: Left
57 | ReflowComments: 'false'
58 | SortIncludes: 'true'
59 | SortUsingDeclarations: 'true'
60 | SpaceAfterCStyleCast: 'true'
61 | SpaceAfterLogicalNot: 'false'
62 | SpaceAfterTemplateKeyword: 'false'
63 | SpaceBeforeAssignmentOperators: 'true'
64 | SpaceBeforeCpp11BracedList: 'true'
65 | SpaceBeforeCtorInitializerColon: 'true'
66 | SpaceBeforeInheritanceColon: 'true'
67 | SpaceBeforeParens: NonEmptyParentheses
68 | SpaceBeforeRangeBasedForLoopColon: 'false'
69 | SpaceInEmptyParentheses: 'false'
70 | SpacesBeforeTrailingComments: '2'
71 | SpacesInAngles: 'false'
72 | SpacesInCStyleCastParentheses: 'false'
73 | SpacesInContainerLiterals: 'false'
74 | SpacesInSquareBrackets: 'false'
75 | Standard: Auto
76 | TabWidth: '4'
77 | UseTab: Never
78 |
79 | ...
80 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Sound Development
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sound Meter
2 | *by Marcel Huibers | [Sound Development](https://www.sounddevelopment.nl) 2025 | Published under the [MIT License](https://en.wikipedia.org/wiki/MIT_License)*
3 |
4 |
**Peak meter JUCE module with optional fader overlay.
**
5 | Used in the Lobith Audio's waveform audio player [PlayerSpecz](https://www.lobith-audio.com/playerspecz/).
6 | Compatible with JUCE 8. For older JUCE version go check out the [JUCE7 branch](https://github.com/SoundDevelopment/sound_meter/tree/juce7).
7 |
8 | ### Features:
9 | - Fully **resize-able**.
10 | - **Adaptive**. Will show header, value, tick-marks only when there is space available.
11 | - Unlimited number of user defineable **segments**, with custom ranges and configurable colours or gradients.
12 | - **Efficient**. Only redraws when needed.
13 | - Configurable meter **ballistics** (meter decay).
14 | - **Tick-marks** (dividing lines on the meter) at user specified levels.
15 | - Peak hold **indicator** and optional peak **value** readout.
16 | - Optional **label strip** next to the meters (which can double as master fader).
17 | - Optional **header** identifying the meter's name (set by user) or channel type.
18 | - Optional **fader** and mute button (in the header).
19 |
20 | ### **New: Meter Presets!**
21 | - Default (-60 dB - 0 dB)
22 | - SMPTE PPM (-44 dB - 0 dB)
23 | - EBU PPM (-30 dB - -10 dB)
24 | - Yamaha 60 Mizer (-60 dB - 0 dB)
25 | - Bob Katz K metering (K20, K14, K12)
26 | - Extended Bottom (-96 dB - 0 dB)
27 | - Extended Top (-50 dB - +20 dB)
28 | - Full Range (-96 dB - +100 dB)
29 |
30 | You can find the API documentation [here](https://www.sounddevelopment.nl/sd/resources/documentation/sound_meter/)...
31 |
32 | An example project, demonstrating sound_meter can be found [here](https://github.com/SoundDevelopment/sound_meter-example)...
33 |
34 | # Usage
35 |
36 | All classes are in the namespace `sd::SoundMeter` to avoid collisions. You can either prefix each symbol, or import the namespace.
37 |
38 | ### MetersComponent
39 |
40 | The [MetersComponent](https://www.sounddevelopment.nl/sd/resources/documentation/sound_meter/classsd_1_1SoundMeter_1_1MetersComponent.html) class creates and controls the meters.
41 | This would live in your **editor.h**.
42 | ```cpp
43 | private:
44 | sd::SoundMeter::MetersComponent m_meters;
45 | ```
46 |
47 |
48 | In the **constructor** you can specify a channel format with [setChannelFormat](https://www.sounddevelopment.nl/sd/resources/documentation/sound_meter/classsd_1_1SoundMeter_1_1MetersComponent.html#aea27fda8af5ec463436186e8fb3afd20) or set the nummer of channels with [setNumChannels](https://www.sounddevelopment.nl/sd/resources/documentation/sound_meter/classsd_1_1SoundMeter_1_1MetersComponent.html#a042d84e77a91f501c57377d461957e41):
49 | ```cpp
50 | m_meters.setChannelFormat (juce::AudioChannelSet::stereo());
51 | ```
52 | and configure it's options: (for all meter options, see [documentation](https://www.sounddevelopment.nl/sd/resources/documentation/sound_meter/structsd_1_1SoundMeter_1_1Options.html))
53 | ```cpp
54 | sd::SoundMeter::Options meterOptions;
55 | meterOptions.faderEnabled = true;
56 | meterOptions.headerEnabled = true;
57 | meterOptions.showTickMarks = true;
58 | m_meters.setOptions (meterOptions);
59 | ```
60 | and configure the segments:
61 | ```cpp
62 | std::vector segmentOptions =
63 | { // From bottom of the meter (0.0f) to the half. Displaying -60 dB up to -18 dB.
64 | { { -60.0f, -18.0f }, { 0.0f, 0.5f }, juce::Colours::green, juce::Colours::green },
65 | // From half of the meter to almost the top (0.9f). Displaying -18 dB up to -3 dB.
66 | { { -18.0f, -3.0f }, { 0.5f, 0.90f }, juce::Colours::green, juce::Colours::yellow },
67 | // From almost the top to the top of the meter (1.0f). Displaying -3 dB up to 0 dB.
68 | { { -3.0f, 0.0f }, { 0.90f, 1.0f }, juce::Colours::yellow, juce::Colours::red } };
69 | m_meters.setMeterSegments (segmentOptions);
70 | ```
71 |
72 | Finally (still in the **constructor**) we add the component and make it visible:
73 | ```cpp
74 | addAndMakeVisible (m_meters);
75 | ```
76 |
77 | In the `resized()` method, you set the bounds (left, right, width, height) of the meters:
78 | ```cpp
79 | m_meters.setBounds (getLocalBounds());
80 | ```
81 |
82 | ### Getting the levels
83 |
84 | Basically everything is set up now.
85 | All left to do now is to supply the meter with the level with the method:
86 | `setInputLevel (int channel, float value);`
87 |
88 | The recommended way to get the levels from the audio processor is to let the editor poll the audio processor (with a timer for instance).
89 | Preferably it would poll atomic values in the audio processor for thread safety.
90 |
91 | A fully working example demonstrating this can be found [here](https://github.com/SoundDevelopment/sound_meter-example)...
92 |
93 |
94 |
95 | -----
96 | *Sound Development 2025*
97 |
--------------------------------------------------------------------------------
/meter/sd_MeterChannel.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2021 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #include "sd_MeterChannel.h"
34 |
35 | namespace sd
36 | {
37 | namespace SoundMeter
38 | {
39 | MeterChannel::MeterChannel() noexcept
40 | {
41 | #if SDTK_ENABLE_FADER
42 | m_fader.onFaderValueChanged = [this]() { notifyParent(); };
43 | #endif
44 | }
45 | //==============================================================================
46 |
47 | MeterChannel::MeterChannel (const Options& meterOptions, Padding padding, const juce::String& channelName, bool isLabelStrip /*= false*/,
48 | ChannelType channelType /*= ChannelType::unknown*/)
49 | : MeterChannel()
50 | {
51 | setName (channelName);
52 |
53 | #if JUCE_VERSION < 0x080000
54 | setBufferedToImage (true); // Less rendering. But rendering takes slightly longer.
55 | #endif
56 |
57 | setChannelType (channelType);
58 | setOptions (meterOptions);
59 | setIsLabelStrip (isLabelStrip);
60 | setPadding (padding);
61 | }
62 | //==============================================================================
63 |
64 | void MeterChannel::reset()
65 | {
66 | m_level.reset();
67 | setDirty();
68 | }
69 | //==============================================================================
70 |
71 | #if SDTK_ENABLE_FADER
72 |
73 | MeterChannel::~MeterChannel()
74 | {
75 | onFaderMove = nullptr;
76 | onMixerReset = nullptr;
77 | onFaderMove = nullptr;
78 | }
79 | //==============================================================================
80 |
81 | void MeterChannel::flashFader()
82 | {
83 | m_fader.flash();
84 | addDirty (m_fader.getBounds());
85 | }
86 | //==============================================================================
87 |
88 | void MeterChannel::notifyParent()
89 | {
90 | if (onFaderMove)
91 | onFaderMove (this);
92 | }
93 | //==============================================================================
94 |
95 | #endif /* SDTK_ENABLE_FADER */
96 | //==============================================================================
97 |
98 | bool MeterChannel::autoSetMinimalMode (int proposedWidth, int proposedHeight)
99 | {
100 | bool minimalMode = !nameFits ("-9.99", proposedWidth);
101 | if (proposedWidth < Constants::kMinModeWidthThreshold)
102 | minimalMode = true;
103 | if (proposedHeight < Constants::kMinModeHeightThreshold)
104 | minimalMode = true;
105 |
106 | setMinimalMode (minimalMode);
107 |
108 | return m_minimalMode;
109 | }
110 | //==============================================================================
111 |
112 | void MeterChannel::setMinimalMode (bool minimalMode)
113 | {
114 | if (!m_meterOptions.useMinimalMode)
115 | return;
116 |
117 | m_minimalMode = minimalMode;
118 | m_level.setMinimalMode (m_minimalMode);
119 | }
120 | //==============================================================================
121 |
122 | void MeterChannel::showHeader (bool headerVisible)
123 | {
124 | if (m_meterOptions.headerEnabled == headerVisible)
125 | return;
126 |
127 | m_meterOptions.headerEnabled = headerVisible;
128 |
129 | resized();
130 | addDirty (m_header.getBounds());
131 | }
132 | //==============================================================================
133 |
134 | void MeterChannel::setOptions (const Options& meterOptions)
135 | {
136 | m_meterOptions = meterOptions;
137 |
138 | setVisible (meterOptions.enabled);
139 | setEnabled (meterOptions.enabled);
140 |
141 | m_level.setMeterOptions (meterOptions);
142 |
143 | showHeader (meterOptions.headerEnabled);
144 |
145 | #if SDTK_ENABLE_FADER
146 | enableFader (meterOptions.faderEnabled);
147 | #endif
148 |
149 | refresh (true); // TODO: - [mh] Change this to refresh(true)?
150 | resized();
151 | }
152 | //==============================================================================
153 |
154 | void MeterChannel::lookAndFeelChanged()
155 | {
156 | visibilityChanged();
157 | }
158 | //==============================================================================
159 |
160 | void MeterChannel::visibilityChanged()
161 | {
162 | setColours();
163 | }
164 | //==============================================================================
165 |
166 | juce::Colour MeterChannel::getColourFromLnf (int colourId, const juce::Colour& fallbackColour) const
167 | {
168 | if (isColourSpecified (colourId))
169 | return findColour (colourId);
170 | if (getLookAndFeel().isColourSpecified (colourId))
171 | return getLookAndFeel().findColour (colourId);
172 |
173 | return fallbackColour;
174 | }
175 | //==============================================================================
176 |
177 | void MeterChannel::setColours()
178 | {
179 | m_meterColours.backgroundColour = getColourFromLnf (backgroundColourId, juce::Colours::black);
180 | m_meterColours.inactiveColour = getColourFromLnf (inactiveColourId, juce::Colours::grey);
181 | m_meterColours.textValueColour = getColourFromLnf (textValueColourId, juce::Colours::white.darker (0.6f)); // NOLINT
182 | m_meterColours.muteColour = getColourFromLnf (mutedColourId, juce::Colours::red);
183 | m_meterColours.muteMouseOverColour = getColourFromLnf (mutedMouseOverColourId, juce::Colours::black);
184 | m_meterColours.solodColour = getColourFromLnf (solodColourId, juce::Colours::red);
185 | m_meterColours.faderColour = getColourFromLnf (faderColourId, juce::Colours::blue.withAlpha (Constants::kFaderAlphaMax));
186 | m_meterColours.textColour = getColourFromLnf (textColourId, juce::Colours::white.darker (0.6f)); // NOLINT
187 | m_meterColours.tickMarkColour = getColourFromLnf (tickMarkColourId, juce::Colours::white.darker (0.3f).withAlpha (0.5f)); // NOLINT
188 | m_meterColours.peakHoldColour = getColourFromLnf (peakHoldColourId, juce::Colours::red);
189 | }
190 | //==============================================================================
191 |
192 | void MeterChannel::useGradients (bool useGradients)
193 | {
194 | m_meterOptions.useGradient = useGradients;
195 | setOptions (m_meterOptions);
196 | }
197 | //==============================================================================
198 |
199 | void MeterChannel::showTickMarks (bool showTickMarks)
200 | {
201 | m_meterOptions.tickMarksEnabled = showTickMarks;
202 | setOptions (m_meterOptions);
203 | }
204 | //==============================================================================
205 |
206 | void MeterChannel::showTickMarksOnTop (bool showTickMarksOnTop)
207 | {
208 | m_meterOptions.tickMarksOnTop = showTickMarksOnTop;
209 | setOptions (m_meterOptions);
210 | }
211 | //==============================================================================
212 |
213 | void MeterChannel::setTickMarks (const std::vector& tickMarks)
214 | {
215 | m_meterOptions.tickMarks = tickMarks;
216 | setOptions (m_meterOptions);
217 | }
218 | //==============================================================================
219 |
220 | void MeterChannel::showValue (bool showValue /*= true*/)
221 | {
222 | m_meterOptions.valueEnabled = showValue;
223 | setOptions (m_meterOptions);
224 | }
225 | //==============================================================================
226 |
227 | void MeterChannel::showPeakHold (bool showPeakHold)
228 | {
229 | m_meterOptions.showPeakHoldIndicator = showPeakHold;
230 | setOptions (m_meterOptions);
231 | }
232 | //==============================================================================
233 |
234 | void MeterChannel::resized()
235 | {
236 | auto meterBounds = getLocalBounds();
237 | if (!m_minimalMode)
238 | meterBounds = SoundMeter::Helpers::applyPadding (getLocalBounds(), m_padding);
239 |
240 | // Meter header...
241 | m_header.setBounds (meterBounds.withHeight (0));
242 | if (m_meterOptions.headerEnabled && !m_minimalMode)
243 | m_header.setBounds (meterBounds.removeFromTop (Constants::kDefaultHeaderHeight));
244 |
245 | m_level.setMeterBounds (meterBounds);
246 |
247 | #if SDTK_ENABLE_FADER
248 | m_fader.setBounds (m_level.getLevelBounds());
249 | #endif
250 | }
251 | //==============================================================================
252 |
253 | void MeterChannel::paint (juce::Graphics& g)
254 | {
255 | if (getLocalBounds().isEmpty())
256 | return;
257 |
258 | g.setFont (m_font);
259 |
260 | // Draw the 'HEADER' part of the meter...
261 | if (!m_header.getBounds().isEmpty() && m_meterOptions.headerEnabled && !m_minimalMode)
262 | {
263 | #if SDTK_ENABLE_FADER
264 | m_header.draw (g, isActive(), m_fader.isEnabled(), m_meterColours);
265 | #else
266 | m_header.draw (g, isActive(), false, m_meterColours);
267 | #endif
268 | }
269 |
270 | drawMeter (g);
271 |
272 | #if SDTK_ENABLE_FADER
273 | m_fader.draw (g, m_meterColours); // Draw FADER....
274 | #endif
275 | }
276 | //==============================================================================
277 |
278 | void MeterChannel::drawMeter (juce::Graphics& g)
279 | {
280 | // Draw meter BACKGROUND...
281 | g.setColour (m_active ? m_meterColours.backgroundColour : m_meterColours.inactiveColour);
282 | g.fillRect (m_level.getMeterBounds());
283 |
284 | m_active ? m_level.drawMeter (g, m_meterColours) : m_level.drawInactiveMeter (g, m_meterColours);
285 | }
286 | //==============================================================================
287 |
288 | bool MeterChannel::isDirty (const juce::Rectangle& rectToCheck /*= {}*/) const noexcept
289 | {
290 | if (rectToCheck.isEmpty())
291 | return !m_dirtyRect.isEmpty();
292 | return m_dirtyRect.intersects (rectToCheck);
293 | }
294 | //==============================================================================
295 |
296 | void MeterChannel::addDirty (const juce::Rectangle& dirtyRect) noexcept
297 | {
298 | if (!isShowing())
299 | return;
300 | m_dirtyRect = m_dirtyRect.getUnion (dirtyRect);
301 | }
302 | //==============================================================================
303 |
304 | void MeterChannel::setDirty (bool isDirty /*= true*/) noexcept
305 | {
306 | if (!isShowing())
307 | return;
308 | m_dirtyRect = { 0, 0, 0, 0 };
309 | if (isDirty)
310 | m_dirtyRect = getLocalBounds();
311 | }
312 | //==============================================================================
313 |
314 | void MeterChannel::refresh (const bool forceRefresh)
315 | {
316 | if (!isShowing())
317 | return;
318 |
319 | if (getBounds().isEmpty())
320 | return;
321 |
322 | if (m_active)
323 | {
324 | m_level.refreshMeterLevel();
325 | const auto levelDirtyBounds = m_level.getDirtyBounds();
326 | if (!levelDirtyBounds.isEmpty())
327 | addDirty (levelDirtyBounds);
328 |
329 | #if SDTK_ENABLE_FADER
330 | if (m_fader.needsRedrawing())
331 | addDirty (m_level.getMeterBounds()); // Repaint if the faders are being moved/faded out...
332 | #endif
333 | }
334 |
335 | // Redraw if dirty or forced to...
336 | if (isDirty())
337 | {
338 | repaint (m_dirtyRect);
339 | setDirty (false);
340 | }
341 | else if (forceRefresh)
342 | {
343 | repaint();
344 | }
345 | }
346 | //==============================================================================
347 |
348 | void MeterChannel::setActive (bool isActive, NotificationOptions notify /*= NotificationOptions::dontNotify*/)
349 | {
350 | if (m_active == isActive)
351 | return;
352 | m_active = isActive;
353 |
354 | #if SDTK_ENABLE_FADER
355 | if (notify == NotificationOptions::notify)
356 | notifyParent();
357 | #else
358 | juce::ignoreUnused (notify);
359 | #endif
360 |
361 | reset();
362 | repaint();
363 | }
364 | //==============================================================================
365 |
366 | void MeterChannel::resetMouseOvers() noexcept
367 | {
368 | m_header.resetMouseOver();
369 | m_level.resetMouseOverValue();
370 | }
371 | //==============================================================================
372 |
373 | void MeterChannel::setFont (const juce::Font& font)
374 | {
375 | m_font = font;
376 | m_header.setFont (m_font);
377 | setDirty(); // TODO: - [mh] Change this to refresh(true)?
378 | }
379 | //==============================================================================
380 |
381 | void MeterChannel::resetPeakHold()
382 | {
383 | m_level.resetPeakHold();
384 | // setDirty(); // TODO: - [mh] Change this to refresh(true)?
385 | }
386 | //==============================================================================
387 |
388 | void MeterChannel::setChannelName (const juce::String& channelName)
389 | {
390 | m_header.setName (channelName);
391 | addDirty (m_header.getBounds());
392 | }
393 | //==============================================================================
394 |
395 | void MeterChannel::setIsLabelStrip (bool isLabelStrip) noexcept
396 | {
397 | m_isLabelStrip = isLabelStrip;
398 | m_level.setIsLabelStrip (isLabelStrip);
399 | }
400 | //==============================================================================
401 |
402 | void MeterChannel::setMeterSegments (const std::vector& segmentsOptions)
403 | {
404 | #if SDTK_ENABLE_FADER
405 | m_fader.setMeterSegments (segmentsOptions);
406 | #endif
407 | m_level.setMeterSegments (segmentsOptions);
408 | setDirty(); // TODO: - [mh] Change this to refresh(true)?
409 | resized();
410 | }
411 | //==============================================================================
412 |
413 | #if SDTK_ENABLE_FADER
414 |
415 | void MeterChannel::showFader (const bool faderVisible /*= true */)
416 | {
417 | m_fader.setVisible (faderVisible);
418 |
419 | // If slider needs to be DE-ACTIVATED...
420 | if (!faderVisible || !m_fader.isEnabled())
421 | resetMouseOvers();
422 | addDirty (m_fader.getBounds());
423 | }
424 | //==============================================================================
425 |
426 | void MeterChannel::setFaderValue (const float value, NotificationOptions notificationOption /*= NotificationOptions::DontNotify*/, const bool mustShowFader /*= true*/)
427 | {
428 | if (m_fader.setValue (value, notificationOption))
429 | {
430 | if (mustShowFader && !m_fader.isVisible())
431 | flashFader();
432 | addDirty (m_fader.getBounds());
433 | }
434 | }
435 | //==============================================================================
436 |
437 | void MeterChannel::enableFader (bool faderEnabled /*= true*/) noexcept
438 | {
439 | m_fader.enable (faderEnabled);
440 | addDirty (m_fader.getBounds());
441 | }
442 |
443 | //==============================================================================
444 |
445 | void MeterChannel::mouseDown (const juce::MouseEvent& event)
446 | {
447 | // Left mouse button down and fader is active...
448 | if (event.mods.isLeftButtonDown() && m_fader.isEnabled())
449 | {
450 | // Clicked on the METER part...
451 | if (!m_header.isMouseOver (event.y) && !m_level.isMouseOverValue (event.y) && m_fader.isVisible())
452 | {
453 | if (!isActive())
454 | setActive (true); // Activate if it was deactivated.
455 | m_fader.setValueFromPos (event.y); // Set the fader level at the value clicked.
456 | addDirty (m_fader.getBounds());
457 | }
458 |
459 | // Clicked on the HEADER part...
460 | if (m_header.isMouseOver (event.y))
461 | {
462 | if (event.mods.isShiftDown())
463 | {
464 | setActive (true, NotificationOptions::dontNotify);
465 | if (onChannelSolo)
466 | onChannelSolo (this);
467 | }
468 | else
469 | {
470 | setActive (!isActive(), NotificationOptions::notify);
471 | }
472 | }
473 | }
474 | }
475 |
476 | #endif /* SDTK_ENABLE_FADER */
477 | //==============================================================================
478 |
479 | void MeterChannel::mouseMove (const juce::MouseEvent& event)
480 | {
481 | // Check if the FADER is enabled...
482 |
483 | #if SDTK_ENABLE_FADER
484 | const bool faderEnabled = m_fader.isEnabled();
485 | #else
486 | const bool faderEnabled = false;
487 | #endif
488 |
489 | // Check if the mouse is over the header part...
490 | const auto isMouseOverHeader = m_header.isMouseOver(); // Get the previous mouse over flag, to check if it has changed.
491 | const bool mouseOverHeaderChanged = (isMouseOverHeader != m_header.isMouseOver (event.y)); // Check if it has changed.
492 | if (m_header.isMouseOver() && mouseOverHeaderChanged && faderEnabled) // If the mouse entered the 'header' part for the first time and the fader is enabled...
493 | {
494 | setMouseCursor (juce::MouseCursor::PointingHandCursor);
495 | setTooltip ("Mute or un-mute channel");
496 | }
497 | if (mouseOverHeaderChanged)
498 | addDirty (m_header.getBounds()); // Mouse over status has changed. Repaint.
499 |
500 | // Check if the mouse is over the value part...
501 | const auto isMouseOverValue = m_level.isMouseOverValue();
502 | const bool mouseOverValueChanged = (isMouseOverValue != m_level.isMouseOverValue (event.y));
503 | if (m_level.isMouseOverValue() && mouseOverValueChanged)
504 | {
505 | setMouseCursor (juce::MouseCursor::PointingHandCursor); // NOLINT
506 | setTooltip ("Double click to clear peak hold.");
507 | }
508 | if (mouseOverValueChanged)
509 | addDirty (m_level.getValueBounds());
510 |
511 | // Check if the mouse is over the meter part...
512 | if (!m_header.isMouseOver() && !m_level.isMouseOverValue())
513 | {
514 | #if SDTK_ENABLE_FADER
515 | if (m_fader.isVisible())
516 | {
517 | setMouseCursor (juce::MouseCursor::PointingHandCursor);
518 | setTooltip (m_isLabelStrip ? "Drag to move master fader." : "Drag to move fader.");
519 | }
520 | else
521 | #endif /* SDTK_ENABLE_FADER */
522 |
523 | {
524 | setMouseCursor (juce::MouseCursor::NormalCursor);
525 | }
526 | }
527 | }
528 | //==============================================================================
529 |
530 | void MeterChannel::mouseExit (const juce::MouseEvent& /*event*/)
531 | {
532 | resetMouseOvers();
533 | repaint();
534 | }
535 | //==============================================================================
536 |
537 | void MeterChannel::mouseDoubleClick (const juce::MouseEvent& event)
538 | {
539 | if (event.mods.isLeftButtonDown())
540 | {
541 |
542 | if (!m_header.isMouseOver (event.y))
543 | {
544 | if (m_level.isMouseOverValue (event.y)) // Double clicking on VALUE resets peak hold...
545 | resetPeakHold();
546 | #if SDTK_ENABLE_FADER
547 | else if (event.mods.isShiftDown()) // Shift double click resets the full mixer...
548 | {
549 | if (onMixerReset)
550 | onMixerReset();
551 | }
552 | else if (isActive()) // Double clicking on the FADER resets it to 0...
553 | setFaderValue (1.0f, NotificationOptions::notify);
554 | #endif /* SDTK_ENABLE_FADER */
555 | }
556 | }
557 | }
558 | #if SDTK_ENABLE_FADER
559 | //==============================================================================
560 |
561 | void MeterChannel::mouseDrag (const juce::MouseEvent& event)
562 | {
563 | // When left button down, the meter is active, the fader is active and the mouse is not over the 'info' area...
564 | if (event.mods == juce::ModifierKeys::leftButtonModifier && isActive() && m_fader.isVisible() && !m_level.isMouseOverValue (event.y))
565 | {
566 | m_fader.setValueFromPos (event.y);
567 | addDirty (m_level.getMeterBounds());
568 | }
569 | }
570 | //==============================================================================
571 |
572 | void MeterChannel::mouseWheelMove (const juce::MouseEvent& /*event*/, const juce::MouseWheelDetails& wheel)
573 | {
574 | setFaderValue (juce::jlimit (0.0f, 1.0f, m_fader.getValue() + (wheel.deltaY / Constants::kFaderSensitivity)), NotificationOptions::notify, false);
575 | }
576 |
577 | #endif /* SDTK_ENABLE_FADER */
578 |
579 | } // namespace SoundMeter
580 | } // namespace sd
--------------------------------------------------------------------------------
/meter/sd_MeterChannel.h:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #pragma once
34 |
35 | #include "sd_MeterHeader.h"
36 | #include "sd_MeterHelpers.h"
37 | #include "sd_MeterLevel.h"
38 |
39 | #include
40 | #include
41 | #include
42 | #include
43 | #if SDTK_ENABLE_FADER
44 | #include "sd_MeterFader.h"
45 | #endif
46 | namespace sd
47 | {
48 | namespace SoundMeter
49 | {
50 | /**
51 | * @brief An individual meter channel.
52 | *
53 | * This represents a single meter.
54 | * Use the MetersComponent to create multiple meters matching
55 | * the specified channel format.
56 | */
57 | class MeterChannel final
58 | : public juce::Component
59 | , private juce::SettableTooltipClient
60 | {
61 | public:
62 | using ChannelType = juce::AudioChannelSet::ChannelType;
63 | using Ptr = juce::Component::SafePointer;
64 |
65 | /**
66 | * @brief Default constructor.
67 | */
68 | MeterChannel() noexcept;
69 |
70 | /**
71 | * @brief Parameterized constructor.
72 | *
73 | * @param meterOptions Meter options to use (defining appearance and functionality).
74 | * @param padding The padding to use (space between meter and the edge of the component).
75 | * @param channelName The channel name (set by the user).
76 | * @param isLabelStrip When set to true, this meter will function as a label strip (with markers for levels at the tick-marks).
77 | * @param channelType The channel type (left, right, center, etc...).
78 | */
79 | MeterChannel (const Options& meterOptions, Padding padding, const juce::String& channelName, bool isLabelStrip = false, ChannelType channelType = ChannelType::unknown);
80 |
81 | #if SDTK_ENABLE_FADER
82 | /** @brief Destructor. */
83 | virtual ~MeterChannel() override;
84 | #endif
85 |
86 | /**
87 | * @brief Reset the meter (but not the peak hold).
88 | *
89 | * @see resetPeakHold
90 | */
91 | void reset();
92 |
93 | /**
94 | * @brief Refresh the meter with the current input level.
95 | *
96 | * @param forceRefresh When set to true, the meter will be forced to repaint (even if not dirty).
97 | * @see setRefreshRate
98 | */
99 | void refresh (bool forceRefresh);
100 |
101 | /**
102 | * @brief Sets the meter's refresh rate.
103 | *
104 | * Set this to optimize the meter's decay rate.
105 | *
106 | * @param refreshRate_hz Refresh rate in Hz.
107 | * @see refresh, setDecay, getDecay
108 | */
109 | void setRefreshRate (float refreshRate_hz) { m_level.setRefreshRate (refreshRate_hz); }
110 |
111 | /**
112 | * @brief Set meter decay.
113 | *
114 | * @param decay_ms Meter decay in milliseconds.
115 | * @see getDecay, setRefreshRate
116 | */
117 | void setDecay (float decay_ms) { m_level.setDecay (decay_ms); }
118 |
119 | /**
120 | * @brief Set the padding of the meter.
121 | *
122 | * The padding is the space between the meter and the edges
123 | * of the component.
124 | *
125 | * @param padding Amount of padding to apply.
126 | */
127 | void setPadding (const Padding& padding) noexcept { m_padding = padding; }
128 |
129 | /**
130 | * @brief Get meter decay.
131 | *
132 | * @return Meter decay in milliseconds.
133 | * @see setDecay, setRefreshRate
134 | */
135 | [[nodiscard]] float getDecay() const noexcept { return m_level.getDecay(); }
136 |
137 | /**
138 | * @brief Set the input level from the audio engine.
139 | *
140 | * Called from the audio thread!
141 | *
142 | * @param inputLevel New input level (in amp).
143 | */
144 | inline void setInputLevel (float inputLevel) { m_level.setInputLevel (inputLevel); }
145 |
146 | /**
147 | * @brief Set the meter's options.
148 | *
149 | * The options determine the appearance and functionality of the meter.
150 | *
151 | * @param meterOptions Meter options to use.
152 | */
153 | void setOptions (const Options& meterOptions);
154 |
155 | /**
156 | * @brief Activate or deactivate (mute) the meter.
157 | *
158 | * @param isActive When set to true, the meter is active.
159 | * @param notify Determine whether to notify all listeners or not.
160 | * @see isActive
161 | */
162 | void setActive (bool isActive, NotificationOptions notify = NotificationOptions::dontNotify);
163 |
164 | /**
165 | * @brief Check if the meter is active (un-muted).
166 | *
167 | * @return True, if the meter is active (un-muted).
168 | *
169 | * @see setActive
170 | */
171 | [[nodiscard]] bool isActive() const noexcept { return m_active; }
172 |
173 | /**
174 | * @brief Set whether this meter is a label strip.
175 | *
176 | * A label strip only draws the value labels (at the tick-marks),
177 | * but does not display any level.
178 | *
179 | * @param isLabelStrip when set, this meter behave like a label strip.
180 | */
181 | void setIsLabelStrip (bool isLabelStrip = false) noexcept;
182 |
183 | /**
184 | * @brief Set the segments the meter is made out of.
185 | *
186 | * All segments have a level range, a range within the meter and a colour (or gradient).
187 | *
188 | * @param segmentsOptions The segments options to create the segments with.
189 | */
190 | void setMeterSegments (const std::vector& segmentsOptions);
191 |
192 | /**
193 | * @brief Reset the peak hold.
194 | *
195 | * Resets the peak hold indicator and value.
196 | *
197 | * @see showPeakHold
198 | */
199 | void resetPeakHold();
200 |
201 | /**
202 | * @brief Show (or hide) the peak hold indicator.
203 | *
204 | * @param showPeakHold When set true, the peak hold indicator will be shown.
205 | * @see showPeakValue, resetPeakHold
206 | */
207 | void showPeakHold (bool showPeakHold = true);
208 |
209 | /**
210 | * @brief Show the peak 'value' part of the meter.
211 | *
212 | * The peak 'value' part will be shown below the meter (in dB).
213 | * It's the same level as the peak hold bar.
214 | *
215 | * @param showValue When set true, shows the 'value' level (in dB) part below the meter.
216 | * @see showPeakHold, resetPeakHold, showValue
217 | */
218 | void showValue (bool showValue = true);
219 |
220 | /**
221 | * @brief Show the 'header' part of the meter.
222 | *
223 | * The 'header' part is the part above the meter displaying
224 | * the channel name (when set) or the channel type.
225 | * It also doubles as a mute button for the specific channel.
226 | *
227 | * @param headerVisible When set to true, the 'header' part will be visible.
228 | */
229 | void showHeader (bool headerVisible);
230 |
231 | /**
232 | * @brief Set the meter font.
233 | *
234 | * Font to be used for the header, value and label strip.
235 | *
236 | * @param font The font to use.
237 | */
238 | void setFont (const juce::Font& font);
239 |
240 | /**
241 | * @brief Set the channel name.
242 | *
243 | * Set's the channel name belonging to the track
244 | * feeding the meter.
245 | *
246 | * @param channelName Name to assign to this meter.
247 | */
248 | void setChannelName (const juce::String& channelName);
249 |
250 | /**
251 | * @brief Set the channel type.
252 | *
253 | * For instance: left, right, center, etc..
254 | *
255 | * @param channelType The channel type assigned to the meter.
256 | *
257 | * @see getType
258 | */
259 | void setChannelType (ChannelType channelType) { m_header.setType (channelType); }
260 |
261 | /**
262 | * @brief Check if a specified name will fit in a give width (in pixels).
263 | *
264 | * This can be a more detailed, multi-line description of the function,
265 | * and it's containing logic.
266 | *
267 | * @param name The name to check the width of.
268 | * @param widthAvailable The width (in pixels) available to fit the name in.
269 | * @return True, if the name will fit in the given width (in pixels).
270 | *
271 | * @see getChannelNameWidth, setChannelNames
272 | */
273 | [[nodiscard]] bool nameFits (const juce::String& name, int widthAvailable) const { return m_header.textFits (name, widthAvailable); }
274 |
275 | /**
276 | * @brief Get the width (in pixels) of the channel info in the 'header' part.
277 | *
278 | * @return The width (in pixels) taken by the channel info in the 'header' part.
279 | *
280 | * @see getChannelTypeWidth, nameFits, setChannelName
281 | */
282 | [[nodiscard]] float getChannelNameWidth() const noexcept { return m_header.getNameWidth(); }
283 |
284 | /**
285 | * @brief Get the width (in pixels) of the full type description in the 'header' part.
286 | *
287 | * @return The width (in pixels) taken by the full type description in the 'header' part.
288 | *
289 | * @see getChannelNameWidth, nameFits, setChannelType
290 | */
291 | [[nodiscard]] float getChannelTypeWidth() const noexcept { return m_header.getTypeWidth(); }
292 |
293 | /**
294 | * @brief Set the referred width (from other meters) used to decide what info to display.
295 | *
296 | * When this is set to zero, each meter uses his own bounds to decide what to display.
297 | * When set to a non zero value (for instance from another meter) this meter will use that
298 | * value to decide what to display.
299 | * When there is not enough room (width) to display the full description or name, display
300 | * the abbreviated type description.
301 | *
302 | * @param referredTypeWidth The width (in pixels) to use when deciding what to display in the header.
303 | */
304 | void setReferredTypeWidth (float referredTypeWidth) noexcept { m_header.setReferredWidth (referredTypeWidth); }
305 |
306 | /**
307 | * @brief Enable tick-marks (divider lines) on the meter.
308 | *
309 | * A tick mark is a horizontal line, dividing the meter.
310 | * This is also the place the label strip will put it's text values.
311 | *
312 | * The tick-marks will be shown when they are enable and visible.
313 | *
314 | * @param showTickMarks When set true, the tick-marks are enabled.
315 | * @see showTickMarks, setTickMarks, showTickMarksOnTop
316 | */
317 | void showTickMarks (bool showTickMarks);
318 |
319 | /**
320 | * @brief Show the tick-marks on top of the level or below it.
321 | *
322 | * When below the level, the tick-marks will be obscured if the
323 | * level is loud enough.
324 | *
325 | * @param showTickMarksOnTop Show the tick-marks on top of the level.
326 | */
327 | void showTickMarksOnTop (bool showTickMarksOnTop);
328 |
329 | /**
330 | * @brief Set the level of the tick marks.
331 | *
332 | * A tick mark is a horizontal line, dividing the meter.
333 | * This is also the place the label strip will put it's text values.
334 | *
335 | * @param tickMarks List of tick mark values (in decibels).
336 | * @see showTickMarks, showTickMarksOnTop, showTickMarks
337 | */
338 | void setTickMarks (const std::vector& tickMarks);
339 |
340 | /**
341 | * Get the bounds of the 'meter' and 'header' parts combined.
342 | *
343 | * @return The bounds of the 'meter' and 'header' parts combined.
344 | */
345 | [[nodiscard]] juce::Rectangle getLabelStripBounds() const noexcept { return m_level.getMeterBounds().getUnion (m_header.getBounds()); }
346 |
347 | /**
348 | * @brief Set the meter in 'minimal' mode.
349 | *
350 | * In minimal mode, the meter is in it's cleanest state possible.
351 | * This means no header, no tick-marks, no value, no faders and no indicator.
352 | *
353 | * @param minimalMode When set to true, 'minimal' mode will be enabled.
354 | * @see isMinimalModeActive, autoSetMinimalMode
355 | */
356 | void setMinimalMode (bool minimalMode);
357 |
358 | /**
359 | * @brief Check if the meter is in 'minimal' mode.
360 | *
361 | * In minimal mode, the meter is in it's cleanest state possible.
362 | * This means no header, no tick-marks, no value, no faders and no indicator.
363 | *
364 | * @return True, if the meter is in 'minimal' mode.
365 | * @see setMinimalMode, autoSetMinimalMode
366 | */
367 | [[nodiscard]] bool isMinimalModeActive() const noexcept { return m_minimalMode; }
368 |
369 | /**
370 | * @brief Automatically set the meter in 'minimal' mode.
371 | *
372 | * Use the proposed height and width to determine if that would lead
373 | * to the meter being in 'minimal' mode. Then apply that mode.
374 | *
375 | * In minimal mode, the meter is in it's cleanest state possible.
376 | * This means no header, no tick-marks, no value, no faders and no indicator.
377 | *
378 | * @param proposedWidth The width use to determine if the meter would be in 'minimal' mode.
379 | * @param proposedHeight The height use to determine if the meter would be in 'minimal' mode.
380 | * @return True, if the meter is in 'minimal' mode.
381 | *
382 | * @see setMinimalMode, isMinimalModeActive
383 | */
384 | bool autoSetMinimalMode (int proposedWidth, int proposedHeight);
385 |
386 | /**
387 | * @brief Use gradients in stead of hard segment boundaries.
388 | *
389 | * @param useGradients When set to true, uses smooth gradients. False gives hard segment boundaries.
390 | */
391 | void useGradients (bool useGradients);
392 |
393 | #if SDTK_ENABLE_FADER
394 |
395 | /**
396 | * @brief Show or hide the fader.
397 | *
398 | * The fader overlay display on top of the 'meter' part
399 | * (in combination with the 'mute' buttons in the 'header' part)
400 | * can be used by the user to control gain or any other
401 | * parameter.
402 | *
403 | * @param faderVisible When set to true, show the fader. Otherwise hide it.
404 | * @see isFaderVisible, setFaderEnabled
405 | */
406 | void showFader (bool faderVisible = true);
407 |
408 | /**
409 | * @brief Check if the meter is visible or hidden.
410 | *
411 | * @return True, if the meter is visible.
412 | * @see showFader, setFaderEnabled
413 | */
414 | [[nodiscard]] bool isFaderVisible() const noexcept { return m_fader.isVisible(); }
415 |
416 | /**
417 | * @brief Enable the 'fader' overlay.
418 | *
419 | * The fader overlay display on top of the 'meter' part
420 | * (in combination with the 'mute' buttons in the 'header' part)
421 | * can be used by the user to control gain or any other
422 | * parameter.
423 | *
424 | * @param faderEnabled True, when the fader needs to be enabled.
425 | * @see isFaderVisible, showFader
426 | */
427 | void enableFader (bool faderEnabled = true) noexcept;
428 |
429 | /**
430 | * @brief Show the fader briefly and fade out (unless overridden and shown longer).
431 | */
432 | void flashFader();
433 |
434 | /**
435 | * @brief Get the value of the meter fader.
436 | *
437 | * @return The current fader value [0..1].
438 | */
439 | [[nodiscard]] float getFaderValue() const noexcept { return m_fader.getValue(); }
440 |
441 | /**
442 | * @brief Set fader value.
443 | *
444 | * The fader overlay display on top of the 'meter' part
445 | * (in combination with the 'mute' buttons in the 'header' part)
446 | * can be used by the user to control gain or any other
447 | * parameter.
448 | *
449 | * @param value The value [0..1] the fader needs to be set to.
450 | * @param notificationOption Select whether to notify the listeners.
451 | * @param showFader When set to true, the fader will briefly appear (when the value is changed).
452 | */
453 | void setFaderValue (float value, NotificationOptions notificationOption = NotificationOptions::dontNotify, bool showFader = true);
454 |
455 | /**
456 | * @brief Notify the parent component that a fader has moved (or a mute button has been pressed).
457 | */
458 | void notifyParent();
459 |
460 | /** @brief You can assign a lambda to this callback object to have it called fader is moved. */
461 | std::function onFaderMove { nullptr };
462 | /** @brief You can assign a lambda to this callback object to have it called channel is soloed. */
463 | std::function onChannelSolo { nullptr };
464 | /** @brief You can assign a lambda to this callback object to have it called when the total mixer needs to be reset. */
465 | std::function onMixerReset { nullptr };
466 | #endif /* SDTK_ENABLE_FADER */
467 |
468 | /** @internal */
469 | void paint (juce::Graphics& g) override;
470 | void resized() override;
471 | void lookAndFeelChanged() override;
472 | void visibilityChanged() override;
473 |
474 | /**
475 | * @brief Colour IDs that can be used to customise the colours.
476 | * This can be done by overriding juce's LookAndFeel class.
477 | */
478 | enum ColourIds
479 | {
480 | backgroundColourId = 0x1a03201, ///< Background colour.
481 | tickMarkColourId = 0x1a03202, ///< Tick-mark colour.
482 | textColourId = 0x1a03203, ///< Text colour.
483 | faderColourId = 0x1a03204, ///< Fader colour.
484 | textValueColourId = 0x1a03205, ///< Value text colour.
485 | mutedColourId = 0x1a03206, ///< Muted button colour.
486 | solodColourId = 0x1a03207, ///< Soloed button colour..
487 | mutedMouseOverColourId = 0x1a03208, ///< Muted mouse over button colour.
488 | inactiveColourId = 0x1a03209, ///< Inactive (muted) colour.
489 | peakHoldColourId = 0x1a03210 ///< Peak hold colour.
490 | };
491 |
492 | private:
493 | // clang-format off
494 | Header m_header { m_font }; ///< 'Header' part of the meter with info relating to the meter (name, channel type, info rect, index in a sequence of multiple meters).
495 | Level m_level {}; ///< 'Meter' part of the meter. Actually displaying the level.
496 | Options m_meterOptions {}; ///< 'Meter' options.
497 |
498 | #if SDTK_ENABLE_FADER
499 |
500 | Fader m_fader;
501 | #endif /* SDTK_ENABLE_FADER */
502 |
503 | bool m_active = true;
504 | bool m_isLabelStrip = false;
505 | bool m_minimalMode = false;
506 |
507 | juce::Rectangle m_dirtyRect {};
508 | Padding m_padding { 0, 0, 0, 0 }; ///< Space between meter and component's edge.
509 | juce::Font m_font { juce::FontOptions{} };
510 | MeterColours m_meterColours {};
511 |
512 | void setDirty (bool isDirty = true) noexcept;
513 | [[nodiscard]] bool isDirty (const juce::Rectangle& rectToCheck = {}) const noexcept;
514 | void addDirty (const juce::Rectangle& dirtyRect) noexcept;
515 | void drawMeter (juce::Graphics& g);
516 | [[nodiscard]] juce::Colour getColourFromLnf (int colourId, const juce::Colour& fallbackColour) const;
517 | void mouseMove (const juce::MouseEvent& event) override;
518 | void mouseExit (const juce::MouseEvent& event) override;
519 | void mouseDoubleClick (const juce::MouseEvent& event) override;
520 | void resetMouseOvers () noexcept;
521 | void setColours ();
522 |
523 | #if SDTK_ENABLE_FADER
524 | void mouseDrag (const juce::MouseEvent& event) override;
525 | void mouseWheelMove (const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel) override;
526 | void mouseDown (const juce::MouseEvent& event) override;
527 | #endif /* SDTK_ENABLE_FADER */
528 |
529 | // clang-format on
530 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MeterChannel)
531 | };
532 | } // namespace SoundMeter
533 | } // namespace sd
534 |
--------------------------------------------------------------------------------
/meter/sd_MeterFader.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #include "sd_MeterFader.h"
34 |
35 | namespace sd // NOLINT
36 | {
37 | namespace SoundMeter
38 | {
39 | //==============================================================================
40 |
41 | void Fader::flash() noexcept
42 | {
43 | if (!m_enabled)
44 | return;
45 |
46 | m_fadeStart = static_cast (juce::Time::getMillisecondCounter());
47 | m_isFading = true;
48 | }
49 | //==============================================================================
50 |
51 | void Fader::setVisible (bool visible /*= true*/) noexcept
52 | {
53 | if (!m_enabled)
54 | return;
55 |
56 | // If fader needs to be HIDDEN...
57 | if (!visible)
58 | {
59 | // If it was visible, FADE it out...
60 | if (m_visible)
61 | m_fadeStart = static_cast (juce::Time::getMillisecondCounter());
62 |
63 | // Hide fader...
64 | m_visible = false;
65 | }
66 | // ... if fader needs to be SHOWN...
67 | else
68 | {
69 | // Show fader...
70 | m_visible = true;
71 | m_fadeStart = 0;
72 | }
73 | }
74 | //==============================================================================
75 |
76 | void Fader::draw (juce::Graphics& g, const MeterColours& meterColours)
77 | {
78 | m_isFading = false;
79 |
80 | if (!m_enabled)
81 | return;
82 |
83 | const auto timeSinceStartFade = getTimeSinceStartFade();
84 |
85 | // Return if the fader was already invisible and a new fade has not been started...
86 | if (!m_visible && timeSinceStartFade >= Constants::kFaderFadeTime_ms)
87 | return;
88 |
89 | auto alpha = Constants::kFaderAlphaMax;
90 |
91 | // If it's fading, calculate it's alpha...
92 | if (timeSinceStartFade < Constants::kFaderFadeTime_ms)
93 | {
94 | constexpr float fadePortion = 2.0f;
95 | alpha = juce::jlimit (0.0f, 1.0f, fadePortion - ((static_cast (timeSinceStartFade) * fadePortion) / Constants::kFaderFadeTime_ms)) * Constants::kFaderAlphaMax;
96 | m_isFading = alpha > 0.0f;
97 | }
98 |
99 | // If the fader is not fully transparent, draw it...
100 | if (alpha > 0.0f)
101 | {
102 | g.setColour (meterColours.faderColour.withAlpha (alpha));
103 | auto faderRect = m_bounds;
104 | m_drawnFaderValue = getValue();
105 | auto value_db = juce::Decibels::gainToDecibels (m_drawnFaderValue);
106 | for (const auto& segment: m_segments)
107 | {
108 | if (Helpers::containsUpTo (segment.levelRange, value_db))
109 | {
110 | const auto valueInSegment = std::clamp ((value_db - segment.levelRange.getStart()) / segment.levelRange.getLength(), 0.0f, 1.0f);
111 | const auto convertedValue = juce::jmap (valueInSegment, segment.meterRange.getStart(), segment.meterRange.getEnd());
112 | g.fillRect (faderRect.removeFromBottom (m_bounds.proportionOfHeight (convertedValue)));
113 | break;
114 | }
115 | }
116 | }
117 | }
118 | //==============================================================================
119 |
120 | void Fader::setMeterSegments (const std::vector& segmentsOptions)
121 | {
122 | m_segments = segmentsOptions;
123 | }
124 | //==============================================================================
125 |
126 | bool Fader::setValue (const float value, NotificationOptions notificationOption /*= NotificationOptions::Notify*/) // NOLINT
127 | {
128 | if (!m_enabled)
129 | return false;
130 | if (juce::approximatelyEqual (m_faderValue.load(), value))
131 | return false;
132 | m_faderValue.store (value);
133 |
134 | #if SDTK_ENABLE_FADER
135 | if (notificationOption == NotificationOptions::notify && onFaderValueChanged)
136 | onFaderValueChanged();
137 | #else
138 | juce::ignoreUnused (notificationOption);
139 | #endif
140 |
141 | return true;
142 | }
143 | //==============================================================================
144 |
145 | void Fader::setValueFromPos (const int position, NotificationOptions notificationOption /*= NotificationOptions::Notify*/)
146 | {
147 | const auto height = static_cast (m_bounds.getHeight());
148 | if (height <= 0.0f)
149 | return;
150 |
151 | auto value = 1.0f - std::clamp ((static_cast (position) - m_bounds.getY()) / height, 0.0f, 1.0f);
152 | for (const auto& segment: m_segments)
153 | {
154 | if (Helpers::containsUpTo (segment.meterRange, value))
155 | {
156 | const auto valueInSegment = std::clamp ((value - segment.meterRange.getStart()) / segment.meterRange.getLength(), 0.0f, 1.0f);
157 | const auto value_db = juce::jmap (valueInSegment, segment.levelRange.getStart(), segment.levelRange.getEnd());
158 | value = juce::Decibels::decibelsToGain (value_db);
159 | break;
160 | }
161 | }
162 |
163 | setValue (value, notificationOption);
164 | }
165 | //==============================================================================
166 | } // namespace SoundMeter
167 | } // namespace sd
168 |
--------------------------------------------------------------------------------
/meter/sd_MeterFader.h:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #pragma once
34 |
35 | #include "sd_MeterHelpers.h"
36 |
37 | #include
38 | #include
39 | #include
40 | #include
41 |
42 | namespace sd // NOLINT
43 | {
44 | namespace SoundMeter
45 | {
46 |
47 | /**
48 | * @brief Class responsible for the fader.
49 | *
50 | * The fader overlay displayed on top of the 'meter' part
51 | * (in combination with the 'mute' buttons in the 'header' part)
52 | * can be used by the user to control gain or any other
53 | * parameter.
54 | */
55 | class Fader final
56 | {
57 | public:
58 | Fader() = default;
59 |
60 | /**
61 | * @brief Show the fader briefly and fade out (unless overridden and shown longer).
62 | */
63 | void flash() noexcept;
64 |
65 | /**
66 | * @brief Check if the fader is visible.
67 | *
68 | * @return True, if the meter is visible, otherwise the fader is hidden.
69 | * @see setVisible, setEnabled, isEnabled
70 | */
71 | [[nodiscard]] bool isVisible() const noexcept { return m_visible && m_enabled; }
72 |
73 | /**
74 | * @brief Show or hide the fader.
75 | *
76 | * @param visible When set to true, show the fader. Otherwise hide it.
77 | * @see isVisible, setEnabled, isEnabled
78 | */
79 | void setVisible (bool visible = true) noexcept;
80 |
81 | /**
82 | * @brief Check if the 'fader' overlay is enabled.
83 | *
84 | * @return True, when the fader is enabled.
85 | * @see setEnabled, isVisible, setVisible
86 | */
87 | [[nodiscard]] bool isEnabled() const noexcept { return m_enabled; }
88 |
89 | /**
90 | * @brief Enable or disable the 'fader' overlay.
91 | *
92 | * @param enabled True, when the fader needs to be enabled.
93 | * @see isEnabled, isActive, setActive
94 | */
95 | void enable (bool enabled = true) noexcept { m_enabled = enabled; }
96 |
97 | /**
98 | * @brief Set the fader bounds.
99 | *
100 | * @param bounds The bounds to use for the fader.
101 | * @see getBounds
102 | */
103 | void setBounds (const juce::Rectangle& bounds) noexcept { m_bounds = bounds.toFloat(); }
104 |
105 | /**
106 | * @brief Get the fader bounds.
107 | *
108 | * @return Bounds used by the fader.
109 | * @see setBounds
110 | */
111 | [[nodiscard]] juce::Rectangle getBounds() const noexcept { return m_bounds.toNearestIntEdges(); }
112 |
113 | /**
114 | * @brief Get the value of the meter fader.
115 | *
116 | * @return The current fader value [0..1].
117 | * @see setValueFromPos, setValue
118 | */
119 | [[nodiscard]] float getValue() const noexcept { return m_faderValue.load(); }
120 |
121 | /**
122 | * @brief Set fader value.
123 | *
124 | * @param value The value [0..1] the fader needs to be set to.
125 | * @param notificationOption Select whether to notify the listeners.
126 | * @return True, if the value actually changed.
127 | *
128 | * @see setValueFromPos, getValue
129 | */
130 | bool setValue (float value, NotificationOptions notificationOption = NotificationOptions::notify);
131 |
132 | /**
133 | * @brief Set fader value according to a supplied mouse position.
134 | *
135 | * @param position The mouse position (y coordinate) to use to calculate the fader value.
136 | * @param notificationOption Select whether to notify the listeners.
137 | *
138 | * @see setValue
139 | */
140 | void setValueFromPos (int position, NotificationOptions notificationOption = NotificationOptions::notify);
141 |
142 | /**
143 | * @brief Check whether the fader is currently fading out.
144 | *
145 | * @return True, if the fader is currently fading out.
146 | */
147 | [[nodiscard]] bool isFading() const noexcept { return m_isFading; }
148 |
149 | /**
150 | * @brief Draw the fader.
151 | *
152 | * @param[in,out] g The juce graphics context to use.
153 | * @param meterColours Colours to draw the fader with.
154 | */
155 | void draw (juce::Graphics& g, const MeterColours& meterColours);
156 |
157 | /**
158 | * @brief Set the segments the meter is made out of.
159 | *
160 | * All segments have a level range, a range within the meter and a colour (or gradient).
161 | *
162 | * @param segmentsOptions The segments options to create the segments with.
163 | */
164 | void setMeterSegments (const std::vector& segmentsOptions);
165 |
166 | /**
167 | * @brief Check if the fader needs redrawing.
168 | */
169 | [[nodiscard]] bool needsRedrawing() noexcept { return !juce::approximatelyEqual (m_drawnFaderValue, m_faderValue.load()) || isFading(); }
170 |
171 | /**
172 | * @brief Set the fader value changed callback.
173 | *
174 | * @param callback The callback to call when the fader value changes.
175 | */
176 | std::function onFaderValueChanged { nullptr };
177 |
178 | private:
179 | std::atomic m_faderValue { 1.0f }; // Fader value (between 0..1).
180 | juce::Rectangle m_bounds {};
181 |
182 | float m_drawnFaderValue = 1.0f;
183 | bool m_visible = false;
184 | bool m_enabled = false;
185 | bool m_isFading = false;
186 | int m_fadeStart = 0;
187 | std::vector m_segments = MeterScales::getDefaultScale();
188 |
189 | [[nodiscard]] int getTimeSinceStartFade() const noexcept { return static_cast (juce::Time::getMillisecondCounter()) - m_fadeStart; }
190 |
191 | JUCE_LEAK_DETECTOR (Fader)
192 | };
193 |
194 | } // namespace SoundMeter
195 |
196 | } // namespace sd
197 |
--------------------------------------------------------------------------------
/meter/sd_MeterHeader.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 |
34 | #include "sd_MeterHeader.h"
35 |
36 | #include "sd_MeterHelpers.h"
37 |
38 | namespace sd // NOLINT
39 | {
40 |
41 | namespace SoundMeter
42 | {
43 |
44 | //==============================================================================
45 |
46 | void Header::draw (juce::Graphics& g, bool meterActive, bool faderEnabled, const MeterColours& meterColours)
47 | {
48 | if (m_bounds.isEmpty())
49 | return;
50 |
51 | // Draw channel names...
52 | const juce::String headerText = getInfo();
53 |
54 | // Draw 'button' for muting/de-activating channel...
55 | if (m_mouseOver && faderEnabled)
56 | {
57 | g.setColour (meterActive ? meterColours.muteColour : meterColours.muteMouseOverColour);
58 | g.fillRect (m_bounds);
59 | g.setColour (meterColours.muteColour.contrasting (0.8f)); // NOLINT
60 | }
61 | else
62 | {
63 | if (meterActive)
64 | {
65 | g.setColour (meterColours.textColour);
66 | }
67 | else
68 | {
69 | g.setColour (meterColours.inactiveColour);
70 | g.fillRect (m_bounds);
71 | g.setColour (meterColours.inactiveColour.contrasting (0.8f)); // NOLINT
72 | }
73 | }
74 | if (headerText.isNotEmpty())
75 | {
76 | g.setFont (m_font.withHeight (Constants::kDefaultHeaderFontHeight));
77 | g.drawFittedText (headerText, m_bounds, juce::Justification::centred, 1);
78 | }
79 | }
80 | //==============================================================================
81 |
82 | void Header::setType (const juce::AudioChannelSet::ChannelType& type)
83 | {
84 | m_type = type;
85 | m_typeDescription = juce::AudioChannelSet::getChannelTypeName (type);
86 | m_typeAbbrDecscription = juce::AudioChannelSet::getAbbreviatedChannelTypeName (type);
87 |
88 | calculateInfoWidth();
89 | }
90 | //==============================================================================
91 |
92 | void Header::setName (const juce::String& name)
93 | {
94 | if (name.isEmpty())
95 | return;
96 |
97 | m_name = name;
98 |
99 | calculateInfoWidth();
100 | }
101 | //==============================================================================
102 |
103 | void Header::calculateInfoWidth()
104 | {
105 | m_nameWidth = juce::GlyphArrangement::getStringWidth (m_font, m_name);
106 | m_typeWidth = juce::GlyphArrangement::getStringWidth (m_font, m_typeDescription);
107 | }
108 | //==============================================================================
109 |
110 | juce::String Header::getInfo() const noexcept
111 | {
112 | // Check which type width to use. This meter's one or a referred meter...
113 | const auto typeWidthToCompare = (m_referredWidth > 0 ? m_referredWidth : m_typeWidth);
114 |
115 | // First check if the channel name fits and is not empty (preferred)...
116 | if (m_name.isNotEmpty() && m_nameWidth < static_cast (m_bounds.getWidth()))
117 | return m_name;
118 |
119 | if (m_typeDescription.isNotEmpty() && typeWidthToCompare < static_cast (m_bounds.getWidth() - 5)) // Check if there is room for the full channel description...
120 | return m_typeDescription;
121 |
122 | return m_typeAbbrDecscription; // ... otherwise use the abbreviated one.
123 | }
124 | //==============================================================================
125 |
126 | void Header::setFont (const juce::Font& font)
127 | {
128 | m_font = font;
129 | calculateInfoWidth();
130 | }
131 | //==============================================================================
132 |
133 | bool Header::textFits (const juce::String& text, const int widthAvailable) const
134 | {
135 | return juce::GlyphArrangement::getStringWidth (m_font, text) <= static_cast (widthAvailable);
136 | }
137 | //==============================================================================
138 |
139 | bool Header::isMouseOver (const int y) noexcept
140 | {
141 | m_mouseOver = (y < m_bounds.getHeight());
142 | return m_mouseOver;
143 | }
144 |
145 | } // namespace SoundMeter
146 | } // namespace sd
--------------------------------------------------------------------------------
/meter/sd_MeterHeader.h:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #pragma once
34 |
35 | #include
36 | #include
37 | #include
38 | #include "sd_MeterHelpers.h"
39 |
40 | namespace sd // NOLINT
41 | {
42 | namespace SoundMeter
43 | {
44 |
45 | /**
46 | * @brief Class responsible for the meter's 'header' part.
47 | *
48 | * The 'header' part, is the part above the meter displaying
49 | * the name (when supplied by the user), the type (left, right) or
50 | * the abbreviated type if the other two do not fit.
51 | *
52 | * The 'header' also doubles as a button which can de-activate (mute) or
53 | * activate the meter.
54 | */
55 | class Header final
56 | {
57 | public:
58 | /**
59 | * @brief Constructor
60 | * @param font The font to use in the header.
61 | */
62 | explicit Header (juce::Font& font) noexcept : Header ({}, juce::AudioChannelSet::unknown, font) { }
63 |
64 | /**
65 | * @brief Constructor with channel identification.
66 | *
67 | * @param name Channel name to display in the header.
68 | * @param type Channel type to display in the header.
69 | * @param font The font to use in the header.
70 | */
71 | Header (juce::String name, const juce::AudioChannelSet::ChannelType& type, juce::Font& font) noexcept
72 | : m_font (font), m_name (std::move (name)), m_type (type)
73 | {
74 | }
75 |
76 | /**
77 | * @brief Set the channel type.
78 | *
79 | * For instance: left, right, center, etc..
80 | *
81 | * @param type The channel type assigned to the meter.
82 | *
83 | * @see getType
84 | */
85 | void setType (const juce::AudioChannelSet::ChannelType& type);
86 |
87 | /**
88 | * @brief Get the channel type.
89 | *
90 | * For instance: left, right, center, etc..
91 | *
92 | * @return The channel type assigned to the meter.
93 | *
94 | * @see setType
95 | */
96 | [[nodiscard]] const juce::AudioChannelSet::ChannelType& getType() const noexcept { return m_type; }
97 |
98 | /**
99 | * @brief Set the channel name.
100 | *
101 | * Can be anything the user sets (mid, side, etc..).
102 | *
103 | * @param name The channel name assigned to the meter.
104 | *
105 | * @see getName
106 | */
107 | void setName (const juce::String& name);
108 |
109 | /**
110 | * @brief Get the channel name.
111 | *
112 | * Can be anything the user sets (mid, side, etc..).
113 | *
114 | * @return The channel name assigned to the meter.
115 | *
116 | * @see setName
117 | */
118 | [[nodiscard]] juce::String getName() const noexcept { return m_name; }
119 |
120 | /**
121 | * @brief Get the width (in pixels) of the channel name.
122 | *
123 | * @return The width (in pixels) taken by the channel name.
124 | * @see getTypeWidth, textFits
125 | */
126 | [[nodiscard]] float getNameWidth() const noexcept { return m_nameWidth; }
127 |
128 | /**
129 | * @brief Get the width (in pixels) of the channel description.
130 | *
131 | * @return The width (in pixels) taken by the channel description.
132 | * @see getNameWdith, textFits
133 | */
134 | [[nodiscard]] float getTypeWidth() const noexcept { return m_typeWidth; }
135 |
136 | /**
137 | * @brief Get the info text displayed in the 'header'
138 | *
139 | * This can be either the channel name (when set by the user),
140 | * the channel type description (left, right, etc..) or the
141 | * abbreviated channel type description when the other two do not fit.
142 | *
143 | * @return The info text displayed in the 'header'.
144 | *
145 | * @see getName, getType
146 | */
147 | [[nodiscard]] juce::String getInfo() const noexcept;
148 |
149 | /**
150 | * @brief Check whether a certain text will fit the width available using the meter's specified font.
151 | *
152 | * @param text The info text to check the width of.
153 | * @param widthAvailable The width available in the 'header' part.
154 | *
155 | * @see getInfo, getTypeWidth, getNameWidth
156 | */
157 | [[nodiscard]] bool textFits (const juce::String& text, int widthAvailable) const;
158 |
159 | /**
160 | * @brief Set the font used to display the info (and other text in the meter).
161 | *
162 | * Font to be used for the header, value and label strip.
163 | *
164 | * @param font The font to use.
165 | * @see getFont
166 | */
167 | void setFont (const juce::Font& font);
168 |
169 | /**
170 | * @brief Set the bounds of the 'header' part of the meter.
171 | *
172 | * @param bounds The bounds to use for the 'header' part of the meter.
173 | * @see getBounds
174 | */
175 | void setBounds (const juce::Rectangle& bounds) noexcept { m_bounds = bounds; }
176 |
177 | /**
178 | * @brief Get the bounds of the 'header' part of the meter.
179 | *
180 | * @return The bounds of the 'header' part of the meter.
181 | * @see setBounds
182 | */
183 | [[nodiscard]] juce::Rectangle getBounds() const noexcept { return m_bounds; }
184 |
185 | /**
186 | * @brief Check if the mouse is over the 'header' part of the meter.
187 | *
188 | * @param y The coordinate to use to check whether the mouse is over the 'header' part.
189 | * @return True, when the mouse is over the 'header' part of the meter, using the supplied y coordinate.
190 | * @see resetMouseOver
191 | */
192 | [[nodiscard]] bool isMouseOver (int y) noexcept;
193 |
194 | /**
195 | * @brief Check if the mouse is over the 'header' part of the meter.
196 | *
197 | * @return True, when the mouse is over the 'header' part of the meter.
198 | * @see resetMouseOver
199 | */
200 | [[nodiscard]] bool isMouseOver() const noexcept { return m_mouseOver; }
201 |
202 | /**
203 | * @brief Reset 'mouse over' status of the 'header' part of the meter.
204 | */
205 | void resetMouseOver() noexcept { m_mouseOver = false; }
206 |
207 | /**
208 | * @brief Set the referred width (from other meters) used to decide what info to display.
209 | *
210 | * When this is set to zero, each meter uses his own bounds to decide what to display.
211 | * When set to a non zero value (for instance from another meter) this meter will use that
212 | * value to decide what to display.
213 | * When there is not enough room (width) to display the full description or name, display
214 | * the abbreviated type description.
215 | *
216 | * @param referredWidth The width (in pixels) to use when deciding what to display in the header.
217 | */
218 | void setReferredWidth (float referredWidth) noexcept { m_referredWidth = referredWidth; }
219 |
220 | /**
221 | * @brief Draw the 'header' part of the meter.
222 | *
223 | * @param[in,out] g The juce graphics context to use.
224 | * @param meterActive True, when the meter is active (not muted).
225 | * @param faderEnabled True, when the fader overlay is enabled.
226 | * @param meterColours The colours to draw the header with.
227 | */
228 | void draw (juce::Graphics& g, bool meterActive, bool faderEnabled, const MeterColours& meterColours);
229 |
230 | private:
231 | juce::Font& m_font;
232 |
233 | // Info
234 | juce::String m_name = "";
235 | juce::AudioChannelSet::ChannelType m_type = juce::AudioChannelSet::ChannelType::unknown;
236 | juce::String m_typeDescription = "";
237 | juce::String m_typeAbbrDecscription = "";
238 |
239 | // Bounds
240 | juce::Rectangle m_bounds {};
241 | float m_nameWidth = 0.0f;
242 | float m_typeWidth = 0.0f;
243 | float m_referredWidth = 0.0f;
244 | bool m_mouseOver = false;
245 |
246 | void calculateInfoWidth();
247 |
248 | // clang-format on
249 | JUCE_LEAK_DETECTOR (Header)
250 | };
251 |
252 | } // namespace SoundMeter
253 | } // namespace sd
254 |
--------------------------------------------------------------------------------
/meter/sd_MeterHelpers.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #include "sd_MeterHelpers.h"
34 |
35 | namespace sd // NOLINT
36 | {
37 | namespace SoundMeter
38 | {
39 | namespace Helpers
40 | {
41 |
42 | //==============================================================================
43 | juce::Rectangle applyPadding (const juce::Rectangle& rectToPad, Padding paddingToApply) noexcept
44 | {
45 | juce::Rectangle result { rectToPad };
46 | if (juce::isPositiveAndBelow (paddingToApply.left, result.getWidth()))
47 | result.setLeft (rectToPad.getX() + paddingToApply.left);
48 | if (juce::isPositiveAndBelow (paddingToApply.right, result.getWidth()))
49 | result.setWidth (rectToPad.getWidth() - paddingToApply.right);
50 | if (juce::isPositiveAndBelow (paddingToApply.bottom, result.getHeight()))
51 | result.setHeight (rectToPad.getHeight() - paddingToApply.bottom);
52 |
53 | return result;
54 | }
55 | //==============================================================================
56 |
57 | static constexpr bool containsUpTo (juce::Range levelRange, float levelDb) noexcept
58 | {
59 | return levelDb > levelRange.getStart() && levelDb <= levelRange.getEnd();
60 | }
61 |
62 | } // namespace Helpers
63 | //==============================================================================
64 |
65 | std::vector MeterScales::getDefaultScale()
66 | {
67 | return getDefaultScale (juce::Colours::green, juce::Colours::yellow, juce::Colours::red);
68 | }
69 | //==============================================================================
70 |
71 | std::vector MeterScales::getSmpteScale()
72 | {
73 | return getSmpteScale (juce::Colours::green, juce::Colours::yellow, juce::Colours::red);
74 | }
75 | //==============================================================================
76 |
77 | Options MeterScales::getSmpteOptions (Options options)
78 | {
79 | options.tickMarks = { 0.0f, -3.0f, -6.0f, -9.0f, -12.0f, -15.0f, -20.0f, -25.0f, -30.0f, -35.0f, -40.0f };
80 | options.decayTime_ms = 2250.0f;
81 | options.defaultDecayTime_ms = 2250.0f; // 20 dB/s.
82 | options.useGradient = false;
83 | return options;
84 | }
85 | //==============================================================================
86 |
87 | std::vector MeterScales::getEbuPpmScale()
88 | {
89 | return getEbuPpmScale (juce::Colours::green, juce::Colours::red);
90 | }
91 | //==============================================================================
92 |
93 | Options MeterScales::getEbuPpmOptions (Options options)
94 | {
95 | options.tickMarks = { -12.0f, -16.0f, -20.0f, -24.0f, -28.0f, -32.0f, -36.0f };
96 | options.nominalLevel_db = -24.0f;
97 | options.decayTime_ms = 2100.0f;
98 | options.defaultDecayTime_ms = 2100.0f; // 13.333 dB/s.
99 | options.useGradient = false;
100 | return options;
101 | }
102 | //==============================================================================
103 |
104 | std::vector MeterScales::getExtendedBottomScale()
105 | {
106 | return getExtendedBottomScale (juce::Colours::navy, juce::Colours::white, juce::Colours::red);
107 | }
108 | //==============================================================================
109 |
110 | Options MeterScales::getExtendedBottomOptions (Options options)
111 | {
112 | options.tickMarks = { 0.0f, -10.0f, -20.0f, -30.0f, -40.0f, -50.0f, -60.0f, -70.0f, -80.0f, -90.0f };
113 | options.decayTime_ms = 4800.0f;
114 | options.defaultDecayTime_ms = 4800.0f; // 20 dB/s.
115 | return options;
116 | }
117 | //==============================================================================
118 |
119 | std::vector MeterScales::getExtendedTopScale()
120 | {
121 | return getExtendedBottomScale (juce::Colours::navy, juce::Colours::white, juce::Colours::red);
122 | }
123 | //==============================================================================
124 |
125 | Options MeterScales::getExtendedTopOptions (Options options)
126 | {
127 | options.tickMarks = { 20.0f, 15.0f, 10.0f, 5.0f, 0.0f, -5.0f, -10.0f, -20.0f, -30.0f, -40.0f };
128 | options.decayTime_ms = 4800.0f;
129 | options.defaultDecayTime_ms = 4800.0f; // 20 dB/s.
130 | return options;
131 | }
132 | //==============================================================================
133 |
134 | std::vector MeterScales::getFullRangeScale()
135 | {
136 | return getFullRangeScale (juce::Colours::navy, juce::Colours::white, juce::Colours::red);
137 | }
138 | //==============================================================================
139 |
140 | Options MeterScales::getFullRangeOptions (Options options)
141 | {
142 | options.tickMarks = { 100.0f, 80.0f, 60.0f, 40.0f, 20.0f, 0.0f, -20.0f, -40.0f, -60.0f, -80.0f };
143 | options.decayTime_ms = 4800.0f;
144 | options.defaultDecayTime_ms = 4800.0f; // 20 dB/s.
145 | return options;
146 | }
147 | //==============================================================================
148 |
149 | Options MeterScales::getK20Options (Options options)
150 | {
151 | options.tickMarks = { 0.0f, -4.0f, -8.0f, -12.0f, -16.0f, -20.0f, -24.0f, -28.0f, -32.0f, -36.0f, -40.0f, -44.0f };
152 | options.nominalLevel_db = -20.0f;
153 | options.decayTime_ms = 3666.7f; // 12 dB/s.
154 | options.defaultDecayTime_ms = 3666.7f;
155 | options.useGradient = false;
156 | return options;
157 | }
158 | //==============================================================================
159 |
160 | std::vector MeterScales::getK20Scale()
161 | {
162 | return getK20Scale (juce::Colours::green, juce::Colours::yellow, juce::Colours::red);
163 | }
164 | //==============================================================================
165 |
166 | Options MeterScales::getK14Options (Options options)
167 | {
168 | options.tickMarks = { 0.0f, -6.0f, -10.0f, -14.0f, -18.0f, -22.0f, -26.0f, -30.0f, -34.0f, -38.0f };
169 | options.nominalLevel_db = -14.0f;
170 | options.decayTime_ms = 3166.7f; // 12 dB/s.
171 | options.defaultDecayTime_ms = 3166.7f;
172 | options.useGradient = false;
173 | return options;
174 | }
175 | //==============================================================================
176 |
177 | std::vector MeterScales::getK14Scale()
178 | {
179 | return getK14Scale (juce::Colours::green, juce::Colours::yellow, juce::Colours::red);
180 | }
181 | //==============================================================================
182 |
183 | Options MeterScales::getK12Options (Options options)
184 | {
185 | options.tickMarks = { 0.0f, -4.0f, -8.0f, -12.0f, -16.0f, -20.0f, -24.0f, -28.0f, -32.0f, -36.0f };
186 | options.nominalLevel_db = -12.0f;
187 | options.decayTime_ms = 3000.0f; // 12 dB/s.
188 | options.defaultDecayTime_ms = 3000.0f;
189 | options.useGradient = false;
190 | return options;
191 | }
192 | //==============================================================================
193 |
194 | std::vector MeterScales::getK12Scale()
195 | {
196 | return getK12Scale (juce::Colours::green, juce::Colours::yellow, juce::Colours::red);
197 | }
198 | //==============================================================================
199 |
200 | // namespace Helpers
201 |
202 | } // namespace SoundMeter
203 | } // namespace sd
--------------------------------------------------------------------------------
/meter/sd_MeterHelpers.h:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #pragma once
34 |
35 | #include
36 | #include
37 |
38 | namespace sd // NOLINT
39 | {
40 | namespace SoundMeter
41 | {
42 | /**
43 | * @brief Various meter helper constants.
44 | */
45 | namespace Constants
46 | {
47 | static constexpr auto kMaxWidth = 500.0f; ///< Maximum meter width (in pixels).
48 | static constexpr auto kPeakHoldHeight = 2; ///< Height of the peak hold strip (in pixels).
49 | static constexpr auto kDefaultHeaderHeight = 25; ///< Default height of the 'header' part (in pixels).
50 | static constexpr auto kDefaultHeaderLabelWidth = 30; ///< Default 'header' label width (in pixels).
51 | static constexpr auto kDefaultHeaderFontHeight = 14.0f; ///< Default height of the font used in the 'header' part (in pixels).
52 | static constexpr auto kLabelStripTextPadding = 2; ///< Padding around the text in a label strip (in pixels).
53 | static constexpr auto kLabelStripLeftPadding = 5; ///< Padding (in pixels) on the left side of the label strip (which can double as a master fader).
54 | static constexpr auto kFaderRightPadding = 1; ///< Padding (in pixels) on the right side of the channel faders.
55 | static constexpr auto kMaxLevel_db = 100.0f; ///< Maximum meter level (in db).
56 | static constexpr auto kMinLevel_db = -96.0f; ///< Minimum meter level (in db).
57 | static constexpr auto kMinDecay_ms = 100.0f; ///< Minimum meter decay speed (in milliseconds).
58 | static constexpr auto kMaxDecay_ms = 8000.0f; ///< Maximum meter decay speed (in milliseconds).
59 | static constexpr auto kDefaultDecay_ms = 3000.0f; ///< Default meter decay speed (in milliseconds).
60 | static constexpr auto kTickMarkThickness = 2; ///< Height of a tick mark (in pixels).
61 | static constexpr auto kFaderFadeTime_ms = 2500; ///< Fader fade out time (in milliseconds).
62 | static constexpr auto kFaderSensitivity = 10.0f; ///< Fader sensitivity value. Must be a positive value > 0.
63 | static constexpr auto kFaderAlphaMax = 0.3f; ///< Maximum transparency (alpha) of the fader overlay.
64 | static constexpr auto kMinModeHeightThreshold = 150.0f; ///< Meter minimum mode height threshold in pixels (min. mod is just the meter. not value, ticks or fader).
65 | static constexpr auto kMinModeWidthThreshold = 20.0f; ///< Meter minimum mode width threshold in pixels (min. mod is just the meter. not value, ticks or fader).
66 | static constexpr auto kMetersId = "meters_panel"; ///< ID (name) of all components in the meters panel.
67 | static constexpr auto kLabelStripId = "label_strip"; ///< ID (name) of the label-strip (master fader).
68 | } // namespace Constants
69 |
70 | /**
71 | * @brief Amount of padding to use on the meters.
72 | * Padding is the space between the meter and the component's edge.
73 | */
74 | struct Padding
75 | {
76 | /**
77 | * @brief Constructor.
78 | * @param newLeft New left padding amount (in pixels).
79 | * @param newRight New right padding amount (in pixels).
80 | * @param newTop New top padding amount (in pixels).
81 | * @param newBottom New bottom padding amount (in pixels).
82 | */
83 | Padding (int newLeft, int newRight, int newTop, int newBottom) noexcept : left (newLeft), right (newRight), top (newTop), bottom (newBottom) { }
84 | int left = 0; ///< Space between meter and left border (in pixels).
85 | int right = 0; ///< Space between meter and right border (in pixels).
86 | int top = 0; ///< Space between meter and top border (in pixels).
87 | int bottom = 0; ///< Space between meter and bottom border (in pixels).
88 | };
89 |
90 | /**
91 | * @brief Options defining the meter segments.
92 | */
93 | struct SegmentOptions
94 | {
95 | juce::Range levelRange { Constants::kMinLevel_db, Constants::kMaxLevel_db }; ///< The range of the segment in decibels.
96 | juce::Range meterRange { 0.0f, 1.0f }; ///< The range of the segment in the meter (0.0f - 1.0f, with 0.0f being the bottom of the meter).
97 | juce::Colour segmentColour { juce::Colours::yellow }; ///< The colour of the segment.
98 | juce::Colour nextSegmentColour { segmentColour.brighter() }; ///< The second colour of the segment (for use in gradients).
99 | };
100 |
101 | /**
102 | * @brief All meter options for appearance and functionality.
103 | */
104 | struct Options
105 | {
106 | bool enabled = true; ///< Enable the meter.
107 | bool headerEnabled = true; ///< Enable the 'header' part of the meter.
108 | bool valueEnabled = true; ///< Enable the 'value' part of the meter.
109 | bool faderEnabled = true; ///< Enable the fader (overlay-ed over the meter). Only works if fader have been enabled in the module.
110 | bool useMinimalMode = true; ///< Automatically adapt the meter to use the most of the space available (by hiding header, value, tick-marks, etc...).
111 | float decayTime_ms = Constants::kDefaultDecay_ms; ///< Actual meter decay in milliseconds.
112 | float defaultDecayTime_ms = Constants::kDefaultDecay_ms; ///< Default meter decay in milliseconds.
113 | float refreshRate = 30.0f; ///< Meter refresh rate when using internal timing.
114 | bool tickMarksEnabled = true; ///< Show tick-marks. Divider lines on the meter at certain db levels.
115 | bool tickMarksOnTop = false; ///< Show the tick-marks below the level or above the level (level might obscure the tick-marks if loud enough).
116 | bool useGradient = true; ///< Use gradients for the meter segments, in stead of solid colours.
117 | bool showPeakHoldIndicator = true; ///< Enable peak hold indicator.
118 | std::vector tickMarks = { 0.0f, -3.0f, -6.0f, -9.0f, -12.0f, -18.0f, -30.0f, -40.0f, -50.0f }; ///< Tick-mark position in db.
119 | float tickMarkThickness = static_cast (Constants::kTickMarkThickness); ///< Thickness of the tick-marks in pixels.
120 | float peakHoldThickness = static_cast (Constants::kPeakHoldHeight); ///< Thickness of the peak hold bar in pixels.
121 | float nominalLevel_db = 0.0f; ///< The level (in dB) where the nominal level should be. e.g. -20.0 for K20.
122 | float minLevel_db = -40.0f; ///< The minimum level (in dB) that the meter will display.
123 | };
124 |
125 | /**
126 | * @brief All meter colours for the appearance of the meters.
127 | */
128 | struct MeterColours
129 | {
130 | juce::Colour backgroundColour = juce::Colours::black; ///< Background colour of the meter.
131 | juce::Colour inactiveColour = juce::Colours::grey; ///< Colour of the meter when inactive.
132 | juce::Colour textValueColour = juce::Colours::white.darker (0.6f); ///< Colour of the peak value text.
133 | juce::Colour muteColour = juce::Colours::red; ///< Colour of the mute button.
134 | juce::Colour muteMouseOverColour = juce::Colours::black; ///< Colour of the mute button when the mouse is over it.
135 | juce::Colour faderColour = juce::Colours::blue.withAlpha (Constants::kFaderAlphaMax); ///< Colour of the fader overlay.
136 | juce::Colour textColour = juce::Colours::white.darker (0.6f); ///< Colour of the text (in the header and label strip).
137 | juce::Colour tickMarkColour = juce::Colours::white.darker (0.3f).withAlpha (0.5f); ///< Colour of the tick-marks.
138 | juce::Colour peakHoldColour = juce::Colours::red; ///< Colour of the peak hold indicator.
139 | juce::Colour solodColour = juce::Colours::yellow; ///< Colour of the solo button.
140 | };
141 |
142 | /**
143 | * @brief A class with static functions to create different types of meter scales.
144 | */
145 | class MeterScales final
146 | {
147 | public:
148 | /**
149 | * @brief Default meter scale. 3 segments, from -60db to 0db.
150 | */
151 | [[nodiscard]] static std::vector getDefaultScale();
152 | [[nodiscard]] static std::vector getDefaultScale (const juce::Colour& low, const juce::Colour& mid, const juce::Colour& high)
153 | {
154 | return { { { -60.0f, -18.0f }, { 0.0f, 0.5f }, low, low }, { { -18.0f, -3.0f }, { 0.5f, 0.90f }, low, mid }, { { -3.0f, 0.0f }, { 0.90f, 1.0f }, mid, high } };
155 | }
156 |
157 | /**
158 | * @brief SMPTE meter scale. 3 segments, from -44db to 0db.
159 | */
160 | [[nodiscard]] static Options getSmpteOptions (Options options);
161 | [[nodiscard]] static std::vector getSmpteScale();
162 | [[nodiscard]] static std::vector getSmpteScale (const juce::Colour& low, const juce::Colour& mid, const juce::Colour& high)
163 | {
164 | return { { { -44.0f, -12.0f }, { 0.0f, 0.7273f }, low, low }, { { -12.0f, -3.0f }, { 0.7273f, 0.9318f }, mid, mid }, { { -3.0f, 0.0f }, { 0.9318f, 1.0f }, high, high } };
165 | }
166 |
167 | /**
168 | * @brief Extended bottom range meter. 3 segments, from -96db to 0db.
169 | */
170 | [[nodiscard]] static Options getExtendedBottomOptions (Options options);
171 | [[nodiscard]] static std::vector getExtendedBottomScale();
172 | [[nodiscard]] static std::vector getExtendedBottomScale (const juce::Colour& low, const juce::Colour& mid, const juce::Colour& high)
173 | {
174 | return { { { -96.0f, -18.0f }, { 0.0f, 0.8125f }, low, mid }, { { -18.0f, -6.0f }, { 0.8125f, 0.9375f }, mid, high }, { { -6.0f, 0.0f }, { 0.9375f, 1.0f }, high, high } };
175 | }
176 |
177 | /**
178 | * @brief Extended top range meter. 3 segments, from -50db to 20db.
179 | */
180 | [[nodiscard]] static Options getExtendedTopOptions (Options options);
181 | [[nodiscard]] static std::vector getExtendedTopScale();
182 | [[nodiscard]] static std::vector getExtendedTopScale (const juce::Colour& low, const juce::Colour& mid, const juce::Colour& high)
183 | {
184 | return { { { -50.0f, -10.0f }, { 0.0f, 0.5714f }, low, mid }, { { -10.0f, 0.0f }, { 0.5714f, 0.7143f }, mid, high }, { { 0.0f, 20.0f }, { 0.7143f, 1.0f }, high, high } };
185 | }
186 |
187 | /**
188 | * @brief Maximum meter scale. 3 segments, from -96db to +100db.
189 | */
190 | [[nodiscard]] static Options getFullRangeOptions (Options options);
191 | [[nodiscard]] static std::vector getFullRangeScale();
192 | [[nodiscard]] static std::vector getFullRangeScale (const juce::Colour& low, const juce::Colour& mid, const juce::Colour& high)
193 | {
194 | return { { { -96.0f, -20.0f }, { 0.0f, 0.3878f }, low, mid }, { { -20.0f, 50.0f }, { 0.3878f, 0.6429f }, mid, high }, { { 50.0f, 100.0f }, { 0.6429f, 1.0f }, high, high } };
195 | }
196 |
197 | /**
198 | * @brief EBU PPM meter scale. 2 segments, from -14db to +14db.
199 | */
200 | [[nodiscard]] static Options getEbuPpmOptions (Options options);
201 | [[nodiscard]] static std::vector getEbuPpmScale();
202 | [[nodiscard]] static std::vector getEbuPpmScale (const juce::Colour& low, const juce::Colour& high)
203 | {
204 | return { { { -38.0f, -16.0f }, { 0.0f, 0.785714f }, low, low }, { { -16.0f, -10.0f }, { 0.785714f, 1.0f }, high, high } };
205 | }
206 |
207 | /**
208 | * @brief Yamaha mixer meter scale. 3 segments, from -60db to 0db.
209 | */
210 | [[nodiscard]] static std::vector getYamaha60()
211 | {
212 | return { { { -60.0f, -30.0f }, { 0.0f, 0.2751f }, juce::Colours::yellow, juce::Colours::yellow },
213 | { { -30.0f, -18.0f }, { 0.2751f, 0.4521f }, juce::Colours::yellow, juce::Colours::yellow },
214 | { { -18.0f, 0.0f }, { 0.4521f, 1.0f }, juce::Colours::red, juce::Colours::red } };
215 | }
216 |
217 | /**
218 | * @brief K-system metering.
219 | */
220 | [[nodiscard]] static Options getK20Options (Options options);
221 | [[nodiscard]] static std::vector getK20Scale();
222 | [[nodiscard]] static std::vector getK20Scale (const juce::Colour& low, const juce::Colour& mid, const juce::Colour& high)
223 | {
224 | return { { { -44.0f, -20.0f }, { 0.0f, 0.55365f }, low, low },
225 | { { -20.0f, -16.0f }, { 0.55365f, 0.64378f }, mid, mid },
226 | { { -16.0f, 0.0f }, { 0.64378f, 1.0f }, high, high } };
227 | }
228 |
229 | [[nodiscard]] static Options getK14Options (Options options);
230 | [[nodiscard]] static std::vector getK14Scale();
231 | [[nodiscard]] static std::vector getK14Scale (const juce::Colour& low, const juce::Colour& mid, const juce::Colour& high)
232 | {
233 | return { { { -38.0f, -14.0f }, { 0.0f, 0.65f }, low, low }, { { -14.0f, -10.0f }, { 0.65f, 0.75f }, mid, mid }, { { -10.0f, 0.0f }, { 0.75f, 1.0f }, high, high } };
234 | }
235 |
236 | [[nodiscard]] static Options getK12Options (Options options);
237 | [[nodiscard]] static std::vector getK12Scale();
238 | [[nodiscard]] static std::vector getK12Scale (const juce::Colour& low, const juce::Colour& mid, const juce::Colour& high)
239 | {
240 | return { { { -36.0f, -12.0f }, { 0.0f, 0.666667f }, low, low },
241 | { { -12.0f, -8.0f }, { 0.666667f, 0.791667f }, mid, mid },
242 | { { -8.0f, 0.0f }, { 0.791667f, 1.0f }, high, high } };
243 | }
244 |
245 | private:
246 | MeterScales() = default;
247 | };
248 |
249 | /** @brief Type indicating whether to notify the listeners or not. */
250 | enum class NotificationOptions
251 | {
252 | notify, ///< Notify any listeners.
253 | dontNotify ///< Do not notify any listeners.
254 | };
255 |
256 | /** @brief Position of the label strip. */
257 | enum class LabelStripPosition
258 | {
259 | left, ///< Left of the meters.
260 | right, ///< Right of the meters.
261 | none ///< No label strip will be shown.
262 | };
263 |
264 | namespace Helpers
265 | {
266 | [[nodiscard]] juce::Rectangle applyPadding (const juce::Rectangle& rectToPad, Padding paddingToApply) noexcept;
267 | static constexpr bool containsUpTo (juce::Range levelRange, float levelDb) noexcept;
268 | }
269 |
270 | } // namespace SoundMeter
271 | } // namespace sd
--------------------------------------------------------------------------------
/meter/sd_MeterLevel.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #include "sd_MeterLevel.h"
34 |
35 | namespace sd // NOLINT
36 | {
37 | namespace SoundMeter
38 | {
39 | Level::Level()
40 | {
41 | setMeterSegments (m_segmentOptions);
42 | }
43 | //==============================================================================
44 |
45 | void Level::drawMeter (juce::Graphics& g, const MeterColours& meterColours)
46 | {
47 | for (auto& segment: m_segments)
48 | segment.draw (g, meterColours);
49 |
50 | if (!m_valueBounds.isEmpty())
51 | drawPeakValue (g, meterColours);
52 | }
53 | //==============================================================================
54 |
55 | void Level::drawInactiveMeter (juce::Graphics& g, const MeterColours& meterColours) const
56 | {
57 | // Check if there is space enough to write the 'MUTE' text...
58 | if (static_cast (m_meterBounds.getWidth()) < (g.getCurrentFont().getHeight()))
59 | return;
60 |
61 | g.saveState();
62 | g.addTransform (juce::AffineTransform::rotation (juce::MathConstants::halfPi, static_cast (m_meterBounds.getCentreX()),
63 | static_cast (m_meterBounds.getY() + (m_meterBounds.getWidth() / 2.0f)))); // NOLINT
64 | g.setColour (meterColours.textColour.darker (0.7f)); // NOLINT
65 |
66 | g.drawText (TRANS ("MUTE"), m_meterBounds.withWidth (m_meterBounds.getHeight()).withHeight (m_meterBounds.getWidth()), juce::Justification::centred);
67 | g.restoreState();
68 | }
69 | //==============================================================================
70 |
71 | void Level::drawPeakValue (juce::Graphics& g, const MeterColours& meterColours) const
72 | {
73 | if (m_valueBounds.isEmpty() || m_isLabelStrip)
74 | return;
75 |
76 | // Draw PEAK value...
77 | const auto peak_db = getPeakHoldLevel();
78 | if (peak_db > m_meterRange.getStart()) // If active, present and enough space is available.
79 | {
80 | const int precision = peak_db <= -10.0f ? 1 : 2; // Set precision depending on peak value. NOLINT
81 | g.setColour (meterColours.textValueColour);
82 | g.drawFittedText (juce::String (peak_db, precision), m_valueBounds, juce::Justification::centred, 1);
83 | }
84 | }
85 | //==============================================================================
86 |
87 | float Level::getInputLevel()
88 | {
89 | m_inputLevelRead.store (true);
90 | return juce::Decibels::gainToDecibels (m_inputLevel.load());
91 | }
92 | //==============================================================================
93 |
94 | void Level::setInputLevel (float newLevel)
95 | {
96 | m_inputLevel.store (m_inputLevelRead.load() ? newLevel : std::max (m_inputLevel.load(), newLevel));
97 | m_inputLevelRead.store (false);
98 | }
99 | //==============================================================================
100 |
101 | float Level::getLinearDecayedLevel (float newLevel_db)
102 | {
103 | const auto currentTime = static_cast (juce::Time::getMillisecondCounter());
104 | const auto timePassed = static_cast (currentTime - m_previousRefreshTime);
105 |
106 | m_previousRefreshTime = currentTime;
107 |
108 | if (newLevel_db >= m_meterLevel_db)
109 | return newLevel_db;
110 |
111 | return std::max (newLevel_db, m_meterLevel_db - (timePassed * m_decayRate));
112 | }
113 | //==============================================================================
114 |
115 | float Level::getDecayedLevel (const float newLevel_db)
116 | {
117 | const auto currentTime = static_cast (juce::Time::getMillisecondCounter());
118 | const auto timePassed = static_cast (currentTime - m_previousRefreshTime);
119 |
120 | // A new frame is not needed yet, return the current value...
121 | if (timePassed < m_refreshPeriod_ms)
122 | return m_meterLevel_db;
123 |
124 | m_previousRefreshTime = currentTime;
125 |
126 | if (newLevel_db >= m_meterLevel_db)
127 | return newLevel_db;
128 |
129 | // More time has passed then the meter decay. The meter has fully decayed...
130 | if (timePassed > m_meterOptions.decayTime_ms)
131 | return newLevel_db;
132 |
133 | if (juce::approximatelyEqual (m_meterLevel_db, newLevel_db))
134 | return newLevel_db;
135 |
136 | // Convert that to refreshed frames...
137 | auto numberOfFramePassed = static_cast (std::round ((timePassed * m_meterOptions.refreshRate) / 1000.0f)); // NOLINT
138 |
139 | auto level_db = m_meterLevel_db;
140 | for (int frame = 0; frame < numberOfFramePassed; ++frame)
141 | level_db = newLevel_db + (m_decayCoeff * (level_db - newLevel_db));
142 |
143 | if (std::abs (level_db - newLevel_db) < m_meterOptions.minLevel_db)
144 | level_db = newLevel_db;
145 |
146 | return level_db;
147 | }
148 | //==============================================================================
149 |
150 | void Level::refreshMeterLevel()
151 | {
152 | m_meterLevel_db = getLinearDecayedLevel (getInputLevel());
153 |
154 | if (m_meterLevel_db > getPeakHoldLevel())
155 | m_peakHoldDirty = true;
156 |
157 | for (auto& segment: m_segments)
158 | segment.setLevel (/*m_meterRange.clipValue(*/m_meterLevel_db/*)*/);
159 | }
160 | //==============================================================================
161 |
162 | void Level::setMeterOptions (const Options& meterOptions)
163 | {
164 | m_meterOptions = meterOptions;
165 |
166 | calculateDecayCoeff (meterOptions);
167 | synchronizeMeterOptions();
168 | }
169 | //==============================================================================
170 |
171 | void Level::synchronizeMeterOptions()
172 | {
173 | for (auto& segment: m_segments)
174 | {
175 | segment.setMeterOptions (m_meterOptions);
176 | segment.setIsLabelStrip (m_isLabelStrip);
177 | segment.setMinimalMode (m_minimalModeActive);
178 | }
179 |
180 | m_peakHoldDirty = true;
181 | }
182 | //==============================================================================
183 |
184 | void Level::setMeterSegments (const std::vector& segmentsOptions)
185 | {
186 | if (segmentsOptions.empty())
187 | return;
188 |
189 | m_segments.clear();
190 | m_meterRange = segmentsOptions[0].levelRange;
191 |
192 | for (const auto& segmentOptions: segmentsOptions)
193 | {
194 | m_segments.emplace_back (m_meterOptions, segmentOptions);
195 | m_meterRange.setStart (std::min (m_meterRange.getStart(), segmentOptions.levelRange.getStart()));
196 | m_meterRange.setEnd (std::max (m_meterRange.getEnd(), segmentOptions.levelRange.getEnd()));
197 | }
198 |
199 | synchronizeMeterOptions();
200 | calculateDecayCoeff (m_meterOptions);
201 | }
202 | //==============================================================================
203 |
204 | void Level::reset()
205 | {
206 | m_inputLevel.store (0.0f);
207 | m_meterLevel_db = Constants::kMinLevel_db;
208 | m_previousRefreshTime = 0;
209 | }
210 | //==============================================================================
211 |
212 | void Level::setIsLabelStrip (bool isLabelStrip) noexcept
213 | {
214 | m_isLabelStrip = isLabelStrip;
215 | synchronizeMeterOptions();
216 | }
217 | //==============================================================================
218 |
219 | void Level::setMinimalMode (bool minimalMode)
220 | {
221 | if (m_minimalModeActive == minimalMode)
222 | return;
223 |
224 | m_minimalModeActive = minimalMode;
225 |
226 | setMeterBounds (m_meterBounds);
227 | synchronizeMeterOptions();
228 | }
229 | //==============================================================================
230 |
231 | void Level::setRefreshRate (float refreshRate_hz)
232 | {
233 | m_meterOptions.refreshRate = refreshRate_hz;
234 | calculateDecayCoeff (m_meterOptions);
235 | synchronizeMeterOptions();
236 | }
237 | //==============================================================================
238 |
239 | void Level::setDecay (float decay_ms)
240 | {
241 | m_meterOptions.decayTime_ms = decay_ms;
242 | calculateDecayCoeff (m_meterOptions);
243 | synchronizeMeterOptions();
244 | }
245 | //==============================================================================
246 |
247 | void Level::resetPeakHold()
248 | {
249 | for (auto& segment: m_segments)
250 | segment.resetPeakHold();
251 | m_peakHoldDirty = true;
252 | }
253 | //==============================================================================
254 |
255 | float Level::getPeakHoldLevel() const noexcept
256 | {
257 | if (m_segments.empty())
258 | return Constants::kMinLevel_db;
259 |
260 | return m_segments[0].getPeakHold();
261 | }
262 | //==============================================================================
263 |
264 | void Level::setMeterBounds (const juce::Rectangle& bounds)
265 | {
266 | m_meterBounds = bounds;
267 | m_levelBounds = m_meterBounds;
268 |
269 | // If the meter is in minimal mode, the value is not displayed...
270 | if (m_meterOptions.valueEnabled && !m_minimalModeActive)
271 | m_valueBounds = m_levelBounds.removeFromBottom (Constants::kDefaultHeaderHeight);
272 | else
273 | m_valueBounds = juce::Rectangle();
274 |
275 | for (auto& segment: m_segments)
276 | segment.setMeterBounds (m_levelBounds);
277 |
278 | m_peakHoldDirty = true;
279 | }
280 | //==============================================================================
281 |
282 | juce::Rectangle Level::getDirtyBounds()
283 | {
284 | juce::Rectangle dirtyBounds {};
285 | for (const auto& segment: m_segments)
286 | {
287 | if (segment.isDirty())
288 | dirtyBounds = dirtyBounds.getUnion (segment.getSegmentBounds().toNearestIntEdges());
289 | }
290 |
291 | if (m_peakHoldDirty)
292 | {
293 | dirtyBounds = dirtyBounds.getUnion (m_valueBounds);
294 | m_peakHoldDirty = false;
295 | }
296 |
297 | return dirtyBounds;
298 | }
299 | //==============================================================================
300 |
301 | void Level::calculateDecayCoeff (const Options& meterOptions)
302 | {
303 | m_meterOptions.decayTime_ms = juce::jlimit (Constants::kMinDecay_ms, Constants::kMaxDecay_ms, meterOptions.decayTime_ms);
304 | m_meterOptions.refreshRate = std::max (1.0f, meterOptions.refreshRate);
305 | m_refreshPeriod_ms = (1.0f / m_meterOptions.refreshRate) * 1000.0f; // NOLINT
306 |
307 | m_decayRate = m_meterRange.getLength() / m_meterOptions.decayTime_ms;
308 |
309 | // Rises to 99% of in value over duration of time constant.
310 | m_decayCoeff = std::pow (0.01f, (1000.0f / (m_meterOptions.decayTime_ms * m_meterOptions.refreshRate))); // NOLINT
311 | }
312 | //==============================================================================
313 |
314 | bool Level::isMouseOverValue (const int y)
315 | {
316 | m_mouseOverValue = (y >= m_valueBounds.getY() && !m_valueBounds.isEmpty());
317 | return m_mouseOverValue;
318 | }
319 | } // namespace SoundMeter
320 | } // namespace sd
321 |
--------------------------------------------------------------------------------
/meter/sd_MeterLevel.h:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #pragma once
34 |
35 | #include "sd_MeterHelpers.h"
36 | #include "sd_MeterSegment.h"
37 |
38 | #include
39 | #include
40 | #include
41 |
42 | namespace sd // NOLINT
43 | {
44 | namespace SoundMeter
45 | {
46 | struct Options;
47 |
48 | /**
49 | * @brief Class responsible for anything relating to the 'meter' and peak 'value' parts.
50 | * This also includes the peak hold indicator and the tick-marks.
51 | */
52 | class Level final
53 | {
54 | public:
55 | /**
56 | * @brief Constructor.
57 | */
58 | Level();
59 |
60 | /**
61 | * @brief Reset the meter (but not the peak hold).
62 | *
63 | * @see resetPeakHold
64 | */
65 | void reset();
66 |
67 | /**
68 | * @brief Set the level of the meter.
69 | *
70 | * Here the level is actually set from the audio engine.
71 | * Beware: very likely called from the audio thread!
72 | *
73 | * @param newLevel The peak level from the audio engine (in amp).
74 | *
75 | * @see getInputLevel
76 | */
77 | void setInputLevel (float newLevel);
78 |
79 | /**
80 | * @brief Get's the meter's input level.
81 | *
82 | * @return The meter's input level (in decibels).
83 | *
84 | * @see setInputLevel
85 | */
86 | [[nodiscard]] float getInputLevel();
87 |
88 | /**
89 | * @brief Calculate the actual meter level (ballistics included).
90 | *
91 | * Calculate the meter's level including ballistics.
92 | * Instant attack, but decayed release.
93 | *
94 | * @see getMeterLevel, setDecay
95 | */
96 | void refreshMeterLevel();
97 |
98 | /**
99 | * @brief Get the actual meter's level (including ballistics).
100 | *
101 | * Get the decayed meter level.
102 | * Instant attack, but decayed release.
103 | *
104 | * @return The actual meter's level (in decibels) with ballistics.
105 | *
106 | * @see setMeterLevel, setDecay
107 | */
108 | [[nodiscard]] float getMeterLevel() const noexcept { return m_meterLevel_db; }
109 |
110 | /**
111 | * @brief Set the meter's options.
112 | *
113 | * The options determine the appearance and functionality of the meter.
114 | *
115 | * @param meterOptions Meter options to use.
116 | */
117 | void setMeterOptions (const Options& meterOptions);
118 |
119 | /**
120 | * @brief Check if the peak 'value' part is visible.
121 | *
122 | * The peak value will be shown below the meter (in db).
123 | * It's the same level as the peak hold bar.
124 | *
125 | * @return True, if the peak hold 'value' part is visible.
126 | *
127 | * @see showValue, resetPeakHold
128 | */
129 | [[nodiscard]] bool isPeakValueVisible() const noexcept { return !m_valueBounds.isEmpty(); }
130 |
131 | /**
132 | * @brief Reset the peak hold level.
133 | * @see getPeakHoldLevel, isPeakValueVisible, setPeakValueVisible, showPeakHold, showValue, isPeakHoldEnabled
134 | */
135 | void resetPeakHold();
136 |
137 | /**
138 | * @brief Get the current peak hold level.
139 | * @return The current peak hold level (in decibels).
140 | * @see resetPeakHold, isPeakValueVisible, setPeakValueVisible, setPeakHoldVisible, isPeakHoldEnabled
141 | */
142 | [[nodiscard]] float getPeakHoldLevel() const noexcept;
143 |
144 | /**
145 | * @brief Set the meter in 'minimal' mode.
146 | *
147 | * In minimal mode, the meter is in it's cleanest state possible.
148 | * This means no header, no tick-marks, no value, no faders and no indicator.
149 | *
150 | * @param minimalMode When set to true, 'minimal' mode will be enabled.
151 | * @see isMinimalModeActive, autoSetMinimalMode
152 | */
153 | void setMinimalMode (bool minimalMode);
154 |
155 | /**
156 | * @brief Sets the meter's refresh rate.
157 | *
158 | * Set this to optimize the meter's decay rate.
159 | *
160 | * @param refreshRate_hz Refresh rate in Hz.
161 | * @see refresh, setDecay, getDecay
162 | */
163 | void setRefreshRate (float refreshRate_hz);
164 |
165 | /**
166 | * @brief Get the meter's refresh (redraw) rate.
167 | *
168 | * @return The refresh rate of the meter in Hz.
169 | *
170 | * @see setRefreshRate
171 | */
172 | [[nodiscard]] float getRefreshRate() const noexcept { return m_meterOptions.refreshRate; }
173 |
174 | /**
175 | * @brief Set meter decay.
176 | *
177 | * @param decay_ms Meter decay in milliseconds.
178 | * @see getDecay, setRefreshRate
179 | */
180 | void setDecay (float decay_ms);
181 |
182 | /**
183 | * @brief Get meter decay.
184 | *
185 | * @return Meter decay in milliseconds
186 | *
187 | * @see setDecay
188 | */
189 | [[nodiscard]] float getDecay() const noexcept { return m_meterOptions.decayTime_ms; }
190 |
191 | /**
192 | * @brief Set the segments the meter is made out of.
193 | *
194 | * All segments have a level range, a range within the meter and a colour (or gradient).
195 | *
196 | * @param segmentsOptions The segments options to create the segments with.
197 | */
198 | void setMeterSegments (const std::vector& segmentsOptions);
199 |
200 | /**
201 | * @brief Set whether this meter is a label strip.
202 | *
203 | * A label strip only draws the value labels (at the tick-marks),
204 | * but does not display any level.
205 | *
206 | * @param isLabelStrip when set, this meter behave like a label strip.
207 | */
208 | void setIsLabelStrip (bool isLabelStrip = false) noexcept;
209 |
210 | /**
211 | * @brief Set the bounds of the 'meter' part of the meter.
212 | *
213 | * @param bounds The bounds to use for the 'meter' part of the meter.
214 | * @see getValueBounds, setValueBounds, getMeterBounds, getDirtyBounds, getLevelBounds
215 | */
216 | void setMeterBounds (const juce::Rectangle& bounds);
217 |
218 | /**
219 | * @brief Get the bounds of the 'value' part of the meter.
220 | *
221 | * @return The bounds of the 'value' part of the meter.
222 | * @see setMeterBounds, setValueBounds, getMeterBounds, getDirtyBounds, getLevelBounds
223 | */
224 | [[nodiscard]] juce::Rectangle getValueBounds() const noexcept { return m_valueBounds; }
225 |
226 | /**
227 | * @brief Get the bounds of the 'meter' part.
228 | *
229 | * @return The bounds of the 'meter' part.
230 | * @see getValueBounds, setValueBounds, setMeterBounds, getDirtyBounds, getLevelBounds
231 | */
232 | [[nodiscard]] juce::Rectangle getMeterBounds() const noexcept { return m_meterBounds; }
233 |
234 | /**
235 | * @brief Get the bounds of the 'level' part.
236 | *
237 | * @return The bounds of the 'level' part.
238 | * @see getValueBounds, setValueBounds, setMeterBounds, getDirtyBounds, getMeterBounds
239 | */
240 | [[nodiscard]] juce::Rectangle getLevelBounds() const noexcept { return m_levelBounds; }
241 |
242 | /** @brief Get the dirty part of the meter.*/
243 | [[nodiscard]] juce::Rectangle getDirtyBounds();
244 |
245 | /**
246 | * @brief Check if the mouse cursor is over the 'value' part of the meter.
247 | *
248 | * @param y The y coordinate (relative to the meter bounds) to use to determine if the mouse if over the 'value' part of the meter.
249 | * @return True, if the mouse cursor is over the 'value' part of the meter.
250 | */
251 | bool isMouseOverValue (int y);
252 |
253 | /**
254 | * @brief Check if the mouse cursor is over the 'value' part of the meter.
255 | *
256 | * @return True, if the mouse cursor is over the 'value' part of the meter.
257 | */
258 | [[nodiscard]] bool isMouseOverValue() const noexcept { return m_mouseOverValue; }
259 |
260 | /**
261 | * @brief Reset 'mouse over' status of the 'value' part of the meter.
262 | */
263 | void resetMouseOverValue() noexcept { m_mouseOverValue = false; }
264 |
265 | /**
266 | * @brief Draws the meter.
267 | *
268 | * @param[in,out] g The juce graphics context to use.
269 | * @param meterColours The colours to use to draw the meter.
270 | *
271 | * @see drawInactiveMeter, drawPeakValue, drawPeakHold, drawTickMarks, drawLabels
272 | */
273 | void drawMeter (juce::Graphics& g, const MeterColours& meterColours);
274 |
275 | /**
276 | * @brief Draw the 'meter' part in it's inactive (muted) state.
277 | *
278 | * @param[in,out] g The juce graphics context to use.
279 | * @param meterColours The colours to use to draw the meter.
280 | *
281 | * @see drawMeter, drawTickMarks, drawPeakValue, drawPeakHold, drawLabels
282 | */
283 | void drawInactiveMeter (juce::Graphics& g, const MeterColours& meterColours) const;
284 |
285 | /**
286 | * @brief Draw the peak 'value'.
287 | * @param[in,out] g The juce graphics context to use.
288 | * @param meterColours The colours to use to draw the meter.
289 | *
290 | * @see drawMeter, drawInactiveMeter, drawInactiveMeter, drawPeakHold, drawTickMarks, drawLabels
291 | */
292 | void drawPeakValue (juce::Graphics& g, const MeterColours& meterColours) const;
293 |
294 | private:
295 | Options m_meterOptions;
296 | std::vector m_segmentOptions = MeterScales::getDefaultScale();
297 |
298 | std::vector m_segments; // List of meter segments.
299 | juce::Range m_meterRange { Constants::kMaxLevel_db, Constants::kMinLevel_db };
300 | juce::Rectangle m_valueBounds; // Bounds of the value area.
301 | juce::Rectangle m_meterBounds; // Bounds of the meter area.
302 | juce::Rectangle m_levelBounds; // Bounds of the level area.
303 |
304 | // Meter levels...
305 | std::atomic m_inputLevel { 0.0f }; // Audio peak level.
306 | std::atomic m_inputLevelRead { false };
307 | float m_meterLevel_db = Constants::kMinLevel_db; // Current meter level.
308 | float m_nominalLevel_db = 0.0f; // The level (in dB) where the nominal level should be. e.g. -20.0 for K20.
309 | bool m_peakHoldDirty = false;
310 | bool m_mouseOverValue = false;
311 | bool m_minimalModeActive = false;
312 | bool m_isLabelStrip = false;
313 | float m_decayCoeff = 0.0f;
314 | float m_refreshPeriod_ms = (1.0f / m_meterOptions.refreshRate) * 1000.0f; // NOLINT
315 | int m_previousRefreshTime = 0;
316 | float m_decayRate = 0.0f; // Decay rate in dB/ms.
317 |
318 | [[nodiscard]] float getDecayedLevel (float newLevel_db);
319 | [[nodiscard]] float getLinearDecayedLevel (float newLevel_db);
320 | void calculateDecayCoeff (const Options& meterOptions);
321 | void synchronizeMeterOptions();
322 |
323 | // clang-format on
324 | JUCE_LEAK_DETECTOR (Level)
325 | };
326 | } // namespace SoundMeter
327 | } // namespace sd
328 |
--------------------------------------------------------------------------------
/meter/sd_MeterSegment.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #include "sd_MeterSegment.h"
34 |
35 | #include "sd_MeterHelpers.h"
36 |
37 | namespace sd // NOLINT
38 | {
39 | namespace SoundMeter
40 | {
41 | Segment::Segment (const Options& meterOptions, const SegmentOptions& segmentOptions)
42 | {
43 | setSegmentOptions (segmentOptions);
44 | setMeterOptions (meterOptions);
45 | }
46 | //==============================================================================
47 |
48 | void Segment::setMinimalMode (bool minimalMode) noexcept
49 | {
50 | if (minimalMode == m_minimalModeActive)
51 | return;
52 |
53 | m_minimalModeActive = false;
54 | if (m_meterOptions.useMinimalMode && minimalMode)
55 | m_minimalModeActive = true;
56 | m_isDirty = true;
57 | }
58 | //==============================================================================
59 |
60 | void Segment::setSegmentOptions (SegmentOptions segmentOptions)
61 | {
62 | // Check level range validity.
63 | jassert (segmentOptions.levelRange.getLength() > 0.0f); // NOLINT
64 | // Check meter range validity (0.0f - 1.0f).
65 | jassert (segmentOptions.meterRange.getStart() >= 0.0f && segmentOptions.meterRange.getEnd() <= 1.0f && segmentOptions.meterRange.getLength() > 0.0f); // NOLINT
66 |
67 | m_segmentOptions = segmentOptions;
68 |
69 | if (!m_meterBounds.isEmpty())
70 | setMeterBounds (m_meterBounds);
71 |
72 | m_isDirty = true;
73 | }
74 | //==============================================================================
75 |
76 | void Segment::draw (juce::Graphics& g, const MeterColours& meterColours)
77 | {
78 | m_isDirty = false;
79 |
80 | if (m_isLabelStrip)
81 | {
82 | drawLabels (g, meterColours);
83 | return;
84 | }
85 |
86 | if (!m_meterOptions.tickMarksOnTop)
87 | drawTickMarks (g, meterColours);
88 |
89 | if (!m_drawnBounds.isEmpty())
90 | {
91 | if (m_meterOptions.useGradient)
92 | g.setGradientFill (m_gradientFill);
93 | else
94 | g.setColour (m_segmentOptions.segmentColour);
95 |
96 | g.fillRect (m_drawnBounds.toNearestIntEdges());
97 | }
98 |
99 | if (m_meterOptions.tickMarksOnTop)
100 | drawTickMarks (g, meterColours);
101 |
102 | if (m_meterOptions.showPeakHoldIndicator && !m_peakHoldBounds.isEmpty())
103 | {
104 | g.setColour (meterColours.peakHoldColour);
105 | g.fillRect (m_peakHoldBounds);
106 | m_drawnPeakHoldBounds = m_peakHoldBounds;
107 | }
108 | }
109 | //==============================================================================
110 |
111 | void Segment::drawTickMarks (juce::Graphics& g, const MeterColours& meterColours)
112 | {
113 | if (m_minimalModeActive || !m_meterOptions.tickMarksEnabled)
114 | return;
115 |
116 | g.setColour (meterColours.tickMarkColour);
117 | for (const auto& tickMark: m_tickMarks)
118 | {
119 | if ((tickMark <= m_currentLevel_db) && !m_meterOptions.tickMarksOnTop)
120 | continue;
121 |
122 | const auto tickMarkLevelRatio = std::clamp ((tickMark - m_segmentOptions.levelRange.getStart()) / m_segmentOptions.levelRange.getLength(), 0.0f, 1.0f);
123 | const auto tickMarkY = m_segmentBounds.getY() + m_segmentBounds.proportionOfHeight (1.0f - tickMarkLevelRatio);
124 | const auto tickMarkBounds = juce::Rectangle (m_segmentBounds.getX(), std::max (m_segmentBounds.getY(), tickMarkY - m_meterOptions.tickMarkThickness * 0.5f),
125 | m_segmentBounds.getWidth(), m_meterOptions.tickMarkThickness);
126 | g.fillRect (tickMarkBounds);
127 | }
128 | }
129 | //==============================================================================
130 |
131 | void Segment::drawLabels (juce::Graphics& g, const MeterColours& meterColours) const
132 | {
133 | g.setColour (meterColours.textColour);
134 | const float fontsize = juce::jlimit (1.0f, 15.0f, m_meterBounds.getHeight() / 4.0f); // Set font size proportionally. NOLINT
135 | g.setFont (fontsize);
136 |
137 | for (const auto& tickMark: m_tickMarks)
138 | {
139 | const auto tickMarkLevelRatio = std::clamp ((tickMark - m_segmentOptions.levelRange.getStart()) / m_segmentOptions.levelRange.getLength(), 0.0f, 1.0f);
140 | const auto tickMarkY = m_segmentBounds.getY() + m_segmentBounds.proportionOfHeight (1.0f - tickMarkLevelRatio);
141 | const auto labelBounds = juce::Rectangle (m_segmentBounds.getX(), tickMarkY - (fontsize / 2.0f), m_segmentBounds.getWidth(), fontsize);
142 |
143 | g.drawFittedText (juce::String (tickMark - m_meterOptions.nominalLevel_db), labelBounds.reduced (Constants::kLabelStripTextPadding, 0).toNearestInt(),
144 | juce::Justification::topLeft, 1);
145 | }
146 | }
147 | //==============================================================================
148 |
149 | void Segment::setMeterBounds (juce::Rectangle meterBounds)
150 | {
151 | m_meterBounds = meterBounds;
152 | auto floatBounds = meterBounds.toFloat();
153 | const auto segmentBounds = floatBounds.withY (floatBounds.getY() + floatBounds.proportionOfHeight (1.0f - m_segmentOptions.meterRange.getEnd()))
154 | .withHeight (floatBounds.proportionOfHeight (m_segmentOptions.meterRange.getLength()));
155 | m_segmentBounds = segmentBounds;
156 | updateLevelBounds();
157 | updatePeakHoldBounds();
158 |
159 | if (m_meterOptions.useGradient)
160 | m_gradientFill =
161 | juce::ColourGradient (m_segmentOptions.segmentColour, segmentBounds.getBottomLeft(), m_segmentOptions.nextSegmentColour, segmentBounds.getTopLeft(), false);
162 |
163 | m_isDirty = true;
164 | }
165 | //==============================================================================
166 |
167 | void Segment::setLevel (float level_db)
168 | {
169 | if (!juce::approximatelyEqual (level_db, m_currentLevel_db))
170 | {
171 | m_currentLevel_db = level_db;
172 | updateLevelBounds();
173 | }
174 |
175 | if (level_db > m_peakHoldLevel_db)
176 | {
177 | m_peakHoldLevel_db = level_db;
178 | updatePeakHoldBounds();
179 | }
180 | }
181 | //==============================================================================
182 |
183 | void Segment::updateLevelBounds()
184 | {
185 | if (m_segmentBounds.isEmpty())
186 | return;
187 |
188 | const auto levelRatio = std::clamp ((m_currentLevel_db - m_segmentOptions.levelRange.getStart()) / m_segmentOptions.levelRange.getLength(), 0.0f, 1.0f);
189 | const auto levelBounds = m_segmentBounds.withTop (m_segmentBounds.getY() + m_segmentBounds.proportionOfHeight (1.0f - levelRatio));
190 |
191 | if (m_drawnBounds == levelBounds)
192 | return;
193 |
194 | m_drawnBounds = levelBounds;
195 | m_isDirty = true;
196 | }
197 | //==============================================================================
198 |
199 | void Segment::updatePeakHoldBounds()
200 | {
201 | auto peakHoldBounds = juce::Rectangle();
202 |
203 | if (Helpers::containsUpTo (m_segmentOptions.levelRange, m_peakHoldLevel_db))
204 | {
205 | const auto peakHoldRatio = std::clamp ((m_peakHoldLevel_db - m_segmentOptions.levelRange.getStart()) / m_segmentOptions.levelRange.getLength(), 0.0f, 1.0f);
206 | if (peakHoldRatio == 0.0f)
207 | return;
208 |
209 | const auto peakHoldY = m_segmentBounds.getY() + m_segmentBounds.proportionOfHeight (1.0f - peakHoldRatio);
210 | peakHoldBounds = m_segmentBounds.withTop (peakHoldY).withHeight (m_meterOptions.peakHoldThickness);
211 | }
212 |
213 | if (peakHoldBounds == m_drawnPeakHoldBounds)
214 | return;
215 |
216 | m_peakHoldBounds = peakHoldBounds;
217 | m_isDirty = true;
218 | }
219 | //==============================================================================
220 |
221 | void Segment::resetPeakHold() noexcept
222 | {
223 | m_peakHoldBounds.setHeight (0);
224 | m_peakHoldLevel_db = Constants::kMinLevel_db;
225 | m_drawnPeakHoldBounds = m_peakHoldBounds;
226 | m_isDirty = true;
227 | }
228 | //==============================================================================
229 |
230 | void Segment::setMeterOptions (const Options& meterOptions)
231 | {
232 | m_meterOptions = meterOptions;
233 |
234 | // Find all tickMark-marks in this segment's range...
235 | m_tickMarks.clear();
236 | for (const auto& tickMark: meterOptions.tickMarks)
237 | {
238 | if (Helpers::containsUpTo (m_segmentOptions.levelRange, tickMark))
239 | m_tickMarks.emplace_back (tickMark);
240 | }
241 |
242 | m_isDirty = true;
243 | }
244 | //==============================================================================
245 | } // namespace SoundMeter
246 | } // namespace sd
247 |
--------------------------------------------------------------------------------
/meter/sd_MeterSegment.h:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #pragma once
34 |
35 | #include "sd_MeterHelpers.h"
36 |
37 | #include
38 | #include
39 |
40 | namespace sd // NOLINT
41 | {
42 |
43 | namespace SoundMeter
44 | {
45 |
46 |
47 | class Segment final
48 | {
49 | public:
50 | /** @brief Construct a segment using the supplied options.*/
51 | Segment (const Options& meterOptions, const SegmentOptions& segmentOptions);
52 |
53 | /** @brief Set the level in decibels.*/
54 | void setLevel (float level_db);
55 |
56 | /** @brief Draw the segment.*/
57 | void draw (juce::Graphics& g, const MeterColours& meterColours);
58 |
59 | /** @brief Set the bounds of the total meter (all segments) */
60 | void setMeterBounds (juce::Rectangle meterBounds);
61 |
62 | /** @brief Get the bounding box of this segment.*/
63 | [[nodiscard]] juce::Rectangle getSegmentBounds() const noexcept { return m_segmentBounds; }
64 |
65 | /** @brief Reset the peak hold.*/
66 | void resetPeakHold() noexcept;
67 |
68 | /** @brief Get the peak hold level.*/
69 | [[nodiscard]] float getPeakHold() const noexcept { return m_peakHoldLevel_db; }
70 |
71 | /** @brief Check if the segment needs to be re-drawn (dirty). */
72 | [[nodiscard]] bool isDirty() const noexcept { return m_isDirty; }
73 |
74 | /**
75 | * @brief Set whether this meter is a label strip.
76 | *
77 | * A label strip only draws the value labels (at the tick-marks),
78 | * but does not display any level.
79 | *
80 | * @param isLabelStrip when set, this meter behave like a label strip.
81 | */
82 | void setIsLabelStrip (bool isLabelStrip = false) noexcept { m_isLabelStrip = isLabelStrip; }
83 |
84 | /**
85 | * @brief Set the meter in 'minimal' mode.
86 | *
87 | * In minimal mode, the meter is in it's cleanest state possible.
88 | * This means no header, no tick-marks, no value, no faders and no indicator.
89 | *
90 | * @param minimalMode When set to true, 'minimal' mode will be enabled.
91 | * @see isMinimalModeActive, autoSetMinimalMode
92 | */
93 | void setMinimalMode (bool minimalMode) noexcept;
94 |
95 | /** @brief Set the segment options, describing the range and colour of the segment. */
96 | void setSegmentOptions (SegmentOptions segmentOptions);
97 |
98 | /** @brief Get the segment options, describing the range and colour of the segment. */
99 | [[nodiscard]] SegmentOptions getSegmentOptions() const noexcept { return m_segmentOptions; }
100 |
101 | /** @brief Set meter options. */
102 | void setMeterOptions (const Options& meterOptions);
103 |
104 | /** @brief Get segment options.*/
105 | [[nodiscard]] Options getMeterOptions() const { return m_meterOptions; }
106 |
107 | private:
108 | SegmentOptions m_segmentOptions {};
109 | Options m_meterOptions {};
110 | std::vector m_tickMarks;
111 | juce::Rectangle m_meterBounds;
112 | juce::Rectangle m_segmentBounds;
113 | juce::Rectangle m_drawnBounds;
114 | juce::Rectangle m_peakHoldBounds;
115 | juce::Rectangle m_drawnPeakHoldBounds;
116 | juce::ColourGradient m_gradientFill;
117 |
118 | float m_currentLevel_db = Constants::kMinLevel_db;
119 | float m_peakHoldLevel_db = Constants::kMinLevel_db;
120 | bool m_isDirty = false;
121 | bool m_minimalModeActive = false;
122 | bool m_isLabelStrip = false;
123 |
124 | void updateLevelBounds();
125 | void updatePeakHoldBounds();
126 | void drawTickMarks (juce::Graphics& g, const MeterColours& meterColours);
127 | void drawLabels (juce::Graphics& g, const MeterColours& meterColours) const;
128 |
129 | JUCE_LEAK_DETECTOR (Segment)
130 | };
131 |
132 | } // namespace SoundMeter
133 | } // namespace sd
134 |
--------------------------------------------------------------------------------
/meter/sd_MetersComponent.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #include "sd_MetersComponent.h"
34 |
35 | namespace sd // NOLINT
36 | {
37 | namespace SoundMeter
38 | {
39 | MetersComponent::MetersComponent() : MetersComponent ({}, {}) { }
40 | //==============================================================================
41 |
42 | MetersComponent::MetersComponent (const juce::AudioChannelSet& channelFormat) : MetersComponent ({}, channelFormat) { }
43 | //==============================================================================
44 |
45 | MetersComponent::MetersComponent (const Options& meterOptions) : MetersComponent (meterOptions, {}) { }
46 | //==============================================================================
47 |
48 | MetersComponent::MetersComponent (const Options& meterOptions, const juce::AudioChannelSet& channelFormat)
49 | : m_meterOptions (meterOptions),
50 | m_labelStrip (meterOptions, Padding (Constants::kLabelStripLeftPadding, 0, 0, 0), Constants::kLabelStripId, true, juce::AudioChannelSet::ChannelType::unknown),
51 | m_channelFormat (channelFormat),
52 | m_font (juce::FontOptions())
53 | {
54 | #if SDTK_ENABLE_FADER
55 | m_labelStrip.enableFader (m_meterOptions.faderEnabled);
56 | m_labelStrip.addMouseListener (this, true);
57 | m_labelStrip.onFaderMove = [this] (MeterChannel::Ptr meterChannel) { faderChanged (meterChannel); };
58 | m_labelStrip.onMixerReset = [this]() { resetFaders(); };
59 | #endif
60 |
61 | setName (Constants::kMetersId);
62 |
63 | addAndMakeVisible (m_labelStrip);
64 |
65 | startTimerHz (static_cast (std::round (m_meterOptions.refreshRate)));
66 |
67 | createMeters (channelFormat, {});
68 | }
69 | //==============================================================================
70 |
71 | MetersComponent::~MetersComponent()
72 | {
73 | #if SDTK_ENABLE_FADER
74 | m_labelStrip.removeMouseListener (this);
75 | #endif
76 |
77 | deleteMeters();
78 | }
79 | //==============================================================================
80 |
81 | void MetersComponent::reset()
82 | {
83 | deleteMeters();
84 |
85 | #if SDTK_ENABLE_FADER
86 | resetFaders();
87 | m_labelStrip.showFader (false);
88 | #endif
89 |
90 | m_labelStrip.showTickMarks (false);
91 | m_channelFormat = juce::AudioChannelSet::stereo();
92 |
93 | refresh (true);
94 | }
95 | //==============================================================================
96 |
97 | void MetersComponent::clearMeters()
98 | {
99 | for (auto* meter: m_meterChannels)
100 | if (meter)
101 | meter->setInputLevel (0.0f);
102 |
103 | refresh (true);
104 | }
105 | //==============================================================================
106 |
107 | void MetersComponent::refresh (const bool forceRefresh /*= false*/)
108 | {
109 | if (!isShowing() || m_meterChannels.isEmpty())
110 | return;
111 |
112 | m_labelStrip.refresh (forceRefresh);
113 | for (auto* meter: m_meterChannels)
114 | {
115 | if (meter)
116 | meter->refresh (forceRefresh);
117 | }
118 | }
119 | //==============================================================================
120 |
121 | void MetersComponent::setRefreshRate (float refreshRate_hz)
122 | {
123 | m_meterOptions.refreshRate = refreshRate_hz;
124 |
125 | m_labelStrip.setRefreshRate (static_cast (refreshRate_hz));
126 | for (auto* meter: m_meterChannels)
127 | if (meter)
128 | meter->setRefreshRate (static_cast (refreshRate_hz));
129 |
130 | if (m_useInternalTimer)
131 | {
132 | stopTimer();
133 | startTimerHz (juce::roundToInt (refreshRate_hz));
134 | }
135 | }
136 | //==============================================================================
137 |
138 | void MetersComponent::useInternalTiming (bool useInternalTiming) noexcept
139 | {
140 | m_useInternalTimer = useInternalTiming;
141 |
142 | stopTimer();
143 |
144 | if (useInternalTiming)
145 | startTimerHz (static_cast (std::round (m_meterOptions.refreshRate)));
146 | }
147 | //==============================================================================
148 |
149 | void MetersComponent::paint (juce::Graphics& g)
150 | {
151 | g.fillAll (m_backgroundColour);
152 | }
153 | //==============================================================================
154 |
155 | void MetersComponent::resized()
156 | {
157 | const auto numOfMeters = static_cast (m_meterChannels.size());
158 | if (numOfMeters <= 0.0f)
159 | return;
160 |
161 | auto panelBounds = getLocalBounds().toFloat();
162 | const auto panelHeight = panelBounds.getHeight();
163 | const auto panelWidth = panelBounds.getWidth();
164 | auto labelStripWidth = static_cast (m_labelStripPosition != LabelStripPosition::none ? Constants::kDefaultHeaderLabelWidth : 0);
165 |
166 | // Calculate meter width from available width taking into account the extra width needed when showing the master strip...
167 | auto meterWidth = juce::jlimit (1.0f, Constants::kMaxWidth, (panelWidth - labelStripWidth) / numOfMeters);
168 | const bool minModeEnabled = m_meterChannels[0]->autoSetMinimalMode (static_cast (meterWidth), static_cast (panelHeight));
169 |
170 | // Don't show the label strip in minimum mode...
171 | if (minModeEnabled)
172 | labelStripWidth = 0.0f;
173 |
174 | // Re-calculate actual width (taking into account the min. mode)...
175 | if (m_labelStripPosition != LabelStripPosition::none)
176 | meterWidth = juce::jlimit (1.0f, Constants::kMaxWidth, (panelWidth - labelStripWidth) / numOfMeters);
177 |
178 | // Position all meters and adapt them to the current size...
179 | for (auto* meter: m_meterChannels)
180 | {
181 | if (meter)
182 | {
183 | meter->setMinimalMode (minModeEnabled);
184 | if (m_labelStripPosition == LabelStripPosition::right)
185 | meter->setBounds (panelBounds.removeFromLeft (meterWidth).toNearestIntEdges());
186 | else
187 | meter->setBounds (panelBounds.removeFromRight (meterWidth).toNearestIntEdges());
188 |
189 | #if SDTK_ENABLE_FADER
190 | if (minModeEnabled)
191 | meter->showFader (false); // ... do not show the gain fader if it's too narrow.
192 | #endif
193 | }
194 | }
195 |
196 | // Position MASTER strip...
197 | if (labelStripWidth == 0.0f)
198 | {
199 | m_labelStrip.setBounds ({});
200 | }
201 | else
202 | {
203 | // Use the dimensions of the 'meter' part combined with the 'value' part...
204 | const auto labelStripBounds = m_meterChannels[0]->getLabelStripBounds().toFloat();
205 | if (m_labelStripPosition == LabelStripPosition::right)
206 | m_labelStrip.setBounds (
207 | panelBounds.removeFromRight (labelStripWidth).withY (labelStripBounds.getY()).withHeight (labelStripBounds.getHeight()).toNearestIntEdges());
208 | else if (m_labelStripPosition == LabelStripPosition::left)
209 | m_labelStrip.setBounds (panelBounds.removeFromLeft (labelStripWidth).withY (labelStripBounds.getY()).withHeight (labelStripBounds.getHeight()).toNearestIntEdges());
210 | m_labelStrip.showTickMarks (true);
211 | }
212 | }
213 | //==============================================================================
214 |
215 | void MetersComponent::setChannelNames (const std::vector& channelNames)
216 | {
217 | if (m_meterChannels.isEmpty())
218 | return;
219 |
220 | const auto numChannelNames = static_cast (channelNames.size());
221 | const auto numMeters = static_cast (m_meterChannels.size());
222 | auto defaultMeterWidth = static_cast (Constants::kMinModeWidthThreshold);
223 |
224 | // Loop through all meters...
225 | for (int meterIdx = 0; meterIdx < numMeters; ++meterIdx)
226 | {
227 | if (meterIdx < numChannelNames)
228 | {
229 | if (channelNames[static_cast (meterIdx)].isNotEmpty())
230 | {
231 | m_meterChannels[meterIdx]->setChannelName (channelNames[static_cast (meterIdx)]); // ... and set the channel name.
232 |
233 | // Calculate the meter width so it fits the largest of channel names...
234 | defaultMeterWidth = std::max (defaultMeterWidth, m_meterChannels[meterIdx]->getChannelNameWidth());
235 | }
236 | }
237 | else
238 | {
239 | // Calculate the meter width so it fits the largest of full type descriptions...
240 | defaultMeterWidth = std::max (defaultMeterWidth, m_meterChannels[meterIdx]->getChannelTypeWidth());
241 | }
242 | }
243 |
244 | if (channelNames.empty())
245 | {
246 | for (auto* meter: m_meterChannels)
247 | if (meter)
248 | meter->setReferredTypeWidth (defaultMeterWidth);
249 | }
250 |
251 | // Calculate default mixer width...
252 | // This is the width at which all channel names can be displayed.
253 | m_autoSizedPanelWidth = static_cast (defaultMeterWidth * static_cast (numMeters)); // Min. width needed for channel names.
254 | m_autoSizedPanelWidth += numMeters * (2 * Constants::kFaderRightPadding); // Add the padding that is on the right side of the channels.
255 | m_autoSizedPanelWidth += Constants::kDefaultHeaderLabelWidth + Constants::kLabelStripLeftPadding; // Add master fader width (incl. padding).
256 | }
257 | //==============================================================================
258 |
259 | void MetersComponent::mouseDoubleClick (const juce::MouseEvent& /*event*/)
260 | {
261 | if (m_meterOptions.faderEnabled)
262 | return;
263 |
264 | resetPeakHold();
265 | }
266 | //==============================================================================
267 |
268 | #if SDTK_ENABLE_FADER
269 |
270 | //==============================================================================
271 |
272 | void MetersComponent::mouseExit (const juce::MouseEvent& /*event*/)
273 | {
274 | showFaders (false);
275 | }
276 | //==============================================================================
277 |
278 | void MetersComponent::mouseEnter (const juce::MouseEvent& /*event*/)
279 | {
280 | if (m_meterChannels.isEmpty())
281 | return;
282 |
283 | if (!m_meterChannels[0]->isMinimalModeActive())
284 | {
285 | for (auto* meter: m_meterChannels)
286 | if (meter)
287 | meter->showFader (true);
288 | m_labelStrip.showFader (true);
289 | }
290 | }
291 | //==============================================================================
292 |
293 | void MetersComponent::faderChanged (MeterChannel::Ptr sourceChannel)
294 | {
295 | jassert (m_faderGains.size() == m_faderGainsBuffer.size());
296 | if (m_faderGains.size() != m_faderGainsBuffer.size())
297 | return;
298 |
299 | // Master strip fader moves all channel faders relatively to each other...
300 | if (sourceChannel == &m_labelStrip)
301 | {
302 | // If the master fader is ACTIVE ...
303 | if (m_labelStrip.isActive())
304 | {
305 | // ... but all meters are muted ...
306 | if (areAllMetersInactive())
307 | muteAll (false); // ... un- mute all meters ...
308 |
309 | // Apply the master fader VALUE to all meter faders ...
310 | for (auto* meter: m_meterChannels)
311 | {
312 | if (meter)
313 | {
314 | auto meterIdx = static_cast (m_meterChannels.indexOf (meter));
315 | if (juce::isPositiveAndBelow (meterIdx, m_faderGains.size()))
316 | {
317 | m_faderGains[meterIdx] = m_faderGainsBuffer[meterIdx] * sourceChannel->getFaderValue(); // Multiply the gain with the master fader value.
318 | meter->setFaderValue (m_faderGains[meterIdx], NotificationOptions::dontNotify, false); // Update the fader to display the new gain value.
319 | }
320 | }
321 | }
322 | }
323 | // If the master fader has been DE-ACTIVATED ...
324 | else
325 | {
326 | muteAll(); // ... mute all meters ...
327 | m_labelStrip.setFaderValue (1.0f, NotificationOptions::dontNotify, false); // ... and set the master fader to unity gain.
328 | }
329 | }
330 | // Any meter/fader but the master fader was moved ...
331 | else
332 | {
333 | m_labelStrip.setFaderValue (1.0f, NotificationOptions::dontNotify, false); // ... reset the master fader.
334 | assembleFaderGains (NotificationOptions::dontNotify);
335 | }
336 |
337 | notifyListeners();
338 | }
339 | //==============================================================================
340 |
341 | void MetersComponent::channelSolo (MeterChannel::Ptr sourceChannel)
342 | {
343 | bool alreadySoloed { true };
344 | for (auto* meter: m_meterChannels)
345 | {
346 | if (meter)
347 | {
348 | if (meter != sourceChannel && meter->isActive())
349 | {
350 | meter->setActive (false, NotificationOptions::dontNotify);
351 | alreadySoloed = false;
352 | }
353 | }
354 | }
355 | if (alreadySoloed)
356 | {
357 | for (auto* meter: m_meterChannels)
358 | if (meter)
359 | meter->setActive (true, NotificationOptions::dontNotify);
360 | }
361 |
362 | assembleFaderGains (NotificationOptions::notify);
363 | }
364 | //==============================================================================
365 |
366 | void MetersComponent::setFaderValues (const std::vector& faderValues, NotificationOptions notificationOption /*= NotificationOptions::dontNotify*/)
367 | {
368 | for (size_t meterIdx = 0; meterIdx < static_cast (m_meterChannels.size()); ++meterIdx)
369 | {
370 | if (meterIdx < faderValues.size())
371 | m_meterChannels[static_cast (meterIdx)]->setFaderValue (faderValues[meterIdx], notificationOption);
372 | }
373 |
374 | m_faderGains = faderValues;
375 | }
376 | //==============================================================================
377 |
378 | void MetersComponent::assembleFaderGains (NotificationOptions notificationOption /*= NotificationOptions::notify*/)
379 | {
380 | if (m_meterChannels.isEmpty())
381 | return;
382 |
383 | // Set number of mixer gains to match the number of channels...
384 | jassert (static_cast (m_faderGains.size()) == m_meterChannels.size()); // NOLINT
385 |
386 | for (int channelIdx = 0; channelIdx < static_cast (m_meterChannels.size()); ++channelIdx)
387 | {
388 | // If the meter is active, get the value from the fader, otherwise a value of 0.0 is used...
389 | m_faderGains[(size_t) channelIdx] = (m_meterChannels[channelIdx]->isActive() ? m_meterChannels[channelIdx]->getFaderValue() : 0.0f);
390 | }
391 |
392 | // If all meters are in-active, so is the master fader ...
393 | m_labelStrip.setActive (!areAllMetersInactive(), NotificationOptions::dontNotify);
394 |
395 | m_faderGainsBuffer = m_faderGains;
396 |
397 | if (notificationOption == NotificationOptions::notify)
398 | notifyListeners();
399 | }
400 | //==============================================================================
401 |
402 | juce::String MetersComponent::serializeFaderGains()
403 | {
404 | assembleFaderGains (NotificationOptions::dontNotify);
405 |
406 | juce::StringArray faderGains {};
407 | for (const auto& gain: m_faderGains)
408 | faderGains.add (juce::String (gain));
409 |
410 | return faderGains.joinIntoString ("|");
411 | }
412 | //==============================================================================
413 |
414 | void MetersComponent::notifyListeners()
415 | {
416 | Component::BailOutChecker checker (this);
417 |
418 | if (checker.shouldBailOut())
419 | return;
420 |
421 | m_fadersListeners.callChecked (checker, [&] (FadersChangeListener& l) { l.fadersChanged (m_faderGains); });
422 |
423 | if (checker.shouldBailOut())
424 | return;
425 | }
426 | //==============================================================================
427 |
428 | void MetersComponent::showFaders (bool mustShowFaders)
429 | {
430 | m_labelStrip.showFader (mustShowFaders);
431 | for (auto* meter: m_meterChannels)
432 | meter->showFader (mustShowFaders);
433 | }
434 | //==============================================================================
435 |
436 | bool MetersComponent::areAllMetersInactive()
437 | {
438 | for (const auto* meter: m_meterChannels)
439 | if (meter->isActive())
440 | return false;
441 | return true;
442 | }
443 | //==============================================================================
444 |
445 | void MetersComponent::toggleMute()
446 | {
447 | const bool allChannelsInactive = areAllMetersInactive();
448 | muteAll (!allChannelsInactive);
449 | }
450 | //==============================================================================
451 |
452 | void MetersComponent::muteAll (bool mute /*= true */)
453 | {
454 | const bool allChannelsInactive = areAllMetersInactive();
455 | if (mute == allChannelsInactive)
456 | return; // All meters already muted.
457 |
458 | for (auto* meter: m_meterChannels)
459 | {
460 | meter->setActive (!mute);
461 | meter->flashFader();
462 | }
463 | assembleFaderGains();
464 | }
465 | //==============================================================================
466 |
467 | void MetersComponent::resetFaders()
468 | {
469 | if (std::any_of (m_faderGains.begin(), m_faderGains.end(), [] (auto gain) { return gain != 1.0f; }))
470 | {
471 | std::fill (m_faderGains.begin(), m_faderGains.end(), 1.0f); // Set all fader gains to unity.
472 | notifyListeners();
473 | }
474 | m_faderGainsBuffer = m_faderGains; // Copy the just reset fader gains to it's buffer.
475 |
476 | // Activate (un-mute) all faders and set them to unity gain...
477 | for (auto* meter: m_meterChannels)
478 | {
479 | meter->setActive (true);
480 | meter->setFaderValue (1.0f);
481 | meter->flashFader();
482 | }
483 | m_labelStrip.setActive (true);
484 | m_labelStrip.setFaderValue (1.0f);
485 | m_labelStrip.flashFader();
486 | }
487 | //==============================================================================
488 |
489 | void MetersComponent::setFadersEnabled (bool faderEnabled)
490 | {
491 | for (auto* meter: m_meterChannels)
492 | meter->enableFader (faderEnabled);
493 | m_labelStrip.enableFader (faderEnabled);
494 | m_meterOptions.faderEnabled = faderEnabled;
495 | }
496 | //==============================================================================
497 |
498 | void MetersComponent::flashFaders()
499 | {
500 | for (auto* meter: m_meterChannels)
501 | meter->flashFader();
502 |
503 | m_labelStrip.flashFader();
504 | }
505 |
506 | #endif /* SDTK_ENABLE_FADER */
507 |
508 | //==============================================================================
509 |
510 | void MetersComponent::setNumChannels (int numChannels, const std::vector& channelNames /*= {}*/)
511 | {
512 | if (numChannels <= 0)
513 | return;
514 |
515 | setChannelFormat (juce::AudioChannelSet::canonicalChannelSet (numChannels), channelNames);
516 | }
517 | //==============================================================================
518 |
519 | void MetersComponent::setChannelFormat (const juce::AudioChannelSet& channels, const std::vector& channelNames)
520 | {
521 | if (channels.size() <= 0)
522 | return;
523 |
524 | m_channelFormat = channels;
525 |
526 | // Make sure the number of meters matches the number of channels ...
527 | if (channels.size() != m_meterChannels.size())
528 | {
529 | deleteMeters(); // ... if not, then delete all previous meters ...
530 | createMeters (channels, channelNames); // ... and create new ones, matching the required channel format.
531 | }
532 |
533 | // Set the channel names...
534 | setChannelNames (channelNames);
535 |
536 | // Resize the mixer to accommodate any optionally added meters...
537 | resized();
538 |
539 | #if SDTK_ENABLE_FADER
540 |
541 | // Make sure the number of mixer gains matches the number of channels ...
542 | const auto numFaderGains = static_cast (m_faderGains.size());
543 |
544 | if (channels.size() != numFaderGains)
545 | {
546 | if (numFaderGains > channels.size() || m_faderGains.empty())
547 | {
548 | m_faderGains.resize (static_cast (channels.size()), 1.0f); // ... and if not resize the mixer gains to accommodate.
549 | m_faderGainsBuffer.resize (static_cast (channels.size()), 1.0f);
550 | }
551 | else
552 | {
553 | const auto numChannelsToAdd = static_cast (channels.size() - numFaderGains);
554 | const auto lastGain = m_faderGains.back();
555 | const auto lastBufferedGain = m_faderGainsBuffer.back();
556 |
557 | m_faderGains.insert (m_faderGains.end(), numChannelsToAdd, lastGain);
558 | m_faderGainsBuffer.insert (m_faderGainsBuffer.end(), numChannelsToAdd, lastBufferedGain);
559 | }
560 | }
561 |
562 | setFaderValues (m_faderGains);
563 |
564 | #endif /* SDTK_ENABLE_FADER */
565 | }
566 |
567 | //==============================================================================
568 |
569 | void MetersComponent::setInputLevel (int channel, float value)
570 | {
571 | if (auto* meterChannel = getMeterChannel (channel))
572 | if (meterChannel)
573 | meterChannel->setInputLevel (value);
574 | }
575 | //==============================================================================
576 |
577 | void MetersComponent::createMeters (const juce::AudioChannelSet& channelFormat, const std::vector& channelNames)
578 | {
579 | // Create enough meters to match the channel format...
580 | for (int channelIdx = 0; channelIdx < channelFormat.size(); ++channelIdx)
581 | {
582 | auto meterChannel = std::make_unique (m_meterOptions, Padding (0, Constants::kFaderRightPadding, 0, 0), Constants::kMetersId, false,
583 | channelFormat.getTypeOfChannel (channelIdx));
584 |
585 | #if SDTK_ENABLE_FADER
586 | meterChannel->onFaderMove = [this] (MeterChannel* channel) { faderChanged (channel); };
587 | meterChannel->onChannelSolo = [this] (MeterChannel* channel) { channelSolo (channel); };
588 | meterChannel->onMixerReset = [this]() { resetFaders(); };
589 | #endif
590 |
591 | meterChannel->setFont (m_font);
592 | meterChannel->addMouseListener (this, true);
593 |
594 | addChildComponent (meterChannel.get());
595 | m_meterChannels.add (meterChannel.release());
596 |
597 | m_labelStrip.setActive (true);
598 | }
599 |
600 | setChannelNames (channelNames);
601 | setMeterSegments (m_segmentsOptions);
602 | }
603 | //==============================================================================
604 |
605 | void MetersComponent::deleteMeters()
606 | {
607 | #if SDTK_ENABLE_FADER
608 | for (auto* meter: m_meterChannels)
609 | if (meter)
610 | meter->removeMouseListener (this);
611 | #endif
612 |
613 | m_meterChannels.clear();
614 | }
615 | //==============================================================================
616 |
617 | MeterChannel* MetersComponent::getMeterChannel (const int meterIndex) noexcept
618 | {
619 | return (juce::isPositiveAndBelow (meterIndex, m_meterChannels.size()) ? m_meterChannels[meterIndex] : nullptr);
620 | }
621 | //==============================================================================
622 |
623 | void MetersComponent::resetMeters()
624 | {
625 | for (auto* meter: m_meterChannels)
626 | if (meter)
627 | meter->reset();
628 | }
629 | //==============================================================================
630 |
631 | void MetersComponent::resetPeakHold()
632 | {
633 | for (auto* meter: m_meterChannels)
634 | if (meter)
635 | meter->resetPeakHold();
636 | }
637 | //==============================================================================
638 |
639 | void MetersComponent::setDecay (float decay_ms)
640 | {
641 | m_meterOptions.decayTime_ms = decay_ms;
642 | for (auto* meter: m_meterChannels)
643 | if (meter)
644 | meter->setDecay (decay_ms);
645 | }
646 | //==============================================================================
647 |
648 | void MetersComponent::setFont (const juce::Font& newFont)
649 | {
650 | m_font = newFont;
651 | for (auto* meter: m_meterChannels)
652 | if (meter)
653 | meter->setFont (m_font);
654 | m_labelStrip.setFont (m_font);
655 | }
656 | //==============================================================================
657 |
658 | void MetersComponent::setOptions (const Options& meterOptions)
659 | {
660 | m_meterOptions = meterOptions;
661 | for (auto* meter: m_meterChannels)
662 | {
663 | if (meter)
664 | meter->setOptions (meterOptions);
665 | }
666 | m_labelStrip.setOptions (meterOptions);
667 |
668 | setRefreshRate (meterOptions.refreshRate);
669 | }
670 | //==============================================================================
671 |
672 | void MetersComponent::enable (bool enabled /*= true*/)
673 | {
674 | m_meterOptions.enabled = enabled;
675 |
676 | for (auto* meter: m_meterChannels)
677 | {
678 | if (meter)
679 | {
680 | meter->setEnabled (enabled);
681 | meter->setVisible (enabled);
682 | }
683 | }
684 |
685 | m_labelStrip.setEnabled (enabled);
686 | m_labelStrip.setVisible (enabled);
687 |
688 | refresh (true);
689 | }
690 | //==============================================================================
691 |
692 | void MetersComponent::showTickMarks (bool showTickMarks)
693 | {
694 | m_meterOptions.tickMarksEnabled = showTickMarks;
695 | for (auto* meter: m_meterChannels)
696 | if (meter)
697 | meter->showTickMarks (showTickMarks);
698 |
699 | m_labelStrip.showTickMarks (showTickMarks);
700 | }
701 | //==============================================================================
702 |
703 | bool MetersComponent::isMinimalModeActive() const noexcept
704 | {
705 | if (m_meterChannels.isEmpty())
706 | return true;
707 |
708 | return m_meterChannels[0]->isMinimalModeActive();
709 | }
710 | //==============================================================================
711 |
712 | void MetersComponent::useGradients (bool useGradients)
713 | {
714 | for (auto* meter: m_meterChannels)
715 | if (meter)
716 | meter->useGradients (useGradients);
717 | }
718 | //==============================================================================
719 |
720 | void MetersComponent::setLabelStripPosition (LabelStripPosition labelStripPosition)
721 | {
722 | m_labelStripPosition = labelStripPosition;
723 | resized();
724 | }
725 | //==============================================================================
726 |
727 | void MetersComponent::showHeader (bool showHeader)
728 | {
729 | if (m_meterOptions.headerEnabled == showHeader)
730 | return;
731 |
732 | m_meterOptions.headerEnabled = showHeader;
733 | m_labelStrip.showHeader (showHeader);
734 |
735 | for (auto* meter: m_meterChannels)
736 | if (meter)
737 | meter->showHeader (showHeader);
738 |
739 | resized();
740 | }
741 | //==============================================================================
742 |
743 | void MetersComponent::showValue (bool showValue)
744 | {
745 | if (m_meterOptions.valueEnabled == showValue)
746 | return;
747 |
748 | m_labelStrip.showValue (showValue);
749 |
750 | for (auto* meter: m_meterChannels)
751 | if (meter)
752 | meter->showValue (showValue);
753 |
754 | resized();
755 | }
756 | //==============================================================================
757 |
758 | void MetersComponent::setMeterSegments (const std::vector& segmentsOptions)
759 | {
760 | m_segmentsOptions = segmentsOptions;
761 |
762 | m_labelStrip.setMeterSegments (segmentsOptions);
763 |
764 | for (auto* meter: m_meterChannels)
765 | if (meter)
766 | meter->setMeterSegments (m_segmentsOptions);
767 | }
768 | //==============================================================================
769 |
770 | void MetersComponent::setColours()
771 | {
772 | if (isColourSpecified (MeterChannel::backgroundColourId))
773 | m_backgroundColour = findColour (MeterChannel::backgroundColourId);
774 | else if (getLookAndFeel().isColourSpecified (MeterChannel::backgroundColourId))
775 | m_backgroundColour = getLookAndFeel().findColour (MeterChannel::backgroundColourId);
776 | }
777 | } // namespace SoundMeter
778 | } // namespace sd
779 |
--------------------------------------------------------------------------------
/meter/sd_MetersComponent.h:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2025 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | #pragma once
34 |
35 | #include "sd_MeterChannel.h"
36 | #include "sd_MeterHelpers.h"
37 |
38 | #include
39 | #include
40 | #include
41 | #include
42 |
43 | /**
44 | * @brief Namespace containing all concerning the sound_meter module.
45 | */
46 | namespace sd // NOLINT
47 | {
48 | namespace SoundMeter
49 | {
50 | class MeterChannel;
51 |
52 | /**
53 | * @brief Component containing one or more meters.
54 | *
55 | * After setting the channel format it
56 | * will automatically create the needed meters and give them proper names.
57 | */
58 | class MetersComponent final
59 | : public juce::Component
60 | , private juce::Timer
61 | {
62 | public:
63 | /**
64 | * @brief Default constructor.
65 | */
66 | MetersComponent();
67 |
68 | /**
69 | * @brief Constructor with meter options.
70 | *
71 | * @param meterOptions The options to use with the meters and the label strip.
72 | */
73 | explicit MetersComponent (const Options& meterOptions);
74 |
75 | /**
76 | * @brief Constructor which accepts a channel format.
77 | *
78 | * This constructor will automatically setup the panel with the
79 | * right amount of meters, named according to the channel format.
80 | *
81 | * @param channelFormat The channel format to use to initialise the panel.
82 | */
83 | explicit MetersComponent (const juce::AudioChannelSet& channelFormat);
84 |
85 | /**
86 | * @brief Constructor with meter options and which accepts a channel format.
87 | *
88 | * This constructor will automatically setup the panel with the
89 | * right amount of meters, named according to the channel format.
90 | *
91 | * @param meterOptions The options to use with the meters and the label strip.
92 | * @param channelFormat The channel format to use to initialise the panel.
93 | */
94 | MetersComponent (const Options& meterOptions, const juce::AudioChannelSet& channelFormat);
95 |
96 | /** @brief Destructor.*/
97 | ~MetersComponent() override;
98 |
99 | /**
100 | * @brief Refresh (redraw) the meters panel.
101 | *
102 | * This can be called manually or internally (see useInternalTiming).
103 | *
104 | * @param forceRefresh When set to true, always redraw the meters panel (not only if it's dirty/changed).
105 | *
106 | * @see setRefreshRate, useInternalTiming
107 | */
108 | void refresh (bool forceRefresh = false);
109 |
110 | /**
111 | * @brief Reset the meters.
112 | *
113 | * Initialise the meters, faders and clears all the levels (but not preserves the peak hold).
114 | *
115 | * @see resetPeakHold, resetMeters
116 | */
117 | void reset();
118 |
119 | /**
120 | * @brief Reset all meters.
121 | *
122 | * Resets all meters to 0 (but not the peak hold).
123 | * @see reset, resetPeakHold
124 | */
125 | void resetMeters();
126 |
127 | /**
128 | * @brief Clear the level of the meters.
129 | */
130 | void clearMeters();
131 |
132 | /**
133 | * @brief Reset all peak hold indicators and 'values'.
134 | *
135 | * @see reset, resetMeters
136 | */
137 | void resetPeakHold();
138 |
139 | /**
140 | * @brief Set the input level.
141 | *
142 | * This supplies a meter of a specific channel with the peak level from the audio engine.
143 | * Beware: this will usually be called from the audio thread.
144 | *
145 | * @param channel The channel to set the input level of.
146 | * @param value The input level to set to the specified channel.
147 | */
148 | void setInputLevel (int channel, float value);
149 |
150 | /**
151 | * @brief Set the number of channels (meters) in the panel.
152 | *
153 | * @param numChannels The number of channels (meters).
154 | * @param channelNames The (optional) channel names to use in the header of the meters.
155 | *
156 | * @see getNumChannels, setChannelFormat
157 | */
158 | void setNumChannels (int numChannels, const std::vector& channelNames = {});
159 |
160 | /**
161 | * @brief Set the channel format (number of channels) to use for the mixer/meters.
162 | *
163 | * @param channels The channel format to use.
164 | * @param channelNames The (optional) channel names to use in the header of the meters.
165 | *
166 | * @see setNumChannels, getNumChannels
167 | */
168 | void setChannelFormat (const juce::AudioChannelSet& channels, const std::vector& channelNames = {});
169 |
170 | /**
171 | * @brief Get the number of meters in the panel.
172 | *
173 | * @return The number of meters in the panel.
174 | *
175 | * @see setNumChannels, setChannelFormat
176 | */
177 | [[nodiscard]] int getNumChannels() const noexcept { return m_meterChannels.size(); }
178 |
179 | /**
180 | * @brief Get the default meters panel width.
181 | *
182 | * Get's the default panel width in pixels.
183 | * This is the width where all channels are wide enough to display
184 | * all channel names.
185 | *
186 | * @return The default panel width (in pixels).
187 | */
188 | [[nodiscard]] int getAutoSizedPanelWidth() const noexcept { return m_autoSizedPanelWidth; }
189 |
190 | /**
191 | * @brief Set the channel names to display above the meters.
192 | *
193 | * @param channelNames The list of names to use for the meters.
194 | */
195 | void setChannelNames (const std::vector& channelNames);
196 |
197 | /**
198 | * @brief Set meter options defining appearance and functionality.
199 | *
200 | * @param meterOptions The options to apply to the meters and label strip.
201 | */
202 | void setOptions (const Options& meterOptions);
203 |
204 | /**
205 | * @brief Get the meter options defining appearance and functionality.
206 | *
207 | * @return The options to apply to the meters and label strip.
208 | */
209 | const Options& getOptions() const noexcept { return m_meterOptions; }
210 |
211 | /**
212 | * @brief Set the refresh (redraw) rate of the meters.
213 | *
214 | * Also used for meter ballistics.
215 | * When using the internal timer (setInternalTiming) this set's it's refresh rate.
216 | * When manually redrawing (with refresh) you could (should) still provide the refresh rate
217 | * to optimize a smooth decay.
218 | *
219 | * @param refreshRate The refresh rate (in Hz).
220 | *
221 | * @see setDecay, refresh, setInternalTiming
222 | */
223 | void setRefreshRate (float refreshRate);
224 |
225 | /**
226 | * @brief Set the timing option to use (internal/external).
227 | *
228 | * When using internal timing, the panel will redraw (refresh) the meters automatically
229 | * using the refresh rate specified in setPanelRefreshRate.
230 | * When using external timing, the user has to do this manually with the 'refresh' method.
231 | *
232 | * @param useInternalTiming When set to true, the meter panel will update itself.
233 | *
234 | * @see refresh, setPanelRefreshRate
235 | */
236 | void useInternalTiming (bool useInternalTiming) noexcept;
237 |
238 | /**
239 | * @brief Set the segments the meter is made out of.
240 | *
241 | * All segments have a level range, a range within the meter and a colour (or gradient).
242 | *
243 | * @param segmentsOptions The segments options to create the segments with.
244 | */
245 | void setMeterSegments (const std::vector& segmentsOptions);
246 |
247 | /**
248 | * @brief Set meter decay rate.
249 | *
250 | * Set's the meter's decay rate in milliseconds.
251 | * The meter's attack is instant.
252 | *
253 | * @param decay_ms The time it takes the meter to decay (in ms).
254 | *
255 | * @see setRefreshRate
256 | */
257 | void setDecay (float decay_ms);
258 |
259 | /**
260 | * @brief Use gradients instead of hard segment boundaries.
261 | *
262 | * @param useGradients When set to true, uses smooth gradients. False gives hard segment boundaries.
263 | */
264 | void useGradients (bool useGradients);
265 |
266 | /**
267 | * @brief Set the position of the label-strip relative to the meters.
268 | *
269 | * @param labelStripPosition The position of the label-strip relative to the meters.
270 | */
271 | void setLabelStripPosition (LabelStripPosition labelStripPosition);
272 |
273 | /**
274 | * @brief Show the 'header' part above the meters.
275 | *
276 | * This will display the channel name (a custom one that the user can set)
277 | * or the channel type (left, right, center, etc...).
278 | * This also doubles as a mute button for the specific channel.
279 | *
280 | * @param showHeader When set to true, the 'header' part will be shown.
281 | * @see showValue, setChannelNames
282 | */
283 | void showHeader (bool showHeader);
284 |
285 | /**
286 | * @brief Show the 'value' part below the meters.
287 | *
288 | * This will display the peak value, in decibels, below the meter.
289 | * The level displayed here matches the peak level indicator on the meter.
290 | * Double clicking will reset the peak hold value (as well as the indicator).
291 | *
292 | * @param showValue When set to true, the 'value' part will be shown.
293 | * @see showHeader, resetPeakHold
294 | */
295 | void showValue (bool showValue);
296 |
297 | /**
298 | * @brief Set the font to be used in the panel and it's meters.
299 | * @param font The font to use.
300 | */
301 | void setFont (const juce::Font& font);
302 |
303 | /**
304 | * @brief Enable or disable the panel.
305 | *
306 | * @param enabled When set to true, the meters panel will be displayed.
307 | */
308 | void enable (bool enabled = true);
309 |
310 | /**
311 | * @brief Show tick-marks (divider lines) on the meter.
312 | *
313 | * A tick mark is a horizontal line, dividing the meter.
314 | * This is also the place the label strip will put it's text values.
315 | *
316 | * @param showTickMarks When set true, shows the tick-marks.
317 | */
318 | void showTickMarks (bool showTickMarks);
319 |
320 | /**
321 | * @brief Check if the minimal mode is active.
322 | * @return True, if the minimal mode is active.
323 | */
324 | [[nodiscard]] bool isMinimalModeActive() const noexcept;
325 |
326 | #if SDTK_ENABLE_FADER
327 |
328 | /**
329 | * @brief Show (or hide) all the faders.
330 | *
331 | * @param showMixer When set to true, will show the faders. Otherwise it will hide them.
332 | */
333 | void showFaders (bool showMixer);
334 |
335 | /**
336 | * @brief Show the faders briefly and fade out (unless overridden and shown longer).
337 | */
338 | void flashFaders();
339 |
340 | /**
341 | * @brief Enable the faders on the meters.
342 | *
343 | * @param faderEnabled When set to true, the faders are enabled.
344 | */
345 | void setFadersEnabled (bool faderEnabled);
346 |
347 | /**
348 | * @brief Assemble all the gain values from the faders.
349 | *
350 | * @param notificationOption Set whether to notify the listeners of the gathered fader values.
351 | * @see notifyListeners()
352 | */
353 | void assembleFaderGains (NotificationOptions notificationOption = NotificationOptions::notify);
354 |
355 | /**
356 | * @brief Get a string representing the fader gains.
357 | */
358 | [[nodiscard]] juce::String serializeFaderGains();
359 |
360 | /**
361 | * @brief Set the values of all channel faders.
362 | *
363 | * @param faderValues A list of all values.
364 | * @param notificationOption Set whether to notify the listeners of the gathered fader values.
365 | */
366 | void setFaderValues (const std::vector& faderValues, NotificationOptions notificationOption = NotificationOptions::dontNotify);
367 |
368 | /**
369 | * @brief Get the master fader.
370 | *
371 | * Get a reference to the master fader component.
372 | *
373 | * @return A reference to the master fader component.
374 | */
375 | const MeterChannel& getMasterFader() const noexcept { return m_labelStrip; }
376 |
377 | /**
378 | * @brief Set all faders to unity gain.
379 | */
380 | void resetFaders();
381 |
382 | /**
383 | * @brief Toggle mute channels.
384 | *
385 | * Mute all channels, or if all were muted, un-mute all.
386 | *
387 | * @see muteAll
388 | */
389 | void toggleMute();
390 |
391 | /**
392 | * @brief Mute (or un-mute) all meters.
393 | *
394 | * @param mute When set to true, all meters will be muted.
395 | * @see toggleMute
396 | */
397 | void muteAll (bool mute = true);
398 |
399 | /**
400 | * Check if all channels have been set inactive..
401 | *
402 | * @return True, if all channels are inactive (muted).
403 | */
404 | [[nodiscard]] bool areAllMetersInactive();
405 |
406 | /**
407 | * @brief A listener to any fader changes in the meters panel.
408 | */
409 | struct FadersChangeListener
410 | {
411 | virtual ~FadersChangeListener() = default;
412 |
413 | /**
414 | * @brief This virtual function gets called when a fader has changed.
415 | * @param faderValues All fader values.
416 | */
417 | virtual void fadersChanged (std::vector faderValues) = 0;
418 | };
419 |
420 | /**
421 | * @brief Add a listener to any fader movement in the panel.
422 | *
423 | * @param listener The listener to add.
424 | *
425 | * @see removeFadersListener
426 | */
427 | void addFadersListener (FadersChangeListener& listener) { m_fadersListeners.add (&listener); }
428 |
429 | /**
430 | * @brief Remove listener to any fader movement in the panel.
431 | *
432 | * @param listener The listener to remove.
433 | *
434 | * @see addFadersListener
435 | */
436 | void removeFadersListener (FadersChangeListener& listener) { m_fadersListeners.remove (&listener); }
437 |
438 | #endif /* SDTK_ENABLE_FADER */
439 |
440 | /** @internal */
441 | void visibilityChanged() override { setColours(); }
442 | void lookAndFeelChanged() override { setColours(); }
443 | void paint (juce::Graphics& g) override;
444 | void resized() override;
445 |
446 | private:
447 | // clang-format off
448 |
449 | Options m_meterOptions {};
450 | std::vector m_segmentsOptions = MeterScales::getDefaultScale();
451 |
452 | using MetersType = juce::OwnedArray;
453 | MetersType m_meterChannels {};
454 | MeterChannel m_labelStrip {};
455 | LabelStripPosition m_labelStripPosition = LabelStripPosition::right;
456 |
457 | juce::AudioChannelSet m_channelFormat = juce::AudioChannelSet::stereo();
458 |
459 | bool m_useInternalTimer = true;
460 | juce::Font m_font;
461 | int m_autoSizedPanelWidth = 0;
462 |
463 | juce::Colour m_backgroundColour = juce::Colours::black;
464 |
465 | void mouseDoubleClick (const juce::MouseEvent& event) override;
466 |
467 | #if SDTK_ENABLE_FADER
468 |
469 | // Fader members and methods...
470 | using FadersListenerList = juce::ListenerList; // List of listeners to fader changes.
471 |
472 | FadersListenerList m_fadersListeners; // List of listeners to fader changes.
473 | std::vector m_faderGainsBuffer;
474 | std::vector m_faderGains;
475 |
476 | void notifyListeners (); // Notify the listeners the faders have been moved.
477 | void mouseEnter (const juce::MouseEvent& event) override;
478 | void mouseExit (const juce::MouseEvent& event) override;
479 | void faderChanged (MeterChannel::Ptr sourceChannel);
480 | void channelSolo (MeterChannel::Ptr sourceChannel);
481 |
482 | #endif
483 |
484 | // Private methods...
485 | void timerCallback () override { refresh(); }
486 | void setColours ();
487 | void createMeters (const juce::AudioChannelSet& channelFormat, const std::vector& channelNames);
488 | void deleteMeters ();
489 | [[nodiscard]] MeterChannel* getMeterChannel (int meterIndex) noexcept;
490 |
491 |
492 | // clang-format on
493 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MetersComponent)
494 | };
495 | } // namespace SoundMeter
496 | } // namespace sd
497 |
--------------------------------------------------------------------------------
/sound_meter.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2021 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | // TODO: - LnF methods.
34 |
35 |
36 | #ifdef SD_SOUND_METER_H_INCLUDED
37 | /** When you add this cpp file to your project, you mustn't include it in a file where you've
38 | already included any other headers - just put it inside a file on its own, possibly with your config
39 | flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
40 | header files that the compiler may be using.
41 | */
42 | #error "Incorrect use of sound_meter cpp file"
43 | #endif
44 |
45 | #include "sound_meter.h"
46 |
47 | #include "meter/sd_MeterHelpers.cpp"
48 | #include "meter/sd_MeterSegment.cpp"
49 | #include "meter/sd_MeterLevel.cpp"
50 | #include "meter/sd_MeterFader.cpp"
51 | #include "meter/sd_MeterHeader.cpp"
52 | #include "meter/sd_MeterChannel.cpp"
53 | #include "meter/sd_MetersComponent.cpp"
--------------------------------------------------------------------------------
/sound_meter.h:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file is part of the sound_meter JUCE module
5 | Copyright (c) 2019 - 2021 Sound Development - Marcel Huibers
6 | All rights reserved.
7 |
8 | ------------------------------------------------------------------------------
9 |
10 | sound_meter is provided under the terms of The MIT License (MIT):
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy
13 | of this software and associated documentation files (the "Software"), to deal
14 | in the Software without restriction, including without limitation the rights
15 | to use, copy, modify, merge, publish, distribute, sub-license, and/or sell
16 | copies of the Software, and to permit persons to whom the Software is
17 | furnished to do so, subject to the following conditions:
18 |
19 | The above copyright notice and this permission notice shall be included in all
20 | copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 | SOFTWARE.
29 |
30 | ==============================================================================
31 | */
32 |
33 | /*******************************************************************************
34 | The block below describes the properties of this module, and is read by
35 | the Projucer to automatically generate project code that uses it.
36 | For details about the syntax and how to create or use a module, see the
37 | JUCE Module Format.txt file.
38 |
39 | #if 0
40 | BEGIN_JUCE_MODULE_DECLARATION
41 |
42 | ID: sound_meter
43 | vendor: Sound Development
44 | name: Resize-able peak meter module with fader and container panel.
45 | version: 0.9.0
46 | description: Resize-able logarithmic peak meter JUCE module, with an (optional) fader and accompanying container panel (mixer).
47 | website: https://www.sounddevelopment.nl
48 | license: MIT
49 | minimumCppStandard: 14
50 | dependencies: juce_audio_basics, juce_gui_basics, juce_events, juce_graphics
51 | END_JUCE_MODULE_DECLARATION
52 |
53 | #endif
54 |
55 | @defgroup sound_meter
56 |
57 | This juce module contains a MetersComponent to easily display
58 | peak levels on your gui.
59 |
60 | *******************************************************************************/
61 |
62 | #pragma once
63 |
64 | #define SD_SOUND_METER_H_INCLUDED
65 |
66 | /** Config: SDTK_ENABLE_FADER
67 | Enable a fader, to be overlayed on top of the meter.
68 | */
69 | #ifndef SDTK_ENABLE_FADER
70 | #define SDTK_ENABLE_FADER 0
71 | #endif
72 |
73 | #include
74 | #include
75 | #include
76 | #include
77 |
78 | #include "meter/sd_MeterHelpers.h"
79 | #include "meter/sd_MeterSegment.h"
80 | #include "meter/sd_MeterLevel.h"
81 | #include "meter/sd_MeterFader.h"
82 | #include "meter/sd_MeterHeader.h"
83 | #include "meter/sd_MeterChannel.h"
84 | #include "meter/sd_MetersComponent.h"
85 |
--------------------------------------------------------------------------------