Umsci
Upmix Spatial Control Interface — OCA/OCP.1 spatial audio utility
Loading...
Searching...
No Matches
UmsciLoudspeakersPaintComponent.cpp
Go to the documentation of this file.
1/* Copyright (c) 2026, Christian Ahrens
2 *
3 * This file is part of Umsci <https://github.com/ChristianAhrens/Umsci>
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
26
30
32{
33 // paint Speaker positions
34 g.setColour(m_speakerDrawablesCurrentColour);
35 for (auto const& speakerDrawableKV : m_speakerDrawables)
36 {
37 // draw speaker icons in target area
38 speakerDrawableKV.second->drawWithin(g, m_speakerDrawableAreas[speakerDrawableKV.first], juce::RectanglePlacement::centred, 1.0f);
39 // draw framing rect around icons, proportional to drawable size
40 g.drawRect(m_speakerDrawableAreas[speakerDrawableKV.first].expanded(2.0f * getControlsSizeMultiplier()));
41 }
42}
43
45{
46 PrerenderSpeakersInBounds();
47}
48
50{
51 UmsciPaintNControlComponentBase::lookAndFeelChanged();
52
53 for (auto const& speakerDrawableKV : m_speakerDrawables)
54 if (speakerDrawableKV.second)
55 speakerDrawableKV.second->replaceColour(m_speakerDrawablesCurrentColour, getLookAndFeel().findColour(juce::TextButton::textColourOnId));
56
57 m_speakerDrawablesCurrentColour = getLookAndFeel().findColour(juce::TextButton::textColourOnId);
58}
59
60void UmsciLoudspeakersPaintComponent::PrerenderSpeakerDrawable(std::int16_t speakerId, const std::array<std::float_t, 6>& rotNPos)
61{
62 auto& hor = rotNPos.at(0);
63 auto& ver = rotNPos.at(1);
64 auto& rot = rotNPos.at(2);
65 auto& x = rotNPos.at(3);
66 auto& y = rotNPos.at(4);
67 auto& z = rotNPos.at(5);
68
69 if (hor != 0.0f || ver != 0.0f || rot != 0.0f || x != 0.0f || y != 0.0f || z != 0.0f)
70 {
71 if (juce::isWithin<int>((int(std::abs(ver)) % 180), 90, 15))
72 m_speakerDrawables[speakerId] = Drawable::createFromSVG(*XmlDocument::parse(BinaryData::loudspeaker_vert24px_svg));
73 else
74 m_speakerDrawables[speakerId] = Drawable::createFromSVG(*XmlDocument::parse(BinaryData::loudspeaker_hor24px_svg));
75 auto& drawable = m_speakerDrawables.at(speakerId);
76 drawable->replaceColour(Colours::black, m_speakerDrawablesCurrentColour);
77 auto drawableBounds = drawable->getBounds().toFloat();
78 // Combine all three angles for correct 2D screen rotation:
79 // hor (azimuth) contributes fully when ver=0 (horizontal speaker), fades with cos(ver)
80 // rot (roll) is invisible in top-down view for ver=0, but maps directly to 2D rotation when ver=90 (vertical speaker), weighted by sin(ver)
81 // +90deg adjusts d&b coordinate convention to screen orientation
82 auto verRad = juce::degreesToRadians(ver);
83 auto angle2D = -hor * std::cos(verRad) + rot * std::sin(verRad) + 90.0f;
84 drawable->setTransform(juce::AffineTransform::rotation(juce::degreesToRadians(angle2D), drawableBounds.getCentreX(), drawableBounds.getCentreY()));
85 }
86}
87
88void UmsciLoudspeakersPaintComponent::setSpeakerPositions(const std::map<std::int16_t, std::array<std::float_t, 6>>& speakerPositions)
89{
90 if (speakerPositions.empty())
91 {
92 m_speakerPositions.clear();
93 m_speakerDrawableAreas.clear();
94 m_speakerDrawables.clear();
95 }
96 else
97 {
98 m_speakerPositions = speakerPositions;
99
100 for (auto const speakerPositionKV : speakerPositions)
101 PrerenderSpeakerDrawable(speakerPositionKV.first, speakerPositionKV.second);
102
103 PrerenderSpeakersInBounds();
104 }
105
106 repaint();
107}
108
109void UmsciLoudspeakersPaintComponent::setSpeakerPosition(std::int16_t speakerId, const std::array<std::float_t, 6>& position)
110{
111 m_speakerPositions[speakerId] = position;
112 PrerenderSpeakerDrawable(speakerId, position);
113 m_speakerDrawableAreas[speakerId] = juce::Rectangle<float>(0.0f, 0.0f, 16.0f * getControlsSizeMultiplier(), 16.0f * getControlsSizeMultiplier())
114 .withCentre(GetPointForRealCoordinate({ position.at(3), position.at(4), position.at(5) }));
115 repaint();
116}
117
123
124void UmsciLoudspeakersPaintComponent::onZoomChanged()
125{
126 PrerenderSpeakersInBounds();
127 repaint();
128}
129
130void UmsciLoudspeakersPaintComponent::PrerenderSpeakersInBounds()
131{
132 // Speaker positions
133 for (auto const speakerPositionKV : m_speakerPositions)
134 {
135 auto& speakerId = speakerPositionKV.first;
136 auto& speakerRotNPos = speakerPositionKV.second;
137 auto& hor = speakerRotNPos.at(0);
138 auto& ver = speakerRotNPos.at(1);
139 auto& rot = speakerRotNPos.at(2);
140 auto& x = speakerRotNPos.at(3);
141 auto& y = speakerRotNPos.at(4);
142 auto& z = speakerRotNPos.at(5);
143 // check if speaker is set (position other than 0,0,0,0,0,0)
144 if (hor != 0.0f || ver != 0.0f || rot != 0.0f || x != 0.0f || y != 0.0f || z != 0.0f)
145 {
146 auto speakerArea = juce::Rectangle<float>(0.0f, 0.0f, 16.0f * getControlsSizeMultiplier(), 16.0f * getControlsSizeMultiplier()).withCentre(GetPointForRealCoordinate({ x, y, z }));
147 m_speakerDrawableAreas[speakerId] = speakerArea;
148 }
149 }
150}
151
void setControlsSize(ControlsSize size) override
Re-prerender all speaker drawables at the new icon size.
void lookAndFeelChanged() override
Re-prerender all speaker drawables in the new look-and-feel colour.
void setSpeakerPosition(std::int16_t speakerId, const std::array< std::float_t, 6 > &position)
Updates a single speaker position and re-prerenders its drawable.
void setSpeakerPositions(const std::map< std::int16_t, std::array< std::float_t, 6 > > &speakerPositions)
Replaces all speaker positions at once (e.g. on reconnect).
Abstract base class for all three overlaid visualisation layers in UmsciControlComponent.
ControlsSize
Visual size of source/speaker icons. Multiplier accessible via getControlsSizeMultiplier().
juce::Point< float > GetPointForRealCoordinate(const std::array< float, 3 > &realCoordinate)
Converts a 3D real-world coordinate to a 2D screen pixel point.
virtual void setControlsSize(ControlsSize size)
Updates the icon size; derived classes may override to re-prerender.
float getControlsSizeMultiplier() const
Returns a multiplier (e.g. 0.5 / 1.0 / 1.5) for S/M/L icon sizes.