tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ExternalController.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14ExternalController::ExternalController (Engine& e, ControlSurface* c) : engine (e), controlSurface (c)
15{
17 controlSurface->owner = this;
18
19 auto& cs = getControlSurface();
20 auto& storage = engine.getPropertyStorage();
21
22 maxTrackNameChars = cs.numCharactersForTrackNames;
23 needsBackChannel = cs.needsMidiBackChannel;
24 needsChannel = cs.needsMidiChannel;
25 needsOSC = cs.needsOSCSocket;
26 wantsClock = cs.wantsClock;
27 followsTrackSelection = cs.followsTrackSelection;
28 deletable = cs.deletable;
29 auxBank = cs.wantsAuxBanks || cs.auxMode == AuxPosition::byPosition ? 0 : -1;
30 allowBankingOffEnd = cs.allowBankingOffEnd;
31
32 numDevices = engine.getPropertyStorage().getPropertyItem (SettingID::externControlNum, getName(), 1);
33 mainDevice = engine.getPropertyStorage().getPropertyItem (SettingID::externControlMain, getName(), 0);
34
35 inputDeviceNames.add (storage.getPropertyItem (SettingID::externControlIn, getName()));
36 outputDeviceNames.add (storage.getPropertyItem (SettingID::externControlOut, getName()));
37
38 for (int i = 1; i < maxDevices; i++)
39 {
40 inputDeviceNames.add (storage.getPropertyItem (SettingID::externControlIn, getName() + juce::String (i)));
41 outputDeviceNames.add (storage.getPropertyItem (SettingID::externControlOut, getName() + juce::String (i)));
42 }
43
44 inputDevices.resize ((size_t) maxDevices);
45 outputDevices.resize ((size_t) maxDevices);
46
47 oscInputPort = storage.getPropertyItem (SettingID::externOscInputPort, getName());
48 oscOutputPort = storage.getPropertyItem (SettingID::externOscOutputPort, getName());
49 oscOutputAddr = storage.getPropertyItem (SettingID::externOscOutputAddr, getName());
50
51 showTrackSelection = storage.getPropertyItem (SettingID::externControlShowSelection, getName());
52 showClipSlotSelection = storage.getPropertyItem (SettingID::externControlShowSelection, getName());
53 selectionColour = juce::Colour::fromString (storage.getPropertyItem (SettingID::externControlSelectionColour, getName(),
54 juce::Colours::red.withHue (0.0f).withSaturation (0.7f).toString()).toString());
55 enabled = storage.getPropertyItem (SettingID::externControlEnable, getName());
56
57 midiInOutDevicesChanged();
58 oscSettingsChanged();
59
60 cs.initialiseDevice (isEnabled());
61 if (numDevices != 1)
62 cs.numExtendersChanged (numDevices - 1, mainDevice);
63
64 updateDeviceState();
65 changeParamBank (0);
66
67 auto& dm = engine.getDeviceManager();
68 dm.addChangeListener (this);
69}
70
71ExternalController::~ExternalController()
72{
74
75 if (auto af = getCurrentPlugin())
76 for (auto p : af->getAutomatableParameters())
77 p->removeListener (this);
78
79 getControlSurface().shutDownDevice();
80 controlSurface = nullptr;
81
82 auto& dm = engine.getDeviceManager();
83 dm.removeChangeListener (this);
84
85 for (auto& d : dm.getMidiInDevices())
86 if (auto min = dynamic_cast<PhysicalMidiInputDevice*> (d.get()))
87 if (min->isEnabled())
88 min->removeExternalController (this);
89
90 if (lastRegisteredSelectable != nullptr)
91 {
92 lastRegisteredSelectable->removeSelectableListener (this);
93 lastRegisteredSelectable = nullptr;
94 }
95}
96
97void ExternalController::changeListenerCallback (juce::ChangeBroadcaster*)
98{
99 hasMidiInput = {};
100}
101
102juce::String ExternalController::getName() const
103{
104 if (auto cs = controlSurface.get())
105 return cs->deviceDescription;
106
107 return {};
108}
109
110bool ExternalController::wantsDevice (const MidiID& m)
111{
112 if (auto cs = controlSurface.get())
113 return cs->wantsDevice (m);
114
115 return false;
116}
117
118juce::String ExternalController::getDesiredMidiChannel() const
119{
120 if (auto cs = controlSurface.get())
121 return cs->midiChannelName;
122
123 return {};
124}
125
126juce::String ExternalController::getDesiredMidiBackChannel() const
127{
128 if (auto cs = controlSurface.get())
129 return cs->midiBackChannelName;
130
131 return {};
132}
133
134Plugin* ExternalController::getCurrentPlugin() const
135{
136 return dynamic_cast<Plugin*> (currentParamSource.get());
137}
138
139void ExternalController::currentEditChanged (Edit* edit)
140{
141 if (controlSurface != nullptr)
142 {
144 getControlSurface().currentEditChanged (edit);
145 }
146}
147
148void ExternalController::currentSelectionManagerChanged (SelectionManager* sm)
149{
150 if (controlSurface != nullptr)
151 {
153 getControlSurface().currentSelectionManagerChanged (sm);
154 }
155}
156
157bool ExternalController::isEnabled() const
158{
159 if (needsChannel)
160 {
161 if (hasMidiInput.has_value())
162 return *hasMidiInput;
163
164 hasMidiInput = getMidiInputDevice (0).isNotEmpty();
165
166 return *hasMidiInput;
167 }
168
169 return enabled;
170}
171
172void ExternalController::setEnabled (bool e)
173{
174 if (controlSurface != nullptr && ! needsChannel)
175 {
177 enabled = e;
178
179 engine.getPropertyStorage().setPropertyItem (SettingID::externControlEnable, getName(), e);
180
181 getControlSurface().initialiseDevice (isEnabled());
182 updateDeviceState();
183 changeParamBank (0);
184 }
185}
186
187int ExternalController::getNumDevices() const
188{
189 return numDevices;
190}
191
192void ExternalController::setNumDevices (int num)
193{
194 numDevices = juce::jlimit (1, maxDevices, num);
195 mainDevice = juce::jlimit (0, numDevices - 1, num);
196
197 controlSurface->numExtendersChanged (num - 1, mainDevice);
198
199 engine.getPropertyStorage().setPropertyItem (SettingID::externControlNum, getName(), num);
200}
201
202int ExternalController::getMainDevice() const
203{
204 return mainDevice;
205}
206
207void ExternalController::setMainDevice (int num)
208{
209 mainDevice = juce::jlimit (0, numDevices - 1, num);
210
211 controlSurface->numExtendersChanged (num - 1, mainDevice);
212
213 engine.getPropertyStorage().setPropertyItem (SettingID::externControlMain, getName(), mainDevice);
214}
215
216juce::String ExternalController::getMidiInputDevice (int idx) const
217{
218 auto name = inputDeviceNames[idx];
219
220 if (name.isNotEmpty() && getMidiInputPorts().contains (name))
221 return name;
222
223 return {};
224}
225
226void ExternalController::setMidiInputDevice (int idx, const juce::String& nameOfMidiInput)
227{
229
230 if (nameOfMidiInput.isNotEmpty())
231 for (auto c : getExternalControllerManager().getControllers())
232 for (int i = 0; i < maxDevices; i++)
233 if (c != this && c->getMidiInputDevice (idx) == nameOfMidiInput)
234 c->setMidiInputDevice (idx, {});
235
236 hasMidiInput = {};
237 inputDeviceNames.set (idx, nameOfMidiInput);
238 engine.getPropertyStorage().setPropertyItem (SettingID::externControlIn,
239 getName() + (idx > 0 ? juce::String (idx) : juce::String()),
240 nameOfMidiInput);
241
242 midiInOutDevicesChanged();
243}
244
245juce::String ExternalController::getBackChannelDevice (int idx) const
246{
247 auto name = outputDeviceNames[idx];
248
249 if (name.isNotEmpty() && getMidiOutputPorts().contains (name))
250 return name;
251
252 return {};
253}
254
255void ExternalController::deleteController()
256{
257 if (controlSurface != nullptr)
258 getControlSurface().deleteController();
259}
260
261juce::Range<int> ExternalController::getActiveChannels() const noexcept
262{
263 return { channelStart, channelStart + getNumFaderChannels() };
264}
265
266juce::Range<int> ExternalController::getActiveParams() const noexcept
267{
268 return { startParamNumber, startParamNumber + getNumParameterControls() };
269}
270
271int ExternalController::getFaderIndexInActiveRegion (int i) const noexcept
272{
273 i -= channelStart;
274 return juce::isPositiveAndBelow (i, getNumFaderChannels()) ? i : -1;
275}
276
277int ExternalController::getNumFaderChannels() const noexcept
278{
279 if (auto cs = controlSurface.get())
280 return cs->numberOfFaderChannels;
281
282 return 0;
283}
284
285int ExternalController::getNumParameterControls() const noexcept
286{
287 if (auto cs = controlSurface.get())
288 return cs->numParameterControls;
289
290 return 0;
291}
292
293void ExternalController::midiInOutDevicesChanged()
294{
295 if (! needsMidiChannel())
296 return;
297
298 auto& dm = engine.getDeviceManager();
299
300 auto oldInputDevices = inputDevices;
301 auto oldOutputDevices = outputDevices;
302
303 for (auto& i : inputDevices)
304 i = nullptr;
305
306 for (auto& d : dm.getMidiInDevices())
307 {
309
310 if (auto min = dynamic_cast<PhysicalMidiInputDevice*> (d.get()))
311 {
312 if (min->isEnabled())
313 {
314 bool used = false;
315
316 for (int j = 0; j < numDevices; j++)
317 {
318 if (min->getName().equalsIgnoreCase (inputDeviceNames[j]))
319 {
320 inputDevices[(size_t) j] = min;
321 used = true;
322 }
323 }
324
325 if (used)
326 min->setExternalController (this);
327 else
328 min->removeExternalController (this);
329 }
330 }
331 }
332
333 for (auto& o : outputDevices)
334 o = nullptr;
335
336 for (int i = dm.getNumMidiOutDevices(); --i >= 0;)
337 {
339 auto mo = dm.getMidiOutDevice (i);
340
341 bool used = false;
342 for (int j = 0; j < numDevices; j++)
343 {
344 if (mo != nullptr && mo->isEnabled() && mo->getName().equalsIgnoreCase (outputDeviceNames[j]))
345 {
346 outputDevices[(size_t) j] = mo;
347 mo->setSendControllerMidiClock (wantsClock);
348 used = true;
349 }
350 }
351
352 if (used)
353 mo->setExternalController (this);
354 else
355 mo->removeExternalController (this);
356 }
357
358 if (oldInputDevices != inputDevices || oldOutputDevices != outputDevices)
359 startTimer (100);
360}
361
362void ExternalController::timerCallback()
363{
364 stopTimer();
365
367 if (controlSurface != nullptr)
368 getControlSurface().initialiseDevice (isEnabled());
369
370 updateDeviceState();
371 changeParamBank (0);
372}
373
374void ExternalController::oscSettingsChanged()
375{
376 if (! needsOSCSocket())
377 return;
378
380 if (controlSurface != nullptr)
381 getControlSurface().initialiseDevice (isEnabled());
382
383 getControlSurface().updateOSCSettings (oscInputPort, oscOutputPort, oscOutputAddr);
384
385 updateDeviceState();
386 changeParamBank (0);
387}
388
389void ExternalController::setBackChannelDevice (int idx, const juce::String& nameOfMidiOutput)
390{
392
393 if (nameOfMidiOutput.isNotEmpty())
394 {
395 for (auto c : getExternalControllerManager().getControllers())
396 for (int i = 0; i < maxDevices; i++)
397 if (c != this && c->getBackChannelDevice (i) == nameOfMidiOutput)
398 c->setBackChannelDevice (i, {});
399 }
400
401 outputDeviceNames.set (idx, nameOfMidiOutput);
402 engine.getPropertyStorage().setPropertyItem (SettingID::externControlOut,
403 getName() + (idx > 0 ? juce::String (idx) : juce::String()),
404 nameOfMidiOutput);
405
406 midiInOutDevicesChanged();
407}
408
409bool ExternalController::isUsingMidiOutputDevice (const MidiOutputDevice* d) const noexcept
410{
411 for (auto od : outputDevices)
412 if (od == d)
413 return true;
414
415 return false;
416}
417
418void ExternalController::setOSCInputPort (int port)
419{
420 oscInputPort = port;
421
422 engine.getPropertyStorage().setPropertyItem (SettingID::externOscInputPort, getName(), oscInputPort);
423 oscSettingsChanged();
424}
425
426void ExternalController::setOSCOutputPort (int port)
427{
428 oscOutputPort = port;
429
430 engine.getPropertyStorage().setPropertyItem (SettingID::externOscOutputPort, getName(), oscOutputPort);
431 oscSettingsChanged();
432}
433
434void ExternalController::setOSCOutputAddress (const juce::String addr)
435{
436 oscOutputAddr = addr;
437
438 engine.getPropertyStorage().setPropertyItem (SettingID::externOscOutputAddr, getName(), oscOutputAddr);
439 oscSettingsChanged();
440}
441
442void ExternalController::setSelectionColour (juce::Colour c)
443{
444 if (selectionColour != c)
445 {
446 selectionColour = c;
447 engine.getPropertyStorage().setPropertyItem (SettingID::externControlSelectionColour, getName(), c.toString());
448 getControlSurface().changed();
449 }
450}
451
452void ExternalController::setShowTrackSelectionColour (bool b)
453{
454 if (showTrackSelection != b)
455 {
456 showTrackSelection = b;
457 engine.getPropertyStorage().setPropertyItem (SettingID::externControlShowSelection, getName(), b);
458 getControlSurface().changed();
459 }
460}
461
462void ExternalController::setShowClipSlotSelectionColour (bool b)
463{
464 if (showClipSlotSelection != b)
465 {
466 showClipSlotSelection = b;
467 engine.getPropertyStorage().setPropertyItem (SettingID::externControlShowClipSlotSelection, getName(), b);
468 getControlSurface().changed();
469 }
470}
471
472void ExternalController::moveFader (int channelNum, float newSliderPos)
473{
474 int i = getFaderIndexInActiveRegion (channelNum);
475
476 if (i >= 0)
477 getControlSurface().moveFader (i, newSliderPos);
478}
479
480void ExternalController::moveMasterFader (float newPos)
481{
483 if (controlSurface != nullptr)
484 getControlSurface().moveMasterLevelFader (newPos);
485}
486
487void ExternalController::movePanPot (int channelNum, float newPan)
488{
489 int i = getFaderIndexInActiveRegion (channelNum);
490
491 if (i >= 0)
492 getControlSurface().movePanPot (i, newPan);
493}
494
495void ExternalController::moveMasterPanPot (float newPan)
496{
497 getControlSurface().moveMasterPanPot (newPan);
498}
499
500void ExternalController::updateSoloAndMute (int channelNum, Track::MuteAndSoloLightState state, bool isBright)
501{
502 int i = getFaderIndexInActiveRegion (channelNum);
503
504 if (i >= 0)
505 getControlSurface().updateSoloAndMute (i, state, isBright);
506}
507
508void ExternalController::soloCountChanged (bool anySoloTracks)
509{
510 if (controlSurface != nullptr)
511 getControlSurface().soloCountChanged (anySoloTracks);
512}
513
514void ExternalController::playStateChanged (bool isPlaying)
515{
516 if (controlSurface != nullptr)
517 getControlSurface().playStateChanged (isPlaying);
518}
519
520void ExternalController::recordStateChanged (bool isRecording)
521{
522 if (controlSurface != nullptr)
523 getControlSurface().recordStateChanged (isRecording);
524}
525
526void ExternalController::automationModeChanged (bool isReading, bool isWriting)
527{
528 if (controlSurface != nullptr)
529 {
530 getControlSurface().automationReadModeChanged (isReading);
531 getControlSurface().automationWriteModeChanged (isWriting);
532 }
533}
534
535void ExternalController::snapChanged (bool isOn)
536{
537 if (controlSurface != nullptr)
538 getControlSurface().snapOnOffChanged (isOn);
539}
540
541void ExternalController::loopChanged (bool isOn)
542{
543 if (controlSurface != nullptr)
544 getControlSurface().loopOnOffChanged (isOn);
545}
546
547void ExternalController::clickChanged (bool isOn)
548{
549 if (controlSurface != nullptr)
550 getControlSurface().clickOnOffChanged (isOn);
551}
552
553void ExternalController::channelLevelChanged (int channelNum, float l, float r)
554{
555 int i = getFaderIndexInActiveRegion (channelNum);
556
557 if (i >= 0)
558 getControlSurface().channelLevelChanged (i, l, r);
559}
560
561void ExternalController::masterLevelsChanged (float leftLevel, float rightLevel)
562{
563 if (controlSurface != nullptr)
564 getControlSurface().masterLevelsChanged (leftLevel, rightLevel);
565}
566
567void ExternalController::timecodeChanged (int barsOrHours,
568 int beatsOrMinutes,
569 int ticksOrSeconds,
570 int millisecs,
571 bool isBarsBeats,
572 bool isFrames)
573{
574 if (controlSurface != nullptr)
575 getControlSurface().timecodeChanged (barsOrHours, beatsOrMinutes, ticksOrSeconds,
576 millisecs, isBarsBeats, isFrames);
577}
578
579void ExternalController::trackSelected (int channelNum, bool isSelected)
580{
581 int i = getFaderIndexInActiveRegion (channelNum);
582
583 if (i >= 0)
584 getControlSurface().trackSelectionChanged (i, isSelected);
585}
586
587void ExternalController::selectOtherObject (SelectableClass::Relationship relationship, bool moveFromCurrentPlugin)
588{
589 if (auto sm = getExternalControllerManager().getSelectionManager())
590 {
591 if (moveFromCurrentPlugin
592 && currentParamSource != nullptr
593 && ! sm->isSelected (currentParamSource))
594 {
595 sm->selectOnly (currentParamSource);
596 }
597
598 sm->selectOtherObjects (relationship, false);
599 }
600}
601
602void ExternalController::muteOrUnmutePlugin()
603{
604 if (auto p = getCurrentPlugin())
605 p->setEnabled (! p->isEnabled());
606}
607
608void ExternalController::changePluginPreset (int delta)
609{
610 if (auto ep = dynamic_cast<ExternalPlugin*> (getCurrentPlugin()))
611 if (ep->getNumPrograms() > 1)
612 ep->setCurrentProgram (juce::jlimit (0,
613 ep->getNumPrograms() - 1,
614 ep->getCurrentProgram() + delta),
615 true);
616}
617
618void ExternalController::soloPluginTrack()
619{
620 if (auto p = getCurrentPlugin())
621 if (auto t = p->getOwnerTrack())
622 t->setSolo (! t->isSolo (false));
623}
624
625void ExternalController::muteOrUnmutePluginsInTrack()
626{
627 if (auto p = getCurrentPlugin())
628 if (auto t = p->getOwnerTrack())
629 t->flipAllPluginsEnablement();
630}
631
632void ExternalController::changeFaderBank (int delta, bool moveSelection)
633{
634 if (controlSurface != nullptr)
635 {
636 if (getEdit() != nullptr)
637 {
639 juce::SortedSet<int> selectedChannels;
640
641 auto& ecm = getExternalControllerManager();
642
643 for (int i = channelStart; i < (channelStart + getNumFaderChannels()); ++i)
644 selectedChannels.add(i);
645
646 channelStart = std::min (juce::jlimit (0, 127, channelStart + delta),
647 std::max (0, ecm.getNumChannelTracks()
648 - (allowBankingOffEnd ? 1 : getNumFaderChannels())));
649
650 for (int i = channelStart; i < (channelStart + getNumFaderChannels()); ++i)
651 {
652 if (selectedChannels.contains(i))
653 selectedChannels.removeValue(i);
654 else
655 selectedChannels.add(i);
656 }
657
658 updateDeviceState();
659
660 if (selectedChannels.size() > 0 && getShowTrackSelectionColour() && isEnabled())
661 for (int i = 0; i < selectedChannels.size(); ++i)
662 ecm.repaintTrack (selectedChannels[i]);
663
664 if (moveSelection)
665 {
666 if (auto sm = ecm.getSelectionManager())
667 if (auto t = ecm.getChannelTrack (channelStart))
668 if (! sm->isSelected (t) || sm->getNumObjectsSelected() != 1)
669 sm->selectOnly (t);
670 }
671 }
672 }
673}
674
675void ExternalController::changePadBank (int delta)
676{
677 if (controlSurface != nullptr)
678 {
679 padStart = std::max (0, padStart + delta);
680 updatePadColours();
681
682 auto& ecm = getExternalControllerManager();
683
684 if (getShowClipSlotSelectionColour() && isEnabled())
685 {
686 for (int i = channelStart; i < (channelStart + getNumFaderChannels()); ++i)
687 ecm.repaintSlots (i);
688 }
689 }
690}
691
692void ExternalController::changeParamBank (int delta)
693{
694 if (controlSurface != nullptr)
695 {
697 startParamNumber += delta;
698 updateParamList();
699 updateParameters();
700 }
701}
702
703void ExternalController::updateParamList()
704{
706
707 if (controlSurface != nullptr)
708 {
709 currentParams.clear();
710
711 if (auto plugin = getCurrentPlugin())
712 {
713 auto params (plugin->getFlattenedParameterTree());
714 AutomatableParameter::Array possibleParams;
715
716 #if TRACKTION_ENABLE_CONTROL_SURFACES
717 if ((getControlSurfaceIfType<NovationRemoteSl>() != nullptr
718 || getControlSurfaceIfType<RemoteSLCompact>() != nullptr)
719 && dynamic_cast<ExternalPlugin*> (plugin) != nullptr)
720 {
721 for (int i = 0; i < 6; ++i)
722 possibleParams.add (nullptr);
723 }
724 else
725 #endif
726 {
727 if (getControlSurface().wantsDummyParams)
728 for (int i = 0; i < 2; ++i)
729 possibleParams.add (nullptr);
730 }
731
732 for (auto p : params)
733 possibleParams.add (p);
734
735 if (controlSurface != nullptr)
736 {
737 startParamNumber = juce::jlimit (0,
738 std::max (0, possibleParams.size() - getControlSurface().numParameterControls),
739 startParamNumber);
740
741 for (int i = 0; i < getControlSurface().numParameterControls && i + startParamNumber < possibleParams.size(); ++i)
742 currentParams.add (possibleParams[startParamNumber + i]);
743 }
744 }
745 }
746}
747
748void ExternalController::userMovedParameterControl (int paramNumber, float newValue, bool delta)
749{
750 if (auto p = currentParams[paramNumber])
751 {
752 if (delta)
753 p->midiControllerMoved (std::clamp (p->getCurrentNormalisedValue() + newValue, 0.0f, 1.0f));
754 else
755 p->midiControllerMoved (newValue);
756 }
757}
758
759void ExternalController::userPressedParameterControl (int paramNumber)
760{
761 if (auto p = currentParams[paramNumber])
762 p->midiControllerPressed();
763}
764
765void ExternalController::userPressedGoToMarker (int marker)
766{
767 if (auto tc = getTransport())
768 if (auto ed = getEdit())
769 if (auto mc = ed->getMarkerManager().getMarkers().getObjectPointer (marker + startMarkerNumber))
770 tc->setPosition (mc->getPosition().getStart());
771}
772
773void ExternalController::updateParameters()
774{
776
777 if (controlSurface == nullptr || ! isEnabled())
778 return;
779
780 auto& cs = getControlSurface();
781
782 if (lastRegisteredSelectable != currentParamSource)
783 {
784 if (auto* p = getCurrentPlugin())
785 for (auto* param : p->getAutomatableParameters())
786 param->removeListener (this);
787
788 if (cs.showingPluginParams())
789 repaintParamSource();
790
791 currentParamSource = lastRegisteredSelectable;
792 updateParamList();
793
794 if (cs.showingPluginParams())
795 repaintParamSource();
796
797 if (auto* p = getCurrentPlugin())
798 for (auto* param : p->getAutomatableParameters())
799 param->addListener (this);
800 }
801
802 auto numAvailableParams = currentParams.size();
803
804 auto paramSourcePlugin = getCurrentPlugin();
805 cs.pluginBypass (paramSourcePlugin != nullptr && ! paramSourcePlugin->isEnabled());
806
807 if (numAvailableParams > 0)
808 {
809 for (int i = 0; i < numAvailableParams; ++i)
810 {
811 ParameterSetting param;
812
813 if (auto p = currentParams[i])
814 {
815 auto pn = p->getParameterShortName (cs.numCharactersForParameterLabels);
816
817 if (pn.length() > cs.numCharactersForParameterLabels)
818 pn = shortenName (pn, 7);
819
820 pn.copyToUTF8 (param.label, (size_t) std::min (cs.numCharactersForParameterLabels,
821 (int) sizeof (param.label) - 1));
822
823 param.value = juce::jlimit (0.0f, 1.0f, p->valueRange.convertTo0to1 (p->getCurrentBaseValue()));
824
825 auto s = p->getLabelForValue (p->getCurrentBaseValue());
826
827 if (s.isEmpty())
828 s = p->getCurrentValueAsString();
829
830 if (s.length() > 6)
831 {
832 if (s.toLowerCase().endsWith(" dB"))
833 s = s.dropLastCharacters(3);
834 else if (s.toLowerCase().endsWith("dB"))
835 s = s.dropLastCharacters(2);
836 }
837
838 if (s.length() > cs.numCharactersForParameterLabels)
839 s = shortenName (s, 7);
840
841 if (s.length() < 6)
842 s = juce::String (" ").substring (0, (7 - s.length()) / 2) + s;
843
844 s.copyToUTF8 (param.valueDescription, 6);
845
846 cs.parameterChanged (i, param);
847 }
848 else
849 {
850 param.label[0] = 0;
851 param.valueDescription[0] = 0;
852 param.value = 0.0f;
853
854 if (auto plugin = getCurrentPlugin())
855 {
856 auto t = plugin->getOwnerTrack();
857
858 if (startParamNumber + i == 0)
859 {
860 if (t != nullptr)
861 shortenName (t->getName(), 7)
862 .copyToUTF8 (param.label, (size_t) std::min (cs.numCharactersForParameterLabels,
863 (int) sizeof (param.label) - 1));
864
865 cs.parameterChanged (i, param);
866 }
867 else if (startParamNumber + i == 1)
868 {
869 shortenName (plugin->getName(), 7)
870 .copyToUTF8 (param.label, (size_t) std::min (cs.numCharactersForParameterLabels,
871 (int) sizeof (param.label) - 1));
872
873 cs.parameterChanged (i, param);
874 }
875 else
876 {
877 cs.clearParameter(i);
878 }
879 }
880 }
881 }
882 }
883 else
884 {
885 startParamNumber = 0;
886 }
887
888 for (int i = numAvailableParams; i < cs.numParameterControls; ++i)
889 cs.clearParameter (i);
890}
891
892void ExternalController::selectedPluginChanged()
893{
894 if (controlSurface != nullptr)
895 {
896 if (getControlSurface().canChangeSelectedPlugin() || lastRegisteredSelectable == nullptr)
897 {
898 if (lastRegisteredSelectable != nullptr)
899 lastRegisteredSelectable->removeSelectableListener (this);
900
901 lastRegisteredSelectable = nullptr;
902
903 if (auto sm = getExternalControllerManager().getSelectionManager())
904 lastRegisteredSelectable = sm->getSelectedObject (0);
905
906 if (lastRegisteredSelectable != nullptr)
907 lastRegisteredSelectable->addSelectableListener (this);
908
909 juce::String pluginName;
910 if (auto plugin = dynamic_cast<Plugin*> (lastRegisteredSelectable.get()))
911 pluginName = plugin->getName();
912
913 getControlSurface().currentSelectionChanged (pluginName);
914 updateParameters();
915 updateTrackSelectLights();
916 }
917 }
918}
919
920void ExternalController::selectableObjectChanged (Selectable*)
921{
922 updateParameters();
923 updateMarkers();
924}
925
926void ExternalController::selectableObjectAboutToBeDeleted (Selectable* s)
927{
928 if (currentParamSource == s)
929 {
930 if (auto* p = getCurrentPlugin())
931 for (auto* param : p->getAutomatableParameters())
932 param->removeListener (this);
933
934 currentParamSource = nullptr;
935 updateParamList();
936 }
937
938 if (lastRegisteredSelectable == s)
939 {
940 s->removeSelectableListener (this);
941 lastRegisteredSelectable = nullptr;
942
943 updateParameters();
944 }
945}
946
947void ExternalController::curveHasChanged (AutomatableParameter&)
948{
949 updateParams = true;
950 triggerAsyncUpdate();
951}
952
953void ExternalController::currentValueChanged (AutomatableParameter&)
954{
955 updateParams = true;
956 triggerAsyncUpdate();
957}
958
959void ExternalController::updateTrackSelectLights()
960{
961 for (int chan = channelStart; chan < (channelStart + getNumFaderChannels()); ++chan)
962 {
963 bool selected = false;
964
965 if (getEdit() != nullptr)
966 if (auto sm = getExternalControllerManager().getSelectionManager())
967 if (auto t = getExternalControllerManager().getChannelTrack (chan))
968 selected = sm->isSelected (t);
969
970 trackSelected (chan, selected);
971 }
972}
973
974void ExternalController::updateTrackRecordLights()
975{
976 if (auto ed = getEdit())
977 {
978 auto& ecm = getExternalControllerManager();
979
980 for (int chan = channelStart; chan < (channelStart + getNumFaderChannels()); ++chan)
981 {
982 if (auto t = ecm.getChannelTrack (chan))
983 {
984 bool isRecording = false;
985
986 for (auto in : ed->getAllInputDevices())
987 {
988 if (auto at = dynamic_cast<AudioTrack*> (t))
989 {
990 if (in->isRecordingActive (at->itemID) && in->getTargets().contains (at->itemID))
991 {
992 isRecording = true;
993 break;
994 }
995 }
996 }
997
998 getControlSurface().trackRecordEnabled (chan - channelStart, isRecording);
999 }
1000 else
1001 {
1002 getControlSurface().trackRecordEnabled (chan - channelStart, false);
1003 }
1004 }
1005 }
1006 else
1007 {
1008 for (int chan = channelStart; chan < (channelStart + getNumFaderChannels()); ++chan)
1009 getControlSurface().trackRecordEnabled (chan - channelStart, false);
1010 }
1011}
1012
1013void ExternalController::updatePunchLights()
1014{
1015 if (auto ed = getEdit())
1016 if (auto cs = controlSurface.get())
1017 cs->punchOnOffChanged (ed->recordingPunchInOut);
1018}
1019
1020void ExternalController::updateScrollLights()
1021{
1022 if (auto cs = controlSurface.get())
1023 cs->scrollOnOffChanged (AppFunctions::isScrolling());
1024}
1025
1026void ExternalController::updateUndoLights()
1027{
1028 if (auto ed = getEdit())
1029 if (auto cs = controlSurface.get())
1030 cs->undoStatusChanged (ed->getUndoManager().canUndo(),
1031 ed->getUndoManager().canRedo());
1032}
1033
1034void ExternalController::clearPadColours()
1035{
1036 auto& cs = getControlSurface();
1037
1038 if (cs.numberOfTrackPads > 0)
1039 {
1040 for (auto track = 0; track < cs.numberOfFaderChannels; track++)
1041 {
1042 cs.clipsPlayingStateChanged (track, false);
1043 for (auto scene = 0; scene < cs.numberOfTrackPads; scene++)
1044 cs.padStateChanged (track, scene, 0, 0);
1045 }
1046 }
1047}
1048
1049void ExternalController::updatePadColours()
1050{
1051 auto& ecm = getExternalControllerManager();
1052 auto& cs = getControlSurface();
1053
1054 if (cs.numberOfTrackPads > 0)
1055 {
1056 for (auto track = 0; track < cs.numberOfFaderChannels; track++)
1057 {
1058 {
1059 bool isPlaying = false;
1060
1061 if (auto at = dynamic_cast<AudioTrack*> (ecm.getChannelTrack (track + channelStart)))
1062 {
1063 for (auto slot : at->getClipSlotList().getClipSlots())
1064 {
1065 if (auto dest = slot->getInputDestination(); dest && dest->input.isRecording (dest->targetID))
1066 isPlaying = true;
1067
1068 if (auto c = slot->getClip())
1069 if (auto lh = c->getLaunchHandle())
1070 if (lh->getPlayingStatus() == LaunchHandle::PlayState::playing || lh->getQueuedStatus() == LaunchHandle::QueueState::playQueued)
1071 isPlaying = true;
1072
1073 if (isPlaying)
1074 break;
1075 }
1076 }
1077
1078 cs.clipsPlayingStateChanged (track, isPlaying);
1079 }
1080
1081 for (auto scene = 0; scene < cs.numberOfTrackPads; scene++)
1082 {
1083 auto colourIdx = 0;
1084 auto state = 0;
1085
1086 if (auto ft = dynamic_cast<FolderTrack*> (ecm.getChannelTrack (track + channelStart)))
1087 {
1088 for (auto at : ft->getAllAudioSubTracks (true))
1089 {
1090 if (auto slot = at->getClipSlotList().getClipSlots()[padStart + scene])
1091 {
1092 if (auto c = slot->getClip())
1093 {
1094 auto col = c->getColour();
1095
1096 if (! col.isTransparent())
1097 {
1098 auto numColours = 19;
1099 auto newHue = col.getHue();
1100
1101 colourIdx = cs.limitedPadColours ? 1 : juce::jlimit (0, numColours - 1, juce::roundToInt (newHue * (numColours - 1) + 1));
1102 break;
1103 }
1104 }
1105 }
1106 }
1107 }
1108 else if (auto at = dynamic_cast<AudioTrack*> (ecm.getChannelTrack (track + channelStart)))
1109 {
1110 if (auto slot = at->getClipSlotList().getClipSlots()[padStart + scene])
1111 {
1112 if (auto dest = slot->getInputDestination(); dest && dest->input.isRecording (dest->targetID))
1113 {
1114 auto epc = dest->input.edit.getTransport().getCurrentPlaybackContext();
1115 const auto currentTime = epc ? epc->getUnloopedPosition() : dest->input.edit.getTransport().getPosition();
1116
1117 if (currentTime < dest->input.getPunchInTime (dest->targetID))
1118 {
1119 colourIdx = cs.limitedPadColours ? 3 : 1;
1120 state = 1;
1121 }
1122 else
1123 {
1124 colourIdx = cs.limitedPadColours ? 3 : 1;
1125 state = 2;
1126 }
1127 }
1128 else if (auto c = slot->getClip())
1129 {
1130 auto col = c->getColour();
1131
1132 if (! col.isTransparent())
1133 {
1134 auto numColours = 19;
1135 auto newHue = col.getHue();
1136
1137 colourIdx = cs.limitedPadColours ? 1 : juce::jlimit (0, numColours - 1, juce::roundToInt (newHue * (numColours - 1) + 1));
1138 }
1139
1140 if (auto tc = getTransport())
1141 {
1142 if (auto lh = c->getLaunchHandle())
1143 {
1144 if (lh->getPlayingStatus() == LaunchHandle::PlayState::playing)
1145 {
1146 colourIdx = cs.limitedPadColours ? 2 : 8;
1147 state = tc->isPlaying() ? 2 : 1;
1148 }
1149 else if (lh->getQueuedStatus() == LaunchHandle::QueueState::playQueued)
1150 {
1151 colourIdx = cs.limitedPadColours ? 2 : 8;;
1152 state = 1;
1153 }
1154 }
1155 }
1156 }
1157 else if (cs.recentlyPressedPads.contains ({track + channelStart, padStart + scene}))
1158 {
1159 auto anyPlaying = [&]()
1160 {
1161 if (auto tc = getTransport())
1162 {
1163 for (auto slot_ : at->getClipSlotList().getClipSlots())
1164 {
1165 if (auto c_ = slot_->getClip())
1166 {
1167 if (auto lh = c_->getLaunchHandle())
1168 {
1169 if (lh->getPlayingStatus() == LaunchHandle::PlayState::playing && tc->isPlaying())
1170 return true;
1171 else if (lh->getQueuedStatus() == LaunchHandle::QueueState::playQueued)
1172 return true;
1173 }
1174 }
1175 }
1176 }
1177 return false;
1178 };
1179
1180 if (anyPlaying())
1181 {
1182 colourIdx = cs.limitedPadColours ? 3 : 1;
1183 state = 1;
1184 }
1185 else
1186 {
1187 cs.recentlyPressedPads.erase ({track + channelStart, padStart + scene});
1188 }
1189 }
1190 }
1191 }
1192
1193 cs.padStateChanged (track, scene, colourIdx, state);
1194 }
1195 }
1196 }
1197}
1198
1199void ExternalController::updateDeviceState()
1200{
1201 if (controlSurface != nullptr)
1202 {
1203 if (auto edit = getEdit())
1204 {
1205 auto& ecm = getExternalControllerManager();
1206 auto& cs = getControlSurface();
1207
1208 {
1210 bool anySoloTracks = edit->areAnyTracksSolo();
1211
1212 for (int chan = channelStart; chan < (channelStart + getNumFaderChannels()); ++chan)
1213 {
1214 if (auto t = ecm.getChannelTrack (chan))
1215 {
1216 auto at = dynamic_cast<AudioTrack*> (t);
1217 auto ft = dynamic_cast<FolderTrack*> (t);
1218
1219 if (auto vp = at != nullptr ? at->getVolumePlugin() : nullptr)
1220 {
1221 moveFader (chan, vp->getSliderPos());
1222 movePanPot (chan, vp->getPan());
1223 }
1224 else if (auto vca = ft != nullptr ? ft->getVCAPlugin() : nullptr)
1225 {
1226 moveFader (chan, vca->getSliderPos());
1227 movePanPot (chan, 0.0f);
1228 }
1229 else
1230 {
1231 moveFader (chan, decibelsToVolumeFaderPosition (0.0f));
1232 movePanPot (chan, 0.0f);
1233 }
1234
1235 updateSoloAndMute (chan, t->getMuteAndSoloLightState(), true);
1236
1237 channelLevelChanged (chan, 0.0f, 0.0f);
1238
1239 if (auto sm = ecm.getSelectionManager())
1240 trackSelected (chan, sm->isSelected (t));
1241 }
1242 else
1243 {
1244 moveFader (chan, decibelsToVolumeFaderPosition (0.0f));
1245 movePanPot (chan, 0.0f);
1246 updateSoloAndMute (chan, {}, false);
1247 channelLevelChanged (chan, 0.0f, 0.0f);
1248 trackSelected (chan, false);
1249 }
1250 }
1251
1252 updateTrackSelectLights();
1253 updateTrackRecordLights();
1254 soloCountChanged (anySoloTracks);
1255 }
1256
1257 {
1259
1260 if (auto masterVol = edit->getMasterVolumePlugin())
1261 {
1262 moveMasterFader (masterVol->getSliderPos());
1263 moveMasterPanPot (masterVol->getPan());
1264 }
1265
1266 juce::StringArray trackNames;
1267
1268 for (int i = 0; i < getNumFaderChannels(); ++i)
1269 {
1270 juce::String name;
1271
1272 if (auto track = ecm.getChannelTrack (i + channelStart))
1273 {
1274 juce::String trackName (track->getName());
1275
1276 if (trackName.startsWithIgnoreCase (TRANS("Track") + " ") && trackName.length() > maxTrackNameChars)
1277 trackName = juce::String (trackName.getTrailingIntValue());
1278 else if (trackName.length() > maxTrackNameChars)
1279 trackName = shortenName (trackName, maxTrackNameChars);
1280
1281 name = trackName.substring (0, maxTrackNameChars);
1282 }
1283
1284 trackNames.add (name);
1285 }
1286
1287 cs.faderBankChanged (channelStart, trackNames);
1288
1289 updatePadColours();
1290
1291 if (cs.showingMarkers())
1292 ecm.updateMarkers();
1293 }
1294
1295 if (auto tc = getTransport())
1296 {
1298 playStateChanged (tc->isPlaying());
1299 recordStateChanged (tc->isRecording());
1300
1301 automationModeChanged (edit->getAutomationRecordManager().isReadingAutomation(),
1302 edit->getAutomationRecordManager().isWritingAutomation());
1303
1304 snapChanged (tc->snapToTimecode);
1305 loopChanged (tc->looping);
1306 clickChanged (edit->clickTrackEnabled);
1307 cs.scrollOnOffChanged (AppFunctions::isScrolling());
1308 cs.punchOnOffChanged (edit->recordingPunchInOut);
1309 cs.slaveOnOffChanged (edit->isTimecodeSyncEnabled());
1310
1311 masterLevelsChanged (0.0f, 0.0f);
1312
1313 updateTrackRecordLights();
1314 cs.auxBankChanged (auxBank);
1315 auxSendLevelsChanged();
1316
1317 cs.updateMiscFeatures();
1318 }
1319 else
1320 {
1322 }
1323 }
1324 else
1325 {
1326 updateTrackSelectLights();
1327 updateTrackRecordLights();
1328 clearPadColours();
1329 }
1330 }
1331}
1332
1333void ExternalController::auxSendLevelsChanged()
1334{
1335 if (controlSurface != nullptr)
1336 {
1337 auto& ecm = getExternalControllerManager();
1338 auto& cs = getControlSurface();
1339
1340 for (int chan = channelStart; chan < (channelStart + getNumFaderChannels()); ++chan)
1341 {
1342 if (auto t = ecm.getChannelTrack (chan))
1343 {
1344 auto at = dynamic_cast<AudioTrack*> (t);
1345
1346 if (at == nullptr)
1347 {
1348 for (auto i = 0; i < cs.numAuxes; i++)
1349 cs.clearAux (chan - channelStart, i);
1350 }
1351 else if (cs.auxMode == AuxPosition::byPosition)
1352 {
1353 for (auto i = 0; i < cs.numAuxes; i++)
1354 {
1355 if (auto aux = at->getAuxSendPlugin (auxBank + i, AuxPosition::byPosition))
1356 {
1357 auto nm = aux->getBusName();
1358
1359 if (nm.length() > cs.numCharactersForAuxLabels)
1360 nm = shortenName (nm, cs.numCharactersForAuxLabels);
1361
1362 cs.moveAux (chan - channelStart, i, nm.toRawUTF8(), decibelsToVolumeFaderPosition (aux->getGainDb()));
1363 }
1364 else
1365 {
1366 cs.clearAux (chan - channelStart, i);
1367 }
1368 }
1369 }
1370 else
1371 {
1372 for (auto i = 0; i < cs.numAuxes; i++)
1373 {
1374 if (auto aux = at->getAuxSendPlugin (auxBank + i, AuxPosition::byBus))
1375 {
1376 auto nm = aux->getBusName();
1377
1378 if (nm.length() > cs.numCharactersForAuxLabels)
1379 nm = shortenName (nm, cs.numCharactersForAuxLabels);
1380
1381 cs.moveAux (chan - channelStart, i, nm.toRawUTF8(), decibelsToVolumeFaderPosition (aux->getGainDb()));
1382 }
1383 else
1384 {
1385 cs.clearAux (chan - channelStart, i);
1386 }
1387 }
1388 }
1389 }
1390 else
1391 {
1392 for (auto i = 0; i < cs.numAuxes; i++)
1393 cs.clearAux (chan - channelStart, i);
1394 }
1395 }
1396 }
1397}
1398
1399void ExternalController::acceptMidiMessage (MidiInputDevice& d, const juce::MidiMessage& m)
1400{
1402 const juce::ScopedLock sl (incomingMidiLock);
1403
1404 int idx = 0;
1405
1406 for (size_t i = 0; i < inputDevices.size(); ++i)
1407 if (inputDevices[i] == &d)
1408 idx = (int) i;
1409
1410 pendingMidiMessages.add ({ idx, m });
1411 processMidi = true;
1412 triggerAsyncUpdate();
1413}
1414
1415bool ExternalController::wantsMessage (MidiInputDevice& d, const juce::MidiMessage& m)
1416{
1417 int idx = 0;
1418
1419 for (size_t i = 0; i < inputDevices.size(); ++i)
1420 if (inputDevices[i] == &d)
1421 idx = (int) i;
1422
1423 return controlSurface != nullptr && getControlSurface().wantsMessage (idx, m);
1424}
1425
1426bool ExternalController::eatsAllMessages() const
1427{
1428 return controlSurface != nullptr && getControlSurface().eatsAllMessages();
1429}
1430
1431void ExternalController::setEatsAllMessages (bool eatAll)
1432{
1433 if (controlSurface != nullptr)
1434 getControlSurface().setEatsAllMessages (eatAll);
1435}
1436
1437void ExternalController::handleAsyncUpdate()
1438{
1439 if (processMidi)
1440 {
1441 processMidi = false;
1442
1443 if (controlSurface != nullptr)
1444 {
1446
1448 messages.ensureStorageAllocated (16);
1449
1450 {
1451 const juce::ScopedLock sl (incomingMidiLock);
1452 messages.swapWith (pendingMidiMessages);
1453 }
1454
1455 for (auto& m : messages)
1456 getControlSurface().acceptMidiMessage (m.first, m.second);
1457 }
1458 }
1459
1460 if (updateParams)
1461 {
1462 updateParams = false;
1463 updateParameters();
1464 }
1465}
1466
1467juce::String ExternalController::getNoDeviceSelectedMessage()
1468{
1469 return "<" + TRANS("No Device Selected") + ">";
1470}
1471
1472juce::StringArray ExternalController::getMidiInputPorts() const
1473{
1475 juce::StringArray inputNames;
1476 inputNames.add (getNoDeviceSelectedMessage());
1477
1478 auto& dm = engine.getDeviceManager();
1479
1480 auto wantsDevice = [] ([[maybe_unused]] MidiInputDevice* in)
1481 {
1482 #if ! JUCE_DEBUG
1483 if (dynamic_cast<VirtualMidiInputDevice*> (in))
1484 return false;
1485 #endif
1486
1487 return true;
1488 };
1489
1490 for (auto& m : dm.getMidiInDevices())
1491 if (m->isEnabled() && wantsDevice (m.get()))
1492 inputNames.add (m->getName());
1493
1494 return inputNames;
1495}
1496
1497juce::StringArray ExternalController::getMidiOutputPorts() const
1498{
1500 juce::StringArray outputNames;
1501 outputNames.add (getNoDeviceSelectedMessage());
1502 auto& dm = engine.getDeviceManager();
1503
1504 for (int i = 0; i < dm.getNumMidiOutDevices(); ++i)
1505 if (auto m = dm.getMidiOutDevice (i))
1506 if (m->isEnabled())
1507 outputNames.add(m->getName());
1508
1509 return outputNames;
1510}
1511
1512bool ExternalController::shouldTrackBeColoured (int channelNum)
1513{
1514 return channelNum >= channelStart
1515 && channelNum < (channelStart + getNumFaderChannels())
1516 && getControlSurface().showingTracks()
1517 && getShowTrackSelectionColour()
1518 && isEnabled();
1519}
1520
1521void ExternalController::getTrackColour (int channelNum, juce::Colour& color)
1522{
1523 if (channelNum >= channelStart
1524 && channelNum < channelStart + getNumFaderChannels()
1525 && getControlSurface().showingTracks()
1526 && getShowTrackSelectionColour()
1527 && isEnabled())
1528 {
1529 if (color.getARGB() == 0)
1530 color = getSelectionColour();
1531 else
1532 color = color.overlaidWith (getSelectionColour().withAlpha (0.8f));
1533 }
1534}
1535
1536std::optional<ColourArea> ExternalController::getColouredArea (const Edit& e)
1537{
1538 if (&e != getEdit())
1539 return {};
1540
1541 if (controlSurface == nullptr || ! getControlSurface().showingClipSlots() || ! getShowClipSlotSelectionColour() || ! isEnabled())
1542 return {};
1543
1544 auto& ecm = getExternalControllerManager();
1545
1546 auto t1 = ecm.getChannelTrack (channelStart);
1547 auto t2 = t1;
1548
1549 for (auto i = 0; i < controlSurface->numberOfFaderChannels; i++)
1550 if (auto t = t2 = ecm.getChannelTrack (channelStart + i))
1551 t2 = t;
1552
1553 if (t1 && t2)
1554 {
1555 ColourArea c = {getSelectionColour(), *t1, *t2, padStart, padStart + controlSurface->numberOfTrackPads - 1};
1556 return c;
1557 }
1558
1559 return {};
1560}
1561
1562bool ExternalController::shouldPluginBeColoured (Plugin* p)
1563{
1564 return controlSurface != nullptr
1565 && (getControlSurface().isPluginSelected (p)
1566 || (p == currentParamSource && getControlSurface().showingPluginParams()))
1567 && getShowTrackSelectionColour()
1568 && isEnabled();
1569}
1570
1571void ExternalController::getPluginColour (Plugin* plugin, juce::Colour& color)
1572{
1573 if (shouldPluginBeColoured (plugin) && getShowTrackSelectionColour() && isEnabled())
1574 {
1575 if (color.getARGB() == 0)
1576 color = getSelectionColour();
1577 else
1578 color = color.overlaidWith (getSelectionColour().withAlpha (0.8f));
1579 }
1580}
1581
1582void ExternalController::repaintParamSource()
1583{
1585
1586 if (auto plugin = getCurrentPlugin())
1587 getExternalControllerManager().repaintPlugin (*plugin);
1588}
1589
1590void ExternalController::redrawTracks()
1591{
1593
1594 auto& ecm = getExternalControllerManager();
1595 auto numTracks = ecm.getNumChannelTracks();
1596
1597 for (int i = 0; i < numTracks; ++i)
1598 ecm.repaintTrack (i);
1599}
1600
1601void ExternalController::changeMarkerBank (int delta)
1602{
1603 if (controlSurface != nullptr)
1604 {
1605 startMarkerNumber += delta;
1606 updateMarkers();
1607 }
1608}
1609
1610void ExternalController::updateMarkers()
1611{
1612 if (controlSurface != nullptr)
1613 {
1614 auto& cs = getControlSurface();
1615 int numMarkersUsed = 0;
1616
1617 if (auto ed = getEdit())
1618 {
1619 auto allMarkers = ed->getMarkerManager().getMarkers();
1620
1621 if (allMarkers.size() > 0)
1622 {
1623 startMarkerNumber = juce::jlimit (0,
1624 std::max (0, allMarkers.size() - cs.numMarkers),
1625 startMarkerNumber);
1626
1627 for (int i = 0; (i < cs.numMarkers) && (i + startMarkerNumber < allMarkers.size()); ++i)
1628 {
1629 if (auto mc = allMarkers.getObjectPointer (i + startMarkerNumber))
1630 {
1631 juce::String pn (mc->getName().replace ("marker", "mk", true));
1632
1633 if (pn.isEmpty())
1634 pn = juce::String (mc->getMarkerID());
1635 else if (pn.length() > cs.numCharactersForMarkerLabels)
1636 pn = shortenName (pn, 7);
1637
1638 MarkerSetting ms;
1639 pn.copyToUTF8 (ms.label, (size_t) std::min (cs.numCharactersForMarkerLabels,
1640 (int) sizeof (ms.label) - 1));
1641 ms.number = mc->getMarkerID();
1642 ms.absolute = mc->isSyncAbsolute();
1643
1644 cs.markerChanged (i, ms);
1645 numMarkersUsed = i + 1;
1646 }
1647 }
1648 }
1649 }
1650
1651 for (int i = numMarkersUsed; i < cs.numMarkers; ++i)
1652 cs.clearMarker(i);
1653 }
1654}
1655
1656void ExternalController::changeAuxBank (int delta)
1657{
1658 if (controlSurface != nullptr)
1659 {
1660 if (getControlSurface().auxMode == AuxPosition::byPosition)
1661 auxBank = juce::jlimit (0, 15, auxBank + delta);
1662 else
1663 auxBank = juce::jlimit (-1, 31, auxBank + delta);
1664
1665 getControlSurface().auxBankChanged (auxBank);
1666 auxSendLevelsChanged();
1667 }
1668}
1669
1670void ExternalController::setAuxBank (int num)
1671{
1672 if (controlSurface != nullptr)
1673 {
1674 if (getControlSurface().auxMode == AuxPosition::byPosition)
1675 auxBank = juce::jlimit (0, 15, num);
1676 else
1677 auxBank = juce::jlimit (-1, 31, num);
1678
1679 getControlSurface().auxBankChanged (auxBank);
1680 auxSendLevelsChanged();
1681 }
1682}
1683
1684juce::String ExternalController::shortenName (juce::String s, int maxLen)
1685{
1686 if (s.length() < maxLen)
1687 return s;
1688
1689 s = s.replace (TRANS("Track"), TRANS("Trk"));
1690
1691 if (s.length() < maxLen)
1692 return s;
1693
1694 bool hasSeenConsonant = false;
1695 juce::String result;
1696
1697 for (int i = 0; i < s.length(); ++i)
1698 {
1699 const bool isVowel = juce::String ("aeiou").containsChar (s[i]);
1700
1701 hasSeenConsonant = (hasSeenConsonant || ! isVowel)
1703
1704 if (! (hasSeenConsonant && isVowel))
1705 result += s[i];
1706 }
1707
1708 return result.substring (0, maxLen);
1709}
1710
1711}} // namespace tracktion { inline namespace engine
T clamp(T... args)
void ensureStorageAllocated(int minNumElements)
static bool isWhitespace(char character) noexcept
static Colour fromString(StringRef encodedColourString)
uint32 getARGB() const noexcept
Colour overlaidWith(Colour foregroundColour) const noexcept
const String & toString() const noexcept
void add(String stringToAdd)
int length() const noexcept
bool containsChar(juce_wchar character) const noexcept
size_t copyToUTF8(CharPointer_UTF8::CharType *destBuffer, size_t maxBufferSizeBytes) const noexcept
String replace(StringRef stringToReplace, StringRef stringToInsertInstead, bool ignoreCase=false) const
String substring(int startIndex, int endIndex) const
bool isNotEmpty() const noexcept
#define TRANS(stringLiteral)
#define jassertfalse
typedef int
T max(T... args)
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
int roundToInt(const FloatType value) noexcept
juce::String getName(LaunchQType t)
Retuns the name of a LaunchQType for display purposes.
typedef size_t
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.