Mema
Memory Matrix — multi-channel audio matrix monitor and router
Loading...
Searching...
No Matches
MemaMoComponent.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
19#include "MemaMoComponent.h"
20
28
30 : juce::Component()
31{
32 m_inputMeteringComponent = std::make_unique<Mema::MeterbridgeComponent>(Mema::MeterbridgeComponent::Direction::Horizontal);
33 addAndMakeVisible(m_inputMeteringComponent.get());
34
35 m_inputDataAnalyzer = std::make_unique<Mema::ProcessorDataAnalyzer>();
36 m_inputDataAnalyzer->addListener(m_inputMeteringComponent.get());
37
38 m_outputDataAnalyzer = std::make_unique<Mema::ProcessorDataAnalyzer>();
39
41}
42
46
48{
49 // output metering requires no spectrum processing
50 if (m_inputDataAnalyzer)
51 m_inputDataAnalyzer->setUseProcessingTypes(true, true, false);
52 if (m_outputDataAnalyzer)
53 m_outputDataAnalyzer->setUseProcessingTypes(true, true, false);
54
55 auto resizeRequired = false;
56 // if ioMeter should be visualized, make sure the components existis
57 if (!m_inputMeteringComponent)
58 {
59 m_inputMeteringComponent = std::make_unique<Mema::MeterbridgeComponent>(Mema::MeterbridgeComponent::Direction::Horizontal);
60 m_inputMeteringComponent->setChannelCount(m_currentIOCount.first);
61 addAndMakeVisible(m_inputMeteringComponent.get());
62 if (m_inputDataAnalyzer)
63 m_inputDataAnalyzer->addListener(m_inputMeteringComponent.get());
64 resizeRequired = true;
65 }
66 if (!m_outputMeteringComponent)
67 {
68 m_outputMeteringComponent = std::make_unique<Mema::MeterbridgeComponent>(Mema::MeterbridgeComponent::Direction::Horizontal);
69 m_outputMeteringComponent->setChannelCount(m_currentIOCount.second);
70 addAndMakeVisible(m_outputMeteringComponent.get());
71 if (m_outputDataAnalyzer)
72 m_outputDataAnalyzer->addListener(m_outputMeteringComponent.get());
73 resizeRequired = true;
74 }
75
76 // none of the other components outputfields/waveF are required - cleanup
77 if (m_outputFieldComponent)
78 {
79 if (m_outputDataAnalyzer)
80 m_outputDataAnalyzer->removeListener(m_outputFieldComponent.get());
81
82 removeChildComponent(m_outputFieldComponent.get());
83 m_outputFieldComponent.reset();
84 resizeRequired = true;
85 }
86
87 if (m_waveformComponent)
88 {
89 if (m_outputDataAnalyzer)
90 m_outputDataAnalyzer->removeListener(m_waveformComponent.get());
91
92 removeChildComponent(m_waveformComponent.get());
93 m_waveformComponent.reset();
94 resizeRequired = true;
95 }
96
97 if (m_spectrumComponent)
98 {
99 if (m_outputDataAnalyzer)
100 m_outputDataAnalyzer->removeListener(m_spectrumComponent.get());
101
102 removeChildComponent(m_spectrumComponent.get());
103 m_spectrumComponent.reset();
104 resizeRequired = true;
105 }
106
107 if (resizeRequired && !getLocalBounds().isEmpty())
108 resized();
109}
110
111void MemaMoComponent::setOutputFieldVisuActive(const juce::AudioChannelSet& channelConfiguration)
112{
113 // output field visu requires no spectrum processing
114 if (m_inputDataAnalyzer)
115 m_inputDataAnalyzer->setUseProcessingTypes(true, true, false);
116 if (m_outputDataAnalyzer)
117 m_outputDataAnalyzer->setUseProcessingTypes(true, true, false);
118
119 auto resizeRequired = false;
120 // if outputfields (incl. iMeter) should be visualized, make sure the components existis
121 if (!m_inputMeteringComponent)
122 {
123 m_inputMeteringComponent = std::make_unique<Mema::MeterbridgeComponent>(Mema::MeterbridgeComponent::Direction::Horizontal);
124 m_inputMeteringComponent->setChannelCount(m_currentIOCount.first);
125 addAndMakeVisible(m_inputMeteringComponent.get());
126 if (m_inputDataAnalyzer)
127 m_inputDataAnalyzer->addListener(m_inputMeteringComponent.get());
128 resizeRequired = true;
129 }
130 if (!m_outputFieldComponent)
131 {
132 m_outputFieldComponent = std::make_unique<Mema::TwoDFieldOutputComponent>();
133 addAndMakeVisible(m_outputFieldComponent.get());
134 if (m_outputDataAnalyzer)
135 m_outputDataAnalyzer->addListener(m_outputFieldComponent.get());
136 resizeRequired = true;
137 }
138 if (m_outputFieldComponent->setChannelConfiguration(channelConfiguration))
139 resizeRequired = true;
140
141 // none of the other components oMeter/waveF are required - cleanup
142 if (m_outputMeteringComponent)
143 {
144 if (m_outputDataAnalyzer)
145 m_outputDataAnalyzer->removeListener(m_outputMeteringComponent.get());
146
147 removeChildComponent(m_outputMeteringComponent.get());
148 m_outputMeteringComponent.reset();
149 resizeRequired = true;
150 }
151
152 if (m_waveformComponent)
153 {
154 if (m_outputDataAnalyzer)
155 m_outputDataAnalyzer->removeListener(m_waveformComponent.get());
156
157 removeChildComponent(m_waveformComponent.get());
158 m_waveformComponent.reset();
159 resizeRequired = true;
160 }
161
162 if (m_spectrumComponent)
163 {
164 if (m_outputDataAnalyzer)
165 m_outputDataAnalyzer->removeListener(m_spectrumComponent.get());
166
167 removeChildComponent(m_spectrumComponent.get());
168 m_spectrumComponent.reset();
169 resizeRequired = true;
170 }
171
172 if (resizeRequired && !getLocalBounds().isEmpty())
173 resized();
174}
175
177{
178 // waveform visu requires no spectrum processing
179 if (m_inputDataAnalyzer)
180 m_inputDataAnalyzer->setUseProcessingTypes(true, true, false);
181 if (m_outputDataAnalyzer)
182 m_outputDataAnalyzer->setUseProcessingTypes(true, true, false);
183
184 auto resizeRequired = false;
185 // if waveform should be visualized, make sure the component existis
186 if (!m_waveformComponent)
187 {
188 m_waveformComponent = std::make_unique<Mema::WaveformAudioComponent>();
189 addAndMakeVisible(m_waveformComponent.get());
190 if (m_outputDataAnalyzer)
191 m_outputDataAnalyzer->addListener(m_waveformComponent.get());
192 resizeRequired = true;
193 }
194
195 // none of the other components ioMeter/outpField are required - cleanup
196 if (m_inputMeteringComponent)
197 {
198 if (m_inputDataAnalyzer)
199 m_inputDataAnalyzer->removeListener(m_inputMeteringComponent.get());
200
201 removeChildComponent(m_inputMeteringComponent.get());
202 m_inputMeteringComponent.reset();
203 resizeRequired = true;
204 }
205
206 if (m_outputMeteringComponent)
207 {
208 if (m_outputDataAnalyzer)
209 m_outputDataAnalyzer->removeListener(m_outputMeteringComponent.get());
210
211 removeChildComponent(m_outputMeteringComponent.get());
212 m_outputMeteringComponent.reset();
213 resizeRequired = true;
214 }
215
216 if (m_outputFieldComponent)
217 {
218 if (m_outputDataAnalyzer)
219 m_outputDataAnalyzer->removeListener(m_outputFieldComponent.get());
220
221 removeChildComponent(m_outputFieldComponent.get());
222 m_outputFieldComponent.reset();
223 resizeRequired = true;
224 }
225
226 if (m_spectrumComponent)
227 {
228 if (m_outputDataAnalyzer)
229 m_outputDataAnalyzer->removeListener(m_spectrumComponent.get());
230
231 removeChildComponent(m_spectrumComponent.get());
232 m_spectrumComponent.reset();
233 resizeRequired = true;
234 }
235
236 if (resizeRequired && !getLocalBounds().isEmpty())
237 resized();
238}
239
241{
242 // spectrum visu requires spectrum processing
243 if (m_inputDataAnalyzer)
244 m_inputDataAnalyzer->setUseProcessingTypes(true, true, true);
245 if (m_outputDataAnalyzer)
246 m_outputDataAnalyzer->setUseProcessingTypes(true, true, true);
247
248 auto resizeRequired = false;
249 // if spectrum should be visualized, make sure the component existis
250 if (!m_spectrumComponent)
251 {
252 m_spectrumComponent = std::make_unique<Mema::SpectrumAudioComponent>();
253 addAndMakeVisible(m_spectrumComponent.get());
254 if (m_outputDataAnalyzer)
255 m_outputDataAnalyzer->addListener(m_spectrumComponent.get());
256 resizeRequired = true;
257 }
258
259 // none of the other components ioMeter/outpField are required - cleanup
260 if (m_inputMeteringComponent)
261 {
262 if (m_inputDataAnalyzer)
263 m_inputDataAnalyzer->removeListener(m_inputMeteringComponent.get());
264
265 removeChildComponent(m_inputMeteringComponent.get());
266 m_inputMeteringComponent.reset();
267 resizeRequired = true;
268 }
269
270 if (m_outputMeteringComponent)
271 {
272 if (m_outputDataAnalyzer)
273 m_outputDataAnalyzer->removeListener(m_outputMeteringComponent.get());
274
275 removeChildComponent(m_outputMeteringComponent.get());
276 m_outputMeteringComponent.reset();
277 resizeRequired = true;
278 }
279
280 if (m_outputFieldComponent)
281 {
282 if (m_outputDataAnalyzer)
283 m_outputDataAnalyzer->removeListener(m_outputFieldComponent.get());
284
285 removeChildComponent(m_outputFieldComponent.get());
286 m_outputFieldComponent.reset();
287 resizeRequired = true;
288 }
289
290 if (m_waveformComponent)
291 {
292 if (m_outputDataAnalyzer)
293 m_outputDataAnalyzer->removeListener(m_waveformComponent.get());
294
295 removeChildComponent(m_waveformComponent.get());
296 m_waveformComponent.reset();
297 resizeRequired = true;
298 }
299
300 if (resizeRequired && !getLocalBounds().isEmpty())
301 resized();
302}
303
304std::optional<std::uint16_t> MemaMoComponent::getNumVisibleChannels()
305{
306 if (!m_waveformComponent)
307 return std::nullopt;
308 else
309 return std::uint16_t(m_waveformComponent->getNumVisibleChannels());
310}
311
313{
314 if (m_waveformComponent)
315 m_waveformComponent->setNumVisibleChannels(int(count));
316}
317
318void MemaMoComponent::paint(Graphics &g)
319{
320 g.fillAll(getLookAndFeel().findColour(juce::Slider::backgroundColourId));
321}
322
324{
325 if (m_inputMeteringComponent && m_outputFieldComponent)
326 {
327 auto margin = 8;
328 auto bounds = getLocalBounds().reduced(margin, margin);
329 auto boundsAspect = bounds.toFloat().getAspectRatio();
330 auto fieldAspect = m_outputFieldComponent->getRequiredAspectRatio();
331 if (boundsAspect >= 1 / fieldAspect)
332 {
333 // landscape
334 auto outputsBounds = bounds.removeFromRight(int(bounds.getHeight() / fieldAspect));
335 outputsBounds.removeFromLeft(margin / 2);
336 auto inputsBounds = bounds;
337 inputsBounds.removeFromRight(margin / 2);
338
339 m_inputMeteringComponent->setBounds(inputsBounds);
340 m_outputFieldComponent->setBounds(outputsBounds);
341 }
342 else
343 {
344 // portrait
345 auto outputBounds = bounds.removeFromBottom(int(bounds.getWidth() * fieldAspect));
346 outputBounds.removeFromTop(margin / 2);
347 auto inputBounds = bounds;
348 inputBounds.removeFromBottom(margin / 2);
349
350 m_inputMeteringComponent->setBounds(inputBounds);
351 m_outputFieldComponent->setBounds(outputBounds);
352 }
353 }
354 else if (m_inputMeteringComponent && m_outputMeteringComponent)
355 {
356 auto margin = 8;
357 auto bounds = getLocalBounds().reduced(margin, margin);
358 if (!bounds.isEmpty() && bounds.getAspectRatio() >= 1)
359 {
360 // landscape
361 if (m_inputMeteringComponent && m_outputMeteringComponent)
362 {
363 auto ic = float(m_inputMeteringComponent->getChannelCount());
364 auto oc = float(m_outputMeteringComponent->getChannelCount());
365
366 if (0.0f != ic && 0.0f != oc)
367 m_ioRatio = ic / (ic + oc);
368 }
369
370 auto inputsBounds = bounds.removeFromLeft(int(bounds.getWidth() * m_ioRatio));
371 inputsBounds.removeFromRight(margin / 2);
372 auto outputsBounds = bounds;
373 outputsBounds.removeFromLeft(margin / 2);
374
375 m_inputMeteringComponent->setBounds(inputsBounds);
376 m_outputMeteringComponent->setBounds(outputsBounds);
377 }
378 else
379 {
380 // portrait
381 auto inputBounds = bounds.removeFromTop(bounds.getHeight() / 2);
382 inputBounds.removeFromBottom(margin / 2);
383 auto outputBounds = bounds;
384 outputBounds.removeFromTop(margin / 2);
385
386 m_inputMeteringComponent->setBounds(inputBounds);
387 m_outputMeteringComponent->setBounds(outputBounds);
388 }
389 }
390 else if (m_waveformComponent)
391 {
392 auto margin = 8;
393 auto bounds = getLocalBounds().reduced(margin, margin);
394 m_waveformComponent->setBounds(bounds);
395 }
396 else if (m_spectrumComponent)
397 {
398 auto margin = 8;
399 auto bounds = getLocalBounds().reduced(margin, margin);
400 m_spectrumComponent->setBounds(bounds);
401 }
402}
403
404void MemaMoComponent::handleMessage(const Message& message)
405{
406 if (RunningStatus::Active != m_runningStatus)
407 {
408 m_runningStatus = RunningStatus::Active;
409 resized();
410 }
411
412 if (auto const apm = dynamic_cast<const Mema::AnalyzerParametersMessage*>(&message))
413 {
414 auto sampleRate = apm->getSampleRate();
415 jassert(sampleRate > 0);
416 auto maximumExpectedSamplesPerBlock = apm->getMaximumExpectedSamplesPerBlock();
417 jassert(maximumExpectedSamplesPerBlock > 0);
418
419 if (m_inputDataAnalyzer)
420 m_inputDataAnalyzer->initializeParameters(sampleRate, maximumExpectedSamplesPerBlock);
421 if (m_outputDataAnalyzer)
422 m_outputDataAnalyzer->initializeParameters(sampleRate, maximumExpectedSamplesPerBlock);
423 }
424 else if (auto const iom = dynamic_cast<const Mema::ReinitIOCountMessage*>(&message))
425 {
426 auto inputCount = iom->getInputCount();
427 jassert(inputCount > 0);
428 if (m_inputMeteringComponent)
429 m_inputMeteringComponent->setChannelCount(inputCount);
430 auto outputCount = iom->getOutputCount();
431 jassert(outputCount > 0);
432 if (m_outputMeteringComponent)
433 m_outputMeteringComponent->setChannelCount(outputCount);
434
435 m_currentIOCount = std::make_pair(inputCount, outputCount);
436
437 resized();
438 }
439 else if (auto m = dynamic_cast<const Mema::AudioBufferMessage*>(&message))
440 {
441 if (m->getFlowDirection() == Mema::AudioBufferMessage::FlowDirection::Input && m_inputDataAnalyzer)
442 {
443 m_inputDataAnalyzer->analyzeData(m->getAudioBuffer());
444 }
445 else if (m->getFlowDirection() == Mema::AudioBufferMessage::FlowDirection::Output && m_outputDataAnalyzer)
446 {
447 m_outputDataAnalyzer->analyzeData(m->getAudioBuffer());
448 }
449 }
450}
451
void setSpectrumVisuActive()
Switches to the FFT spectrum visualisation mode.
@ Active
Actively receiving and visualising audio data from Mema.
void setNumVisibleChannels(std::uint16_t count)
Propagates a channel-count change to the active visualisation component.
void resized() override
Lays out visualisation components to fill the available area.
void paint(juce::Graphics &g) override
Paints the background when no visualisation is active.
void setWaveformVisuActive()
Switches to the scrolling waveform visualisation mode.
void setOutputFieldVisuActive(const juce::AudioChannelSet &channelConfiguration)
Switches to the 2-D spatial field visualisation for the given speaker layout.
std::optional< std::uint16_t > getNumVisibleChannels()
Returns the number of channels currently shown, or empty if no active visualiser.
void handleMessage(const Message &message) override
Dispatches inbound network messages to the appropriate analyzer or state handler.
~MemaMoComponent() override
void setOutputMeteringVisuActive()
Switches to the meterbridge (level-bar) visualisation mode.
Carries audio-device parameters (sample rate, block size) from Mema to clients.
Base message carrying a serialised audio buffer and its flow-direction metadata.
@ Input
Pre-matrix input samples (as seen by the input analyzers).
@ Output
Post-matrix output samples (as seen by the output analyzers).
@ Horizontal
Bars grow left-to-right.
Instructs clients to tear down and rebuild their UI for a new channel count.