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