Mema
Memory Matrix — multi-channel audio matrix monitor and router
Loading...
Searching...
No Matches
MeterbridgeComponent.cpp
Go to the documentation of this file.
1/* Copyright (c) 2024-2025, Christian Ahrens
2 *
3 * This file is part of Mema <https://github.com/ChristianAhrens/Mema>
4 *
5 * This tool is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Lesser General Public License version 3.0 as published
7 * by the Free Software Foundation.
8 *
9 * This tool is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12 * details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this tool; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
20
21#include <CustomLookAndFeel.h>
22
23namespace Mema
24{
25
26//==============================================================================
29{
32
33#ifdef DEBUG_LEVELDATA
34 auto p = 0.001f;
35 auto r = 0.01f;
36 auto h = 0.1f;
37
38 Mema::ProcessorLevelData::LevelVal l1(p, r, h, -90.0f);
39 Mema::ProcessorLevelData::LevelVal l2(p, r, h, -10.0f);
40 Mema::ProcessorLevelData::LevelVal l3(p, r, h, -70.0f);
41 Mema::ProcessorLevelData::LevelVal l4(p, r, h, -50.0f);
42
43 auto p1 = l1.GetFactorPEAKdB();
44 auto r1 = l1.GetFactorRMSdB();
45 auto h1 = l1.GetFactorHOLDdB();
46
47 auto p2 = l2.GetFactorPEAKdB();
48 auto r2 = l2.GetFactorRMSdB();
49 auto h2 = l2.GetFactorHOLDdB();
50
51 auto p3 = l3.GetFactorPEAKdB();
52 auto r3 = l3.GetFactorRMSdB();
53 auto h3 = l3.GetFactorHOLDdB();
54
55 auto p4 = l4.GetFactorPEAKdB();
56 auto r4 = l4.GetFactorRMSdB();
57 auto h4 = l4.GetFactorHOLDdB();
58#endif
59}
60
66
71
73{
74 // (Our component is opaque, so we must completely fill the background with a solid colour)
75 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
76
77 // calculate what we need for our center circle
78 auto visuAreaWidth = static_cast<float>(getWidth());
79 auto visuAreaHeight = static_cast<float>(getHeight());
80
81 auto channelCount = static_cast<unsigned long>(m_channelCount);
82
83 if (m_direction == Direction::Horizontal)
84 {
85 auto margin = getWidth() / ((2 * channelCount) + 1);
86
87 auto visuArea = getLocalBounds();
88 auto visuAreaOrigY = visuAreaHeight;
89
90 // draw meters
91 auto meterSpacing = margin;
92 auto meterThickness = float(visuArea.getWidth() - (channelCount) * meterSpacing) / float(channelCount);
93 auto meterMaxLength = visuArea.getHeight();
94 auto meterLeft = 0.5f * meterSpacing;
95
96 g.setFont(14.0f);
97 for (unsigned long i = 1; i <= channelCount; ++i)
98 {
99 auto level = m_levelData.GetLevel(i);
100 float peakMeterLength{ 0 };
101 float rmsMeterLength{ 0 };
102 float holdMeterLength{ 0 };
103 if (getUsesValuesInDB())
104 {
105 peakMeterLength = meterMaxLength * level.GetFactorPEAKdB();
106 rmsMeterLength = meterMaxLength * level.GetFactorRMSdB();
107 holdMeterLength = meterMaxLength * level.GetFactorHOLDdB();
108 }
109 else
110 {
111 peakMeterLength = meterMaxLength * level.peak;
112 rmsMeterLength = meterMaxLength * level.rms;
113 holdMeterLength = meterMaxLength * level.hold;
114 }
115
116 // peak bar
117 g.setColour(getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId));
118 g.fillRect(juce::Rectangle<float>(meterLeft, visuAreaOrigY - peakMeterLength, meterThickness, peakMeterLength));
119 // rms bar
120 g.setColour(getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId));
121 g.fillRect(juce::Rectangle<float>(meterLeft, visuAreaOrigY - rmsMeterLength, meterThickness, rmsMeterLength));
122 // hold strip
123 g.setColour(getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringHoldColourId));
124 g.drawLine(juce::Line<float>(meterLeft, visuAreaOrigY - holdMeterLength, meterLeft + meterThickness, visuAreaOrigY - holdMeterLength));
125 // channel # label
126 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
127 g.drawText(juce::String(i), juce::Rectangle<float>(meterLeft - (0.5f * meterSpacing), visuAreaOrigY - float(margin + 2), meterThickness + meterSpacing, float(margin)), juce::Justification::centred);
128
129 meterLeft += meterThickness + meterSpacing;
130 }
131
132 // draw a simple baseline
133 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
134 g.drawLine(juce::Line<float>(0.0f, visuAreaOrigY, visuAreaWidth, visuAreaOrigY));
135 // draw dBFS
136 g.setFont(12.0f);
137 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
138 juce::String rangeText;
139 if (getUsesValuesInDB())
140 rangeText = juce::String(ProcessorDataAnalyzer::getGlobalMindB()) + " ... " + juce::String(ProcessorDataAnalyzer::getGlobalMaxdB()) + " dBFS";
141 else
142 rangeText = "0 ... 1";
143 g.drawText(rangeText, visuArea, juce::Justification::topRight, true);
144 }
145 else
146 {
147 auto margin = getHeight() / ((2 * channelCount) + 1);
148
149 auto visuArea = getLocalBounds();
150 auto visuAreaOrigX = 0.0f;
151
152 // draw meters
153 auto meterSpacing = margin;
154 auto meterThickness = float(visuArea.getHeight() - (channelCount) * meterSpacing) / float(channelCount);
155 auto meterMaxLength = visuArea.getWidth();
156 auto meterTop = 0.5f * meterSpacing;
157
158 g.setFont(14.0f);
159 for (unsigned long i = 1; i <= channelCount; ++i)
160 {
161 auto level = m_levelData.GetLevel(i);
162 float peakMeterLength{ 0 };
163 float rmsMeterLength{ 0 };
164 float holdMeterLength{ 0 };
165 if (getUsesValuesInDB())
166 {
167 peakMeterLength = meterMaxLength * level.GetFactorPEAKdB();
168 rmsMeterLength = meterMaxLength * level.GetFactorRMSdB();
169 holdMeterLength = meterMaxLength * level.GetFactorHOLDdB();
170 }
171 else
172 {
173 peakMeterLength = meterMaxLength * level.peak;
174 rmsMeterLength = meterMaxLength * level.rms;
175 holdMeterLength = meterMaxLength * level.hold;
176 }
177
178 // peak bar
179 g.setColour(getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId));
180 g.fillRect(juce::Rectangle<float>(visuAreaOrigX, meterTop, peakMeterLength, meterThickness));
181 // rms bar
182 g.setColour(getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId));
183 g.fillRect(juce::Rectangle<float>(visuAreaOrigX, meterTop, rmsMeterLength, meterThickness));
184 // hold strip
185 g.setColour(getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringHoldColourId));
186 g.drawLine(juce::Line<float>(visuAreaOrigX + holdMeterLength, meterTop, visuAreaOrigX + holdMeterLength, meterTop + meterThickness));
187 // channel # label
188 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
189 g.drawText(juce::String(i), juce::Rectangle<float>(visuAreaOrigX, meterTop, float(0.5f * meterMaxLength), meterThickness), juce::Justification::centred);
190
191 meterTop += meterThickness + meterSpacing;
192 }
193
194 // draw a simple baseline
195 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
196 g.drawLine(juce::Line<float>(0.0f, 0.0f, 0.0f, visuAreaHeight));
197 // draw dBFS
198 g.setFont(12.0f);
199 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
200 juce::String rangeText;
201 if (getUsesValuesInDB())
202 rangeText = juce::String(ProcessorDataAnalyzer::getGlobalMindB()) + " ... " + juce::String(ProcessorDataAnalyzer::getGlobalMaxdB()) + " dBFS";
203 else
204 rangeText = "0 ... 1";
205
206 g.setOrigin(visuArea.getBottomLeft());
207 g.addTransform(juce::AffineTransform().rotated(90));
208 g.drawText(rangeText, visuArea, juce::Justification::topRight, true);
209 }
210}
211
213{
214 if (!data)
215 return;
216
217 switch (data->GetDataType())
218 {
220 m_levelData = *(static_cast<ProcessorLevelData*>(data));
222 break;
226 default:
227 break;
228 }
229}
230
232{
233 m_direction = direction;
234 repaint();
235}
236
238{
239 m_channelCount = channelCount;
240}
241
243{
244 return m_channelCount;
245}
246
247
248}
Base class for all audio-data visualisation components in the Mema processor editor.
void notifyChanges()
Marks that new data is available and triggers a repaint on the next timer tick.
void setRefreshFrequency(int frequency)
Sets the display refresh rate in Hz.
void setUsesValuesInDB(bool useValuesInDB)
Base class for all data objects exchanged between the audio processor and its analyzers/visualisers.
@ Invalid
Uninitialised or unknown data.
@ AudioSignal
Raw audio buffer data.
@ Level
Peak/RMS/hold level metering data.
@ Spectrum
FFT frequency-spectrum data.
Type GetDataType()
Returns the concrete type of this data object.
void paint(Graphics &) override
void setDirection(Direction direction)
Direction
Orientation of the meter bars.
@ Horizontal
Bars grow left-to-right.
void setChannelCount(int channelCount)
void processingDataChanged(AbstractProcessorData *data) override
LevelVal GetLevel(unsigned long channel)
Definition Mema.cpp:27
Per-channel level values in both linear and dB domains.