21#include <CustomLookAndFeel.h>
22#include <FixedFontTextEditor.h>
37 : juce::KeyboardFocusTraverser()
39 m_focusElements = focusElements;
45 if (m_focusElements.empty() || m_focusElements.front() == parentComponent)
48 return m_focusElements.front();
52 if (m_focusElements.empty())
55 auto currentIter = std::find(m_focusElements.begin(), m_focusElements.end(), current);
56 if (currentIter == m_focusElements.end())
60 if (currentIter == (m_focusElements.end() - 1))
61 return *(m_focusElements.begin());
63 return *(currentIter + 1);
68 if (m_focusElements.empty())
71 auto currentIter = std::find(m_focusElements.begin(), m_focusElements.end(), current);
72 if (currentIter == m_focusElements.end())
76 if (currentIter == m_focusElements.begin())
77 return *(m_focusElements.end() - 1);
79 return *(currentIter - 1);
84 return m_focusElements;
89 std::vector<juce::Component*> m_focusElements;
97 m_sharpnessEdit = std::make_unique<JUCEAppBasics::FixedFontTextEditor>(
"SharpnessEdit");
98 m_sharpnessEdit->setTooltip(
"Panning sharpness 0.0 ... 1.0");
99 m_sharpnessEdit->setText(juce::String(0.5),
false);
100 m_sharpnessEdit->setInputFilter(
new juce::TextEditor::LengthAndCharacterRestriction(3,
"0123456789."),
true);
101 m_sharpnessEdit->setJustification(juce::Justification::centred);
102 m_sharpnessEdit->onReturnKey = [=]() {
103 if (0 != m_currentlySelectedInput)
105 m_inputPositions[m_currentlySelectedInput].sharpness = jlimit(0.0f, 1.0f, m_sharpnessEdit->getText().getFloatValue());
107 onInputPositionChanged(m_currentlySelectedInput, m_inputPositions[m_currentlySelectedInput].value, m_inputPositions[m_currentlySelectedInput].sharpness, m_inputPositions[m_currentlySelectedInput].layer);
111 for (
auto& inputPositionKV : m_inputPositions)
113 inputPositionKV.second.sharpness = jlimit(0.0f, 1.0f, m_sharpnessEdit->getText().getFloatValue());
115 onInputPositionChanged(inputPositionKV.first, inputPositionKV.second.value, inputPositionKV.second.sharpness, inputPositionKV.second.layer);
119 addAndMakeVisible(m_sharpnessEdit.get());
120 m_sharpnessLabel = std::make_unique<juce::Label>(
"SharpnessLabel");
121 m_sharpnessLabel->setText(
"Sharpness", juce::dontSendNotification);
122 m_sharpnessLabel->setJustificationType(juce::Justification::centredLeft);
123 addAndMakeVisible(m_sharpnessLabel.get());
132 return std::make_unique<TwoDFieldMultisliderKeyboardFocusTraverser>(std::vector<juce::Component*>({
this, m_sharpnessEdit.get() }));
139 g.fillAll(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId));
142 if (!m_positionedChannelsArea.isEmpty())
143 paintCircularLevelIndication(g, m_positionedChannelsArea, m_channelLevelMaxPoints, m_clockwiseOrderedChannelTypes);
144 if (!m_positionedHeightChannelsArea.isEmpty())
145 paintCircularLevelIndication(g, m_positionedHeightChannelsArea, m_channelHeightLevelMaxPoints, m_clockwiseOrderedHeightChannelTypes);
148 jassert(m_inputPositions.size() == m_inputPositionStackingOrder.size());
150 for (
auto const & inputNumber : std::vector<std::uint16_t>(m_inputPositionStackingOrder.rbegin(), m_inputPositionStackingOrder.rend()))
152 auto& inputPosition = m_inputPositions[inputNumber];
153 auto emptyRect = juce::Rectangle<float>();
154 auto& area = emptyRect;
155 if (ChannelLayer::Positioned == inputPosition.layer && !m_positionedChannelsArea.isEmpty())
156 area = m_positionedChannelsArea;
157 else if (ChannelLayer::PositionedHeight == inputPosition.layer && !m_positionedHeightChannelsArea.isEmpty())
158 area = m_positionedHeightChannelsArea;
161 paintSliderKnob(g, area, inputPosition.value.relXPos, inputPosition.value.relYPos, inputNumber, inputPosition.isOn, inputPosition.isSliding);
165void TwoDFieldMultisliderComponent::paintCircularLevelIndication(juce::Graphics& g,
const juce::Rectangle<float>& circleArea,
const std::map<
int, juce::Point<float>>& channelLevelMaxPoints,
const juce::Array<juce::AudioChannelSet::ChannelType>& channelsToPaint)
167#if defined DEBUG && defined PAINTINGHELPER
168 g.setColour(juce::Colours::blueviolet);
169 g.drawRect(circleArea);
173 g.setColour(getLookAndFeel().findColour(juce::ResizableWindow::backgroundColourId).darker());
174 g.fillEllipse(circleArea);
176#if defined DEBUG && defined PAINTINGHELPER
177 g.setColour(juce::Colours::red);
178 g.drawRect(circleArea);
179 g.setColour(juce::Colours::blue);
180 g.drawRect(getLocalBounds());
184 const float meterWidth = 5.0f;
185 const float halfMeterWidth = 2.0f;
188 auto circleCenter = circleArea.getCentre();
191 std::map<int, juce::Point<float>> centerToMaxVectors;
192 std::map<int, juce::Point<float>> meterWidthOffsetVectors;
193 for (
int i = 0; i < channelsToPaint.size(); i++)
195 auto const& channelType = channelsToPaint[i];
196 auto iTy = int(channelType);
197 if (0 < channelLevelMaxPoints.count(iTy))
199 auto angleRad = juce::degreesToRadians(getAngleForChannelTypeInCurrentConfiguration(channelType));
200 centerToMaxVectors[iTy] = circleCenter - channelLevelMaxPoints.at(iTy);
201 meterWidthOffsetVectors[iTy] = { cosf(angleRad) * halfMeterWidth, sinf(angleRad) * halfMeterWidth };
206 auto createAndPaintLevelPath = [=](std::map<int, juce::Point<float>>& centerToMaxPoints, std::map<int, juce::Point<float>>& meterWidthOffsetPoints, std::map<juce::AudioChannelSet::ChannelType, float>& levels, juce::Graphics& g,
const juce::Colour& colour,
bool stroke) {
208 auto pathStarted =
false;
209 for (
auto const& channelType : channelsToPaint)
211 auto iTy = int(channelType);
212 auto channelMaxPoint = circleCenter - (centerToMaxPoints[iTy] * levels[channelType]);
216 path.startNewSubPath(channelMaxPoint - meterWidthOffsetPoints[iTy]);
220 path.lineTo(channelMaxPoint - meterWidthOffsetPoints[iTy]);
222 path.lineTo(channelMaxPoint + meterWidthOffsetPoints[iTy]);
228 g.strokePath(path, juce::PathStrokeType(1));
231#if defined DEBUG && defined PAINTINGHELPER
232 g.setColour(juce::Colours::yellow);
233 g.drawRect(path.getBounds());
236 auto paintLevelMeterLines = [=](std::map<int, juce::Point<float>>& centerToMaxPoints, std::map<int, juce::Point<float>>& meterWidthOffsetPoints, std::map<juce::AudioChannelSet::ChannelType, float>& levels, juce::Graphics& g,
const juce::Colour& colour,
bool isHoldLine) {
238 for (
auto const& channelType : channelsToPaint)
240 auto iTy = int(channelType);
241 auto channelMaxPoint = circleCenter - (centerToMaxPoints[iTy] * levels[channelType]);
244 g.drawLine(juce::Line<float>(channelMaxPoint - meterWidthOffsetPoints[iTy], channelMaxPoint + meterWidthOffsetPoints[iTy]), 1.0f);
246 g.drawLine(juce::Line<float>(circleCenter, channelMaxPoint), meterWidth);
250 std::map<juce::AudioChannelSet::ChannelType, float> levels;
251 for (
auto& vKV : m_inputToOutputVals)
254 for (
auto const& oVals : vKV.second)
256 auto& channel = oVals.first;
257 auto& state = oVals.second.first;
258 auto& level = oVals.second.second;
259 levels[channel] = state ? level : 0.0f;
263 if (m_currentlySelectedInput == vKV.first)
264 colour = getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId).withAlpha(0.8f);
266 colour = getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId).withAlpha(0.4f);
269 createAndPaintLevelPath(centerToMaxVectors, meterWidthOffsetVectors, levels, g, colour,
false);
271 paintLevelMeterLines(centerToMaxVectors, meterWidthOffsetVectors, levels, g, colour,
false);
275 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
276 g.drawEllipse(circleArea, 1);
279 float dparam[]{ 4.0f, 5.0f };
280 for (
auto const& channelType : channelsToPaint)
281 if (0 < channelLevelMaxPoints.count(channelType))
282 g.drawDashedLine(juce::Line<float>(channelLevelMaxPoints.at(channelType), circleCenter), dparam, 2);
285 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
286 for (
auto const& channelType : channelsToPaint)
288 if (0 >= channelLevelMaxPoints.count(channelType))
291 auto channelName = juce::AudioChannelSet::getAbbreviatedChannelTypeName(channelType);
292 auto textRect = juce::Rectangle<float>(juce::GlyphArrangement::getStringWidth(g.getCurrentFont(), channelName), g.getCurrentFont().getHeight());
293 auto angle = getAngleForChannelTypeInCurrentConfiguration(channelType);
294 auto textRectOffset = juce::Point<int>(-
int(textRect.getWidth() / 2.0f), 0);
297 else if (-90.0f > angle)
300 textRectOffset.addXY(0, -
int(g.getCurrentFont().getHeight()));
301 auto angleRad = juce::degreesToRadians(angle);
304 g.setOrigin(channelLevelMaxPoints.at(channelType).toInt());
305 g.addTransform(juce::AffineTransform().translated(textRectOffset).rotated(angleRad));
306 g.drawText(channelName, textRect, Justification::centred,
true);
308#if defined DEBUG && defined PAINTINGHELPER
309 g.setColour(juce::Colours::lightblue);
310 g.drawRect(textRect);
311 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOffId));
318void TwoDFieldMultisliderComponent::paintSliderKnob(juce::Graphics& g,
const juce::Rectangle<float>& sliderArea,
const float& relXPos,
const float& relYPos,
const int& silderNumber,
bool isSliderOn,
bool isSliderSliding)
320 juce::Path valueTrack;
321 auto minPoint = sliderArea.getCentre();
322 auto maxPoint = sliderArea.getCentre() + juce::Point<float>((sliderArea.getWidth() / 2) * relXPos, (sliderArea.getHeight() / 2) * relYPos * -1.0f);
328 valueTrack.startNewSubPath(minPoint);
329 valueTrack.lineTo(maxPoint);
330 g.setColour(getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId));
331 g.strokePath(valueTrack, { m_trackWidth, juce::PathStrokeType::curved, juce::PathStrokeType::rounded });
334 g.setColour(getLookAndFeel().findColour(juce::Slider::thumbColourId));
335 g.fillEllipse(juce::Rectangle<float>(
static_cast<float>(m_thumbWidth),
static_cast<float>(m_thumbWidth)).withCentre(maxPoint));
337 g.setColour(getLookAndFeel().findColour(juce::TextButton::textColourOnId));
338 g.drawText(juce::String(silderNumber), juce::Rectangle<float>(
static_cast<float>(m_thumbWidth),
static_cast<float>(m_thumbWidth)).withCentre(maxPoint), juce::Justification::centred);
344 valueTrack.startNewSubPath(minPoint);
345 valueTrack.lineTo(maxPoint);
346 auto valueTrackOutline = valueTrack;
347 juce::PathStrokeType pt(m_trackWidth, juce::PathStrokeType::curved, juce::PathStrokeType::rounded);
348 pt.createStrokedPath(valueTrackOutline, valueTrack);
349 g.setColour(getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringPeakColourId));
350 g.strokePath(valueTrackOutline, juce::PathStrokeType(1.0f));
353 g.setColour(getLookAndFeel().findColour(juce::ResizableWindow::ColourIds::backgroundColourId));
354 g.fillEllipse(juce::Rectangle<float>(
static_cast<float>(m_thumbWidth - 1),
static_cast<float>(m_thumbWidth - 1)).withCentre(maxPoint));
356 g.setColour(getLookAndFeel().findColour(juce::Slider::thumbColourId));
357 g.drawEllipse(juce::Rectangle<float>(
static_cast<float>(m_thumbWidth),
static_cast<float>(m_thumbWidth)).withCentre(maxPoint), 1.0f);
359 g.drawText(juce::String(silderNumber), juce::Rectangle<float>(
static_cast<float> (m_thumbWidth),
static_cast<float> (m_thumbWidth)).withCentre(maxPoint), juce::Justification::centred);
366 auto coreTwoDFieldOnly = usesPositionedChannels() && !usesPositionedHeightChannels() && !usesDirectionlessChannels();
367 auto coreTwoDFieldWithMeterbridge = usesPositionedChannels() && !usesPositionedHeightChannels() && usesDirectionlessChannels();
368 auto bothTwoDFields = usesPositionedChannels() && usesPositionedHeightChannels() && !usesDirectionlessChannels();
369 auto bothTwoDFieldsWithMeterbridge = usesPositionedChannels() && usesPositionedHeightChannels() && usesDirectionlessChannels();
372 auto bounds = getLocalBounds().reduced(8).toFloat();
373 auto width = bounds.getWidth();
374 auto height = bounds.getHeight();
376 auto sharpnessBounds = bounds.toNearestInt();
377 sharpnessBounds = sharpnessBounds.removeFromBottom(40);
379 m_sharpnessEdit->setBounds(sharpnessBounds.removeFromBottom(20).removeFromLeft(40));
380 if (m_sharpnessLabel)
381 m_sharpnessLabel->setBounds(sharpnessBounds.removeFromLeft(75));
383 if (coreTwoDFieldOnly)
385 m_positionedChannelsArea = bounds.reduced(margin);
386 m_positionedHeightChannelsArea = {};
387 m_directionlessChannelsArea = {};
389 else if (coreTwoDFieldWithMeterbridge)
391 m_positionedChannelsArea = bounds;
392 m_directionlessChannelsArea = m_positionedChannelsArea.removeFromRight(
float(m_directionLessChannelTypes.size() * m_ctrlsSize));
393 m_positionedChannelsArea.reduce(margin, margin);
395 m_positionedHeightChannelsArea = {};
397 else if (bothTwoDFields)
399 m_positionedHeightChannelsArea = bounds.reduced(margin);
400 m_positionedHeightChannelsArea.removeFromRight(width * (8.4f / 12.0f));
401 m_positionedHeightChannelsArea.removeFromBottom(height * (5.4f / 10.0f));
403 m_positionedChannelsArea = bounds.reduced(margin);
404 m_positionedChannelsArea.removeFromLeft(width * (3.4f / 12.0f));
405 m_positionedChannelsArea.removeFromTop(height * (1.4f / 10.0f));
407 m_directionlessChannelsArea = {};
409 else if (bothTwoDFieldsWithMeterbridge)
411 m_positionedHeightChannelsArea = bounds.reduced(margin);
412 m_positionedHeightChannelsArea.removeFromRight(width * (8.4f / 13.0f));
413 m_positionedHeightChannelsArea.removeFromBottom(height * (5.4f / 10.0f));
415 m_positionedChannelsArea = bounds;
416 m_directionlessChannelsArea = m_positionedChannelsArea.removeFromRight(
float(m_directionLessChannelTypes.size() * m_ctrlsSize));
417 m_positionedChannelsArea.reduce(margin, margin);
418 m_positionedChannelsArea.removeFromLeft(width * (3.4f / 13.0f));
419 m_positionedChannelsArea.removeFromTop(height * (1.4f / 10.0f));
422 for (
auto const& channelType : m_clockwiseOrderedChannelTypes)
424 auto angleRad = juce::degreesToRadians(getAngleForChannelTypeInCurrentConfiguration(channelType));
425 auto xLength = sinf(angleRad) * (m_positionedChannelsArea.getHeight() / 2);
426 auto yLength = cosf(angleRad) * (m_positionedChannelsArea.getWidth() / 2);
427 m_channelLevelMaxPoints[channelType] = m_positionedChannelsArea.getCentre() + juce::Point<float>(xLength, -yLength);
430 for (
auto const& channelType : m_clockwiseOrderedHeightChannelTypes)
432 auto angleRad = juce::degreesToRadians(getAngleForChannelTypeInCurrentConfiguration(channelType));
433 auto xLength = sinf(angleRad) * (m_positionedHeightChannelsArea.getHeight() / 2);
434 auto yLength = cosf(angleRad) * (m_positionedHeightChannelsArea.getWidth() / 2);
435 m_channelHeightLevelMaxPoints[channelType] = m_positionedHeightChannelsArea.getCentre() + juce::Point<float>(xLength, -yLength);
438 if (!m_directionlessChannelsArea.isEmpty() && !m_directionslessChannelSliders.empty() && !m_directionslessChannelLabels.empty())
440 jassert(m_directionslessChannelSliders.size() == m_directionslessChannelLabels.size());
441 auto directionlessChannelsWidth = m_directionlessChannelsArea.getWidth() / m_directionslessChannelSliders.size();
442 auto areaToDivide = m_directionlessChannelsArea;
443 for (
auto const& sliderKV : m_directionslessChannelSliders)
445 auto const& slider = sliderKV.second;
446 auto const& label = m_directionslessChannelLabels.at(sliderKV.first);
447 auto sliderBounds = areaToDivide.removeFromLeft(directionlessChannelsWidth).toNearestInt();
448 auto labelBounds = sliderBounds.removeFromBottom(m_thumbWidth);
450 slider->setBounds(sliderBounds);
452 label->setBounds(labelBounds);
459 if (!m_directionslessChannelSliders.empty())
461 for (
auto const& slider : m_directionslessChannelSliders)
463 slider.second->setColour(juce::Slider::ColourIds::trackColourId, getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId));
471 jassert(m_inputPositions.size() == m_inputPositionStackingOrder.size());
472 for (
auto const& inputNumber : m_inputPositionStackingOrder)
474 auto& inputPosition = m_inputPositions[inputNumber];
475 auto emptyRect = juce::Rectangle<float>();
476 auto& area = emptyRect;
477 if (ChannelLayer::Positioned == inputPosition.layer && !m_positionedChannelsArea.isEmpty())
478 area = m_positionedChannelsArea;
479 else if (ChannelLayer::PositionedHeight == inputPosition.layer && !m_positionedHeightChannelsArea.isEmpty())
480 area = m_positionedHeightChannelsArea;
481 else if (ChannelLayer::Directionless == inputPosition.layer && !m_directionlessChannelsArea.isEmpty())
482 area = m_directionlessChannelsArea;
484 auto maxPoint = area.getCentre() - juce::Point<float>((area.getWidth() / 2) * -inputPosition.value.relXPos, (area.getHeight() / 2) * inputPosition.value.relYPos);
485 auto sliderKnob = juce::Rectangle<float>(
static_cast<float>(m_thumbWidth),
static_cast<float>(m_thumbWidth)).withCentre(maxPoint);
486 if (sliderKnob.contains(e.getMouseDownPosition().toFloat()) &&
false == hadHit)
488 inputPosition.isOn =
true;
490 selectInput(inputNumber,
true, juce::sendNotification);
496 selectInput(inputNumber,
false, juce::sendNotification);
500 juce::Component::mouseDown(e);
505 juce::Component::mouseUp(e);
510 if (e.mouseWasDraggedSinceMouseDown())
513 jassert(m_inputPositions.size() == m_inputPositionStackingOrder.size());
514 for (
auto const& inputNumber : m_inputPositionStackingOrder)
516 auto& inputPosition = m_inputPositions[inputNumber];
517 if (inputPosition.isSliding)
519 auto emptyRect = juce::Rectangle<float>();
520 auto& area = emptyRect;
521 if (ChannelLayer::Positioned == inputPosition.layer && !m_positionedChannelsArea.isEmpty())
522 area = m_positionedChannelsArea;
523 else if (ChannelLayer::PositionedHeight == inputPosition.layer && !m_positionedHeightChannelsArea.isEmpty())
524 area = m_positionedHeightChannelsArea;
525 else if (ChannelLayer::Directionless == inputPosition.layer && !m_directionlessChannelsArea.isEmpty())
526 area = m_directionlessChannelsArea;
528 auto mousePosition = e.getMouseDownPosition() + e.getOffsetFromDragStart();
530 juce::Path ellipsePath;
531 ellipsePath.addEllipse(area);
533 if (ellipsePath.contains(mousePosition.toFloat()))
535 auto positionInArea = area.getCentre() - area.getConstrainedPoint(mousePosition.toFloat());
536 auto relXPos = positionInArea.getX() / (0.5f * area.getWidth());
537 auto relYPos = positionInArea.getY() / (0.5f * area.getHeight());
538 setInputPosition(inputNumber, { -relXPos, relYPos }, inputPosition.sharpness, inputPosition.layer, juce::sendNotification);
542 juce::Path positionedChannelsEllipsePath, positionedHeightChannelsEllipsePath;
543 positionedChannelsEllipsePath.addEllipse(m_positionedChannelsArea);
544 positionedHeightChannelsEllipsePath.addEllipse(m_positionedHeightChannelsArea);
546 if (positionedChannelsEllipsePath.contains(mousePosition.toFloat()))
548 auto positionInArea = m_positionedChannelsArea.getCentre() - m_positionedChannelsArea.getConstrainedPoint(mousePosition.toFloat());
549 auto relXPos = positionInArea.getX() / (0.5f * m_positionedChannelsArea.getWidth());
550 auto relYPos = positionInArea.getY() / (0.5f * m_positionedChannelsArea.getHeight());
551 setInputPosition(inputNumber, { -relXPos, relYPos }, inputPosition.sharpness, ChannelLayer::Positioned, juce::sendNotification);
553 else if (positionedHeightChannelsEllipsePath.contains(mousePosition.toFloat()))
555 auto positionInArea = m_positionedHeightChannelsArea.getCentre() - m_positionedHeightChannelsArea.getConstrainedPoint(mousePosition.toFloat());
556 auto relXPos = positionInArea.getX() / (0.5f * m_positionedHeightChannelsArea.getWidth());
557 auto relYPos = positionInArea.getY() / (0.5f * m_positionedHeightChannelsArea.getHeight());
558 setInputPosition(inputNumber, { -relXPos, relYPos }, inputPosition.sharpness, ChannelLayer::PositionedHeight, juce::sendNotification);
563 juce::Point<float> constrainedPoint;
564 ellipsePath.getNearestPoint(mousePosition.toFloat(), constrainedPoint);
565 auto positionInArea = area.getCentre() - constrainedPoint;
566 auto relXPos = positionInArea.getX() / (0.5f * area.getWidth());
567 auto relYPos = positionInArea.getY() / (0.5f * area.getHeight());
568 inputPosition.value = { relXPos, relYPos };
569 setInputPosition(inputNumber, { -relXPos, relYPos }, inputPosition.sharpness, inputPosition.layer, juce::sendNotification);
577 juce::Component::mouseDrag(e);
582 jassert(m_inputPositions.size() == m_inputPositionStackingOrder.size());
583 m_inputPositions[channel].value = value;
584 m_inputPositions[channel].sharpness = sharpness;
585 m_inputPositions[channel].layer = layer;
589 DBG(juce::String(__FUNCTION__) <<
" new pos: " <<
int(channel) <<
" " << value.
relXPos <<
"," << value.
relYPos <<
"(" << sharpness <<
"/" << layer <<
")");
597 jassert(m_inputPositions.size() == m_inputPositionStackingOrder.size());
598 m_inputPositions[channel].value = value;
602 DBG(juce::String(__FUNCTION__) <<
" new pos: " <<
int(channel) <<
" " << value.
relXPos <<
"," << value.
relYPos <<
"(" << m_inputPositions[channel].sharpness <<
"/" << m_inputPositions[channel].layer <<
")");
605 onInputPositionChanged(channel, value, m_inputPositions[channel].sharpness, m_inputPositions[channel].layer);
610 jassert(m_inputPositions.size() == m_inputPositionStackingOrder.size());
611 m_inputPositions[channel].sharpness = sharpness;
613 if (m_sharpnessEdit && 0 != m_currentlySelectedInput)
614 m_sharpnessEdit->setText(juce::String(sharpness), juce::dontSendNotification);
618 DBG(juce::String(__FUNCTION__) <<
" new pos: " <<
int(channel) <<
" " << m_inputPositions[channel].value.relXPos <<
"," << m_inputPositions[channel].value.relYPos <<
"(" << sharpness <<
"/" << m_inputPositions[channel].layer <<
")");
621 onInputPositionChanged(channel, m_inputPositions[channel].value, sharpness, m_inputPositions[channel].layer);
626 jassert(m_inputPositions.size() == m_inputPositionStackingOrder.size());
627 m_inputPositions[channel].layer = layer;
631 DBG(juce::String(__FUNCTION__) <<
" new pos: " <<
int(channel) <<
" " << m_inputPositions[channel].value.relXPos <<
"," << m_inputPositions[channel].value.relYPos <<
"(" << m_inputPositions[channel].sharpness <<
"/" << layer <<
")");
634 onInputPositionChanged(channel, m_inputPositions[channel].value, m_inputPositions[channel].sharpness, layer);
639 jassert(m_inputPositions.size() == m_inputPositionStackingOrder.size());
640 for (
auto const& inputNumber : m_inputPositionStackingOrder)
642 auto& inputPosition = m_inputPositions[inputNumber];
650 jassert(0 != m_inputPositions.count(channel));
651 if (0 == m_inputPositions.count(channel))
656 auto posIter = std::find(m_inputPositionStackingOrder.begin(), m_inputPositionStackingOrder.end(), channel);
657 if (posIter != m_inputPositionStackingOrder.end() && posIter != m_inputPositionStackingOrder.begin())
659 m_inputPositionStackingOrder.erase(posIter);
660 m_inputPositionStackingOrder.insert(m_inputPositionStackingOrder.begin(), channel);
664 m_inputPositions[channel].isSliding = selectOn;
665 if (m_currentlySelectedInput == channel && !selectOn)
666 m_currentlySelectedInput = 0;
667 else if (m_currentlySelectedInput != channel && selectOn)
668 m_currentlySelectedInput = channel;
672 if (!m_directionslessChannelSliders.empty() && !m_inputToOutputVals.empty())
674 for (
auto const& slider : m_directionslessChannelSliders)
678 if (0 == m_currentlySelectedInput && slider.second)
680 configureDirectionlessSliderToRelativeCtrl(slider.first, *slider.second);
682 else if (selectOn && 0 < m_inputToOutputVals.count(channel))
684 auto output = slider.first;
685 jassert(0 < m_inputToOutputVals.at(channel).count(output));
686 if(0 < m_inputToOutputVals.at(channel).count(output))
688 slider.second->setTitle(juce::String(channel));
689 slider.second->setRange(0.0, 1.0, 0.01);
690 slider.second->displayValueConverter = [](
double val) {
return juce::String(juce::Decibels::gainToDecibels(val,
static_cast<double>(
ProcessorDataAnalyzer::getGlobalMindB())), 1) +
" dB"; };
691 slider.second->setValue(m_inputToOutputVals.at(channel).at(output).second);
692 slider.second->setToggleState(m_inputToOutputVals.at(channel).at(output).first, juce::dontSendNotification);
701 if (0 != m_currentlySelectedInput)
703 m_sharpnessLabel->setText(
"In" + juce::String(m_currentlySelectedInput) +
" sharpness", juce::dontSendNotification);
704 m_sharpnessEdit->setText(juce::String(m_inputPositions[m_currentlySelectedInput].sharpness), juce::dontSendNotification);
708 m_sharpnessLabel->setText(
"All sharpness", juce::dontSendNotification);
709 m_sharpnessEdit->setTextToShowWhenEmpty(
"0.5", getLookAndFeel().findColour(juce::TextButton::ColourIds::textColourOffId));
710 m_sharpnessEdit->setText(
"", juce::dontSendNotification);
714 if (juce::dontSendNotification != notification &&
onInputSelected && selectOn)
720 auto channelsToRemove = std::vector<std::uint16_t>();
721 auto channelsToAdd = std::vector<std::uint16_t>();
722 for (
auto const& inputPositionKV : m_inputPositions)
724 if (inputPositionKV.first > ioCount.first)
725 channelsToRemove.push_back(inputPositionKV.first);
727 for (
auto i = std::uint16_t(1); i <= ioCount.first; i++)
729 if (0 == m_inputPositions.count(i))
730 channelsToAdd.push_back(i);
733 for (
auto const& channelToRemove : channelsToRemove)
735 m_inputPositions.erase(channelToRemove);
736 auto iterToRemove = std::find(m_inputPositionStackingOrder.begin(), m_inputPositionStackingOrder.end(), channelToRemove);
737 if (iterToRemove != m_inputPositionStackingOrder.end())
738 m_inputPositionStackingOrder.erase(iterToRemove);
741 auto angleRadDistributionSegment = channelsToAdd.empty() ? 1.0f : juce::degreesToRadians(360.0f / channelsToAdd.size());
742 auto defaultPosAngleRad = 0.0f;
743 for (
auto const& channelToAdd : channelsToAdd)
745 m_inputPositionStackingOrder.push_back(channelToAdd);
747 auto defaultXPos = 0.5f * sinf(defaultPosAngleRad);
748 auto defaultYPos = 0.5f * cosf(defaultPosAngleRad);
749 m_inputPositions[channelToAdd] = { ChannelLayer::Positioned, { defaultXPos, defaultYPos }, 0.5f,
false,
false };
750 defaultPosAngleRad -= angleRadDistributionSegment;
753 m_currentOutputCount = ioCount.second;
760 m_ctrlsSize = ctrlsSize;
761 m_thumbWidth = int(
float(4 * ctrlsSize) / 7.0f);
762 m_trackWidth = float(8 * ctrlsSize) / 35.0f;
770 for (
auto const& iKV : inputToOutputStates)
772 for (
auto const& oKV : iKV.second)
774 jassert(0 < iKV.first);
775 auto output = getChannelTypeForChannelNumberInCurrentConfiguration(oKV.first);
776 if (juce::AudioChannelSet::ChannelType::unknown != output)
777 m_inputToOutputVals[iKV.first][output].first = oKV.second;
778 if (m_directionLessChannelTypes.contains(output) && m_directionslessChannelSliders.at(output) && m_currentlySelectedInput == iKV.first)
779 m_directionslessChannelSliders.at(output)->setValue(oKV.second, juce::dontSendNotification);
788 for (
auto const& iKV : inputToOutputLevels)
790 for (
auto const& oKV : iKV.second)
792 jassert(0 < iKV.first);
793 auto output = getChannelTypeForChannelNumberInCurrentConfiguration(oKV.first);
794 if (juce::AudioChannelSet::ChannelType::unknown != output)
795 m_inputToOutputVals[iKV.first][output].second = oKV.second;
796 if (m_directionLessChannelTypes.contains(output) && m_directionslessChannelSliders.at(output) && m_currentlySelectedInput == iKV.first)
797 m_directionslessChannelSliders.at(output)->setValue(oKV.second, juce::dontSendNotification);
801 if (0 == m_currentlySelectedInput)
802 for (
auto const& sliderKV : m_directionslessChannelSliders)
803 if (sliderKV.second && sliderKV.second->displayValueConverter)
804 configureDirectionlessSliderToRelativeCtrl(sliderKV.first, *sliderKV.second);
811 auto wasUpdated = TwoDFieldBase::setChannelConfiguration(channelLayout);
813 rebuildDirectionslessChannelSliders();
818void TwoDFieldMultisliderComponent::rebuildDirectionslessChannelSliders()
820 m_directionslessChannelSliders.clear();
821 for (
auto const& channelType : m_directionLessChannelTypes)
823 m_directionslessChannelSliders[channelType] = std::make_unique<JUCEAppBasics::ToggleStateSlider>(juce::Slider::LinearVertical, juce::Slider::NoTextBox);
824 m_directionslessChannelSliders[channelType]->setColour(juce::Slider::ColourIds::trackColourId, getLookAndFeel().findColour(JUCEAppBasics::CustomLookAndFeel::ColourIds::MeteringRmsColourId));
825 m_directionslessChannelSliders[channelType]->setRange(0.0, 1.0, 0.01);
826 m_directionslessChannelSliders[channelType]->setToggleState(
false, juce::dontSendNotification);
827 m_directionslessChannelSliders[channelType]->displayValueConverter = [](
double val) {
return juce::String(juce::Decibels::gainToDecibels(val,
static_cast<double>(
ProcessorDataAnalyzer::getGlobalMindB())), 1) +
" dB"; };
828 m_directionslessChannelSliders[channelType]->onToggleStateChange = [
this, channelType]() {
829 std::map<std::uint16_t, std::map<std::uint16_t, bool >> states;
830 if (0 != m_currentlySelectedInput)
832 m_inputToOutputVals[m_currentlySelectedInput][channelType].first = m_directionslessChannelSliders[channelType]->getToggleState();
833 states[m_currentlySelectedInput][std::uint16_t(getChannelNumberForChannelTypeInCurrentConfiguration(channelType))] = m_directionslessChannelSliders[channelType]->getToggleState();
837 for (
auto& ioValKV : m_inputToOutputVals)
839 ioValKV.second[channelType].first = m_directionslessChannelSliders[channelType]->getToggleState();
840 auto outputChannel = getChannelNumberForChannelTypeInCurrentConfiguration(channelType);
841 if (outputChannel <= m_currentOutputCount)
842 states[ioValKV.first][std::uint16_t(outputChannel)] = m_directionslessChannelSliders[channelType]->getToggleState();
849 m_directionslessChannelSliders[channelType]->onValueChange = [
this, channelType]() {
850 std::map<std::uint16_t, std::map<std::uint16_t, float >> values;
851 if (0 != m_currentlySelectedInput)
853 auto value = float(m_directionslessChannelSliders[channelType]->getValue());
854 m_inputToOutputVals[m_currentlySelectedInput][channelType].second = value;
855 values[m_currentlySelectedInput][std::uint16_t(getChannelNumberForChannelTypeInCurrentConfiguration(channelType))] = value;
859 auto latestValue = m_directionslessChannelSliders[channelType]->getValue();
860 auto latestDelta = latestValue - m_directionlessSliderRelRef[channelType];
861 for (
auto& ioValKV : m_inputToOutputVals)
863 ioValKV.second[channelType].second = jlimit(0.0f, 1.0f, ioValKV.second[channelType].second +
float(latestDelta));
864 auto outputChannel = getChannelNumberForChannelTypeInCurrentConfiguration(channelType);
865 if (outputChannel <= m_currentOutputCount)
866 values[ioValKV.first][std::uint16_t(outputChannel)] = ioValKV.second[channelType].second;
869 m_directionlessSliderRelRef[channelType] = latestValue;
875 addAndMakeVisible(m_directionslessChannelSliders[channelType].get());
877 m_directionslessChannelLabels[channelType] = std::make_unique<juce::Label>();
878 m_directionslessChannelLabels[channelType]->setText(juce::AudioChannelSet::getAbbreviatedChannelTypeName(channelType), juce::dontSendNotification);
879 m_directionslessChannelLabels[channelType]->setJustificationType(juce::Justification::centred);
880 addAndMakeVisible(m_directionslessChannelLabels[channelType].get());
884void TwoDFieldMultisliderComponent::configureDirectionlessSliderToRelativeCtrl(
const juce::AudioChannelSet::ChannelType& channelType, JUCEAppBasics::ToggleStateSlider& slider)
886 auto anyInputToDirectionLessOff =
false;
887 for (
auto const& iKV : m_inputToOutputVals)
889 if (0 < iKV.second.count(channelType) && !iKV.second.at(channelType).first)
891 anyInputToDirectionLessOff =
true;
897 slider.setRange(0.0, 1.0, 0.01);
898 slider.setValue(0.5);
899 slider.displayValueConverter = {};
900 slider.setToggleState(!anyInputToDirectionLessOff, juce::dontSendNotification);
901 m_directionlessSliderRelRef[channelType] = 0.5;
static int getGlobalMindB()
std::function< void(std::uint16_t channel, const TwoDMultisliderValue &value, const float &sharpness, std::optional< ChannelLayer > layer)> onInputPositionChanged
void setInputPositionValue(std::uint16_t channel, const TwoDMultisliderValue &value, juce::NotificationType notification=juce::dontSendNotification)
void mouseDown(const juce::MouseEvent &e) override
~TwoDFieldMultisliderComponent()
void setInputPositionLayer(std::uint16_t channel, const ChannelLayer &layer, juce::NotificationType notification=juce::dontSendNotification)
void setInputPositionSharpness(std::uint16_t channel, const float &sharpness, juce::NotificationType notification=juce::dontSendNotification)
void triggerInputPositionsDump()
void setInputToOutputStates(const std::map< std::uint16_t, std::map< std::uint16_t, bool > > &inputToOutputStates)
void selectInput(std::uint16_t channel, bool selectOn, juce::NotificationType notification=juce::dontSendNotification)
std::function< void(std::uint16_t channel)> onInputSelected
void setInputPosition(std::uint16_t channel, const TwoDMultisliderValue &value, const float &panningSharpness, const ChannelLayer &layer, juce::NotificationType notification=juce::dontSendNotification)
void mouseUp(const MouseEvent &e) override
std::function< void(const std::map< std::uint16_t, std::map< std::uint16_t, float > > &)> onInputToOutputValuesChanged
void mouseDrag(const MouseEvent &e) override
void setControlsSize(int ctrlsSize)
std::function< void(const std::map< std::uint16_t, std::map< std::uint16_t, bool > > &)> onInputToOutputStatesChanged
TwoDFieldMultisliderComponent()
void setIOCount(const std::pair< int, int > &ioCount)
void paint(Graphics &) override
void setInputToOutputLevels(const std::map< std::uint16_t, std::map< std::uint16_t, float > > &inputToOutputLevels)
std::unique_ptr< juce::ComponentTraverser > createKeyboardFocusTraverser() override
bool setChannelConfiguration(const juce::AudioChannelSet &channelLayout) override
void lookAndFeelChanged() override
virtual ~TwoDFieldMultisliderKeyboardFocusTraverser()=default
std::vector< juce::Component * > getAllComponents(juce::Component *) override
juce::Component * getDefaultComponent(juce::Component *parentComponent) override
TwoDFieldMultisliderKeyboardFocusTraverser(const std::vector< juce::Component * > &focusElements)
juce::Component * getNextComponent(juce::Component *current) override
juce::Component * getPreviousComponent(juce::Component *current) override