21#include <CustomLookAndFeel.h>
45 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
48 if (!m_positionedChannelsArea.isEmpty())
49 paintCircularLevelIndication(g, m_positionedChannelsArea, m_channelLevelMaxPoints, m_clockwiseOrderedChannelTypes);
50 if (!m_positionedHeightChannelsArea.isEmpty())
51 paintCircularLevelIndication(g, m_positionedHeightChannelsArea, m_channelHeightLevelMaxPoints, m_clockwiseOrderedHeightChannelTypes);
52 if (!m_directionlessChannelsArea.isEmpty())
53 paintLevelMeterIndication(g, m_directionlessChannelsArea, m_directionLessChannelTypes);
57 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
58 juce::String rangeText;
62 rangeText =
"0 ... 1";
63 g.drawText(rangeText, getLocalBounds(), juce::Justification::topRight,
true);
66void TwoDFieldOutputComponent::paintCircularLevelIndication(juce::Graphics& g,
const juce::Rectangle<float>& circleArea,
const std::map<
int, juce::Point<float>>& channelLevelMaxPoints,
const juce::Array<juce::AudioChannelSet::ChannelType>& channelsToPaint)
68#if defined DEBUG && defined PAINTINGHELPER
69 g.setColour(juce::Colours::blueviolet);
70 g.drawRect(circleArea);
74 g.setColour(getLookAndFeel().findColour(juce::Slider::backgroundColourId));
75 g.fillEllipse(circleArea);
77#if defined DEBUG && defined PAINTINGHELPER
78 g.setColour(juce::Colours::red);
79 g.drawRect(circleArea);
80 g.setColour(juce::Colours::blue);
81 g.drawRect(getLocalBounds());
85 const float meterWidth = 5.0f;
86 const float halfMeterWidth = 2.0f;
90 auto calcLevelVals = [=](std::map<int, float>& levels,
bool isHold,
bool isPeak,
bool isRms) {
91 for (
auto const& channelType : channelsToPaint)
96 levels[channelType] = m_levelData.
GetLevel(getChannelNumberForChannelTypeInCurrentConfiguration(channelType)).
GetFactorHOLDdB();
98 levels[channelType] = m_levelData.
GetLevel(getChannelNumberForChannelTypeInCurrentConfiguration(channelType)).
hold;
103 levels[channelType] = m_levelData.
GetLevel(getChannelNumberForChannelTypeInCurrentConfiguration(channelType)).
GetFactorPEAKdB();
105 levels[channelType] = m_levelData.
GetLevel(getChannelNumberForChannelTypeInCurrentConfiguration(channelType)).
peak;
110 levels[channelType] = m_levelData.
GetLevel(getChannelNumberForChannelTypeInCurrentConfiguration(channelType)).
GetFactorRMSdB();
112 levels[channelType] = m_levelData.
GetLevel(getChannelNumberForChannelTypeInCurrentConfiguration(channelType)).
rms;
117 std::map<int, float> holdLevels;
118 calcLevelVals(holdLevels,
true,
false,
false);
120 std::map<int, float> peakLevels;
121 calcLevelVals(peakLevels,
false,
true,
false);
123 std::map<int, float> rmsLevels;
124 calcLevelVals(rmsLevels,
false,
false,
true);
127 auto circleCenter = circleArea.getCentre();
130 std::map<int, juce::Point<float>> centerToMaxVectors;
131 std::map<int, juce::Point<float>> meterWidthOffsetVectors;
132 for (
int i = 0; i < channelsToPaint.size(); i++)
134 auto const& channelType = channelsToPaint[i];
135 if (0 < channelLevelMaxPoints.count(channelType))
137 auto angleRad = juce::degreesToRadians(getAngleForChannelTypeInCurrentConfiguration(channelType));
138 centerToMaxVectors[channelType] = circleCenter - channelLevelMaxPoints.at(channelType);
139 meterWidthOffsetVectors[channelType] = { cosf(angleRad) * halfMeterWidth, sinf(angleRad) * halfMeterWidth };
144 auto createAndPaintLevelPath = [=](std::map<int, juce::Point<float>>& centerToMaxPoints, std::map<int, juce::Point<float>>& meterWidthOffsetPoints, std::map<int, float>& levels, juce::Graphics& g,
const juce::Colour& colour,
bool stroke) {
146 auto pathStarted =
false;
147 for (
auto const& channelType : channelsToPaint)
149 auto channelMaxPoint = circleCenter - (centerToMaxPoints[channelType] * levels[channelType]);
153 path.startNewSubPath(channelMaxPoint - meterWidthOffsetPoints[channelType]);
157 path.lineTo(channelMaxPoint - meterWidthOffsetPoints[channelType]);
159 path.lineTo(channelMaxPoint + meterWidthOffsetPoints[channelType]);
165 g.strokePath(path, juce::PathStrokeType(1));
168#if defined DEBUG && defined PAINTINGHELPER
169 g.setColour(juce::Colours::yellow);
170 g.drawRect(path.getBounds());
174 createAndPaintLevelPath(centerToMaxVectors, meterWidthOffsetVectors, holdLevels, g, getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringHoldColourId),
true);
176 createAndPaintLevelPath(centerToMaxVectors, meterWidthOffsetVectors, peakLevels, g, getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId),
false);
178 createAndPaintLevelPath(centerToMaxVectors, meterWidthOffsetVectors, rmsLevels, g, getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId),
false);
182 auto paintLevelMeterLines = [=](std::map<int, juce::Point<float>>& centerToMaxPoints, std::map<int, juce::Point<float>>& meterWidthOffsetPoints, std::map<int, float>& levels, juce::Graphics& g,
const juce::Colour& colour,
bool isHoldLine) {
184 for (
auto const& channelType : channelsToPaint)
186 auto channelMaxPoint = circleCenter - (centerToMaxPoints[channelType] * levels[channelType]);
189 g.drawLine(juce::Line<float>(channelMaxPoint - meterWidthOffsetPoints[channelType], channelMaxPoint + meterWidthOffsetPoints[channelType]), 1.0f);
191 g.drawLine(juce::Line<float>(circleCenter, channelMaxPoint), meterWidth);
195 paintLevelMeterLines(centerToMaxVectors, meterWidthOffsetVectors, holdLevels, g, getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringHoldColourId),
true);
197 paintLevelMeterLines(centerToMaxVectors, meterWidthOffsetVectors, peakLevels, g, getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId),
false);
199 paintLevelMeterLines(centerToMaxVectors, meterWidthOffsetVectors, rmsLevels, g, getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId),
false);
202 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
203 g.drawEllipse(circleArea, 1);
206 float dparam[]{ 4.0f, 5.0f };
207 for (
auto const& channelType : channelsToPaint)
208 if (0 < channelLevelMaxPoints.count(channelType))
209 g.drawDashedLine(juce::Line<float>(channelLevelMaxPoints.at(channelType), circleCenter), dparam, 2);
212 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
213 for (
auto const& channelType : channelsToPaint)
215 if (0 >= channelLevelMaxPoints.count(channelType))
218 auto channelName = juce::AudioChannelSet::getAbbreviatedChannelTypeName(channelType);
219 auto textRect = juce::Rectangle<float>(juce::GlyphArrangement::getStringWidth(g.getCurrentFont(), channelName), g.getCurrentFont().getHeight());
220 auto angle = getAngleForChannelTypeInCurrentConfiguration(channelType);
221 auto textRectOffset = juce::Point<int>(-
int(textRect.getWidth() / 2.0f), 0);
224 else if (-90.0f > angle)
227 textRectOffset.addXY(0, -
int(g.getCurrentFont().getHeight()));
228 auto angleRad = juce::degreesToRadians(angle);
231 g.setOrigin(channelLevelMaxPoints.at(channelType).toInt());
232 g.addTransform(juce::AffineTransform().translated(textRectOffset).rotated(angleRad));
233 g.drawText(channelName, textRect, Justification::centred,
true);
235#if defined DEBUG && defined PAINTINGHELPER
236 g.setColour(juce::Colours::lightblue);
237 g.drawRect(textRect);
238 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
245void TwoDFieldOutputComponent::paintLevelMeterIndication(juce::Graphics& g,
const juce::Rectangle<float>& levelMeterArea,
const juce::Array<juce::AudioChannelSet::ChannelType>& channelsToPaint)
247#if defined DEBUG && defined PAINTINGHELPER
248 g.setColour(juce::Colours::aqua);
249 g.drawRect(levelMeterArea);
252 auto channelCount = channelsToPaint.size();
253 auto margin = levelMeterArea.getWidth() / ((2 * channelCount) + 1);
255 auto visuArea = levelMeterArea;
256 auto visuAreaOrigY = visuArea.getBottom();
259 auto meterSpacing = margin;
260 auto meterThickness = float(visuArea.getWidth() - (channelCount)*meterSpacing) / float(channelCount);
261 auto meterMaxLength = visuArea.getHeight();
262 auto meterLeft = levelMeterArea.getX() + 0.5f * meterSpacing;
265 for (
auto const& channelType : channelsToPaint)
267 auto level = m_levelData.
GetLevel(getChannelNumberForChannelTypeInCurrentConfiguration(channelType));
268 float peakMeterLength{ 0 };
269 float rmsMeterLength{ 0 };
270 float holdMeterLength{ 0 };
274 rmsMeterLength = meterMaxLength * level.GetFactorRMSdB();
275 holdMeterLength = meterMaxLength * level.GetFactorHOLDdB();
279 peakMeterLength = meterMaxLength * level.peak;
280 rmsMeterLength = meterMaxLength * level.rms;
281 holdMeterLength = meterMaxLength * level.hold;
285 g.setColour(getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId));
286 g.fillRect(juce::Rectangle<float>(meterLeft, visuAreaOrigY - peakMeterLength, meterThickness, peakMeterLength));
288 g.setColour(getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId));
289 g.fillRect(juce::Rectangle<float>(meterLeft, visuAreaOrigY - rmsMeterLength, meterThickness, rmsMeterLength));
291 g.setColour(getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringHoldColourId));
292 g.drawLine(juce::Line<float>(meterLeft, visuAreaOrigY - holdMeterLength, meterLeft + meterThickness, visuAreaOrigY - holdMeterLength));
294 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
295 g.drawText(juce::AudioChannelSet::getAbbreviatedChannelTypeName(channelType), juce::Rectangle<float>(meterLeft - (0.5f * meterSpacing), visuAreaOrigY -
float(margin + 2), meterThickness + meterSpacing,
float(margin)), juce::Justification::centred);
297 meterLeft += meterThickness + meterSpacing;
301 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
302 g.drawLine(juce::Line<float>(levelMeterArea.getX(), visuAreaOrigY, levelMeterArea.getX() + visuArea.getWidth(), visuAreaOrigY));
308 auto coreTwoDFieldOnly = usesPositionedChannels() && !usesPositionedHeightChannels() && !usesDirectionlessChannels();
309 auto coreTwoDFieldWithMeterbridge = usesPositionedChannels() && !usesPositionedHeightChannels() && usesDirectionlessChannels();
310 auto bothTwoDFields = usesPositionedChannels() && usesPositionedHeightChannels() && !usesDirectionlessChannels();
311 auto bothTwoDFieldsWithMeterbridge = usesPositionedChannels() && usesPositionedHeightChannels() && usesDirectionlessChannels();
314 auto bounds = getLocalBounds().toFloat();
315 auto width = bounds.getWidth();
316 auto height = bounds.getHeight();
317 if (coreTwoDFieldOnly)
319 m_positionedChannelsArea = bounds.reduced(margin);
320 m_positionedHeightChannelsArea = {};
321 m_directionlessChannelsArea = {};
323 else if (coreTwoDFieldWithMeterbridge)
325 m_positionedChannelsArea = bounds.reduced(margin);
326 m_positionedChannelsArea.removeFromRight(width * (1.0f / 11.0f));
328 m_positionedHeightChannelsArea = {};
330 m_directionlessChannelsArea = bounds;
331 m_directionlessChannelsArea.removeFromLeft(width * (10.0f / 11.0f));
333 else if (bothTwoDFields)
335 m_positionedHeightChannelsArea = bounds.reduced(margin);
336 m_positionedHeightChannelsArea.removeFromRight(width * (8.4f / 12.0f));
337 m_positionedHeightChannelsArea.removeFromBottom(height * (5.4f / 10.0f));
339 m_positionedChannelsArea = bounds.reduced(margin);
340 m_positionedChannelsArea.removeFromLeft(width * (3.4f / 12.0f));
341 m_positionedChannelsArea.removeFromTop(height * (1.4f / 10.0f));
343 m_directionlessChannelsArea = {};
345 else if (bothTwoDFieldsWithMeterbridge)
347 m_positionedHeightChannelsArea = bounds.reduced(margin);
348 m_positionedHeightChannelsArea.removeFromRight(width * (8.4f / 13.0f));
349 m_positionedHeightChannelsArea.removeFromBottom(height * (5.4f / 10.0f));
351 m_positionedChannelsArea = bounds;
352 m_directionlessChannelsArea = m_positionedChannelsArea.removeFromRight(width * (1.0f / 13.0f));
353 m_positionedChannelsArea.reduce(margin, margin);
354 m_positionedChannelsArea.removeFromLeft(width * (3.4f / 13.0f));
355 m_positionedChannelsArea.removeFromTop(height * (1.4f / 10.0f));
358 for (
auto const& channelType : m_clockwiseOrderedChannelTypes)
360 auto angleRad = juce::degreesToRadians(getAngleForChannelTypeInCurrentConfiguration(channelType));
361 auto xLength = sinf(angleRad) * (m_positionedChannelsArea.getHeight() / 2);
362 auto yLength = cosf(angleRad) * (m_positionedChannelsArea.getWidth() / 2);
363 m_channelLevelMaxPoints[channelType] = m_positionedChannelsArea.getCentre() + juce::Point<float>(xLength, -yLength);
366 for (
auto const& channelType : m_clockwiseOrderedHeightChannelTypes)
368 auto angleRad = juce::degreesToRadians(getAngleForChannelTypeInCurrentConfiguration(channelType));
369 auto xLength = sinf(angleRad) * (m_positionedHeightChannelsArea.getHeight() / 2);
370 auto yLength = cosf(angleRad) * (m_positionedHeightChannelsArea.getWidth() / 2);
371 m_channelHeightLevelMaxPoints[channelType] = m_positionedHeightChannelsArea.getCentre() + juce::Point<float>(xLength, -yLength);
Base class for all audio-data visualisation components in the Mema processor editor.
void paint(Graphics &) override
Paints the visualiser background.
void notifyChanges()
Marks that new data is available and triggers a repaint on the next timer tick.
void setUsesValuesInDB(bool useValuesInDB)
void resized() override
Lays out child components.
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.
static int getGlobalMaxdB()
static int getGlobalMindB()
LevelVal GetLevel(unsigned long channel)
void paint(Graphics &) override
TwoDFieldOutputComponent()
~TwoDFieldOutputComponent()
void processingDataChanged(AbstractProcessorData *data) override
float rms
Linear RMS level.
float hold
Linear hold level.
float peak
Linear peak level.