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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ExternalControllerManager.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
14bool ColourArea::contains (ClipSlot& clipSlot) const
15{
16 auto t1 = firstTrack.getIndexInEditTrackList();
17 auto t2 = lastTrack.getIndexInEditTrackList();
18
19 auto t = clipSlot.track.getIndexInEditTrackList();
20 auto s = clipSlot.getIndex();
21
22 if (t >= t1 && t <= t2 && s >= firstScene && s <= lastScene)
23 return true;
24
25 return false;
26}
27
28bool ColourArea::isLeft (ClipSlot& clipSlot) const
29{
30 auto t1 = firstTrack.getIndexInEditTrackList();
31
32 auto t = clipSlot.track.getIndexInEditTrackList();
33 auto s = clipSlot.getIndex();
34
35 if (t == t1 && s >= firstScene && s <= lastScene)
36 return true;
37
38 return false;
39}
40
41bool ColourArea::isRight (ClipSlot& clipSlot) const
42{
43 auto t2 = lastTrack.getIndexInEditTrackList();
44
45 auto t = clipSlot.track.getIndexInEditTrackList();
46 auto s = clipSlot.getIndex();
47
48 if (t == t2 && s >= firstScene && s <= lastScene)
49 return true;
50
51 return false;
52}
53
54bool ColourArea::isTop (ClipSlot& clipSlot) const
55{
56 auto t1 = firstTrack.getIndexInEditTrackList();
57 auto t2 = lastTrack.getIndexInEditTrackList();
58
59 auto t = clipSlot.track.getIndexInEditTrackList();
60 auto s = clipSlot.getIndex();
61
62 if (t >= t1 && t <= t2 && s == firstScene)
63 return true;
64
65 return false;
66}
67
68bool ColourArea::isBottom (ClipSlot& clipSlot) const
69{
70 auto t1 = firstTrack.getIndexInEditTrackList();
71 auto t2 = lastTrack.getIndexInEditTrackList();
72
73 auto t = clipSlot.track.getIndexInEditTrackList();
74 auto s = clipSlot.getIndex();
75
76 if (t >= t1 && t <= t2 && s == lastScene)
77 return true;
78
79 return false;
80}
81
83 private juce::Timer,
84 private juce::AsyncUpdater,
86{
87 EditTreeWatcher (ExternalControllerManager& o, Edit& e) : owner (o), edit (e)
88 {
89 edit.state.addListener (this);
91 startTimer (40);
92 }
93
94 ~EditTreeWatcher() override
95 {
96 edit.state.removeListener (this);
98 }
99
100 void slotUpdated (int, int) override
101 {
102 updatePads.set (true);
103 }
104
105private:
106 //==============================================================================
108 Edit& edit;
109
111 juce::Atomic<int> updateAux;
112 juce::Atomic<bool> updatePads;
113
114 void valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& i) override
115 {
116 if (v.hasType (IDs::MARKERCLIP))
117 {
119 }
120 else if (Clip::isClipState (v))
121 {
122 if (i == IDs::colour)
123 updatePads.set (true);
124 }
125 else if (v.hasType (IDs::PLUGIN))
126 {
127 if (i == IDs::volume || i == IDs::pan)
128 pluginsToUpdate.addIfNotAlreadyThere (v);
129 else if (i == IDs::auxSendSliderPos && v.getProperty (IDs::type) == AuxSendPlugin::xmlTypeName)
130 updateAux.set (1);
131 }
132 else if (v.hasType (IDs::TRACK))
133 {
134 if (i == IDs::name)
135 owner.updateDeviceState();
136 }
137 }
138
139 void valueTreeChildAdded (juce::ValueTree&, juce::ValueTree& c) override
140 {
141 if (c.hasType (IDs::MARKERCLIP))
143 }
144
145 void valueTreeChildRemoved (juce::ValueTree&, juce::ValueTree& c, int) override
146 {
147 if (c.hasType (IDs::MARKERCLIP))
149 }
150
151 void valueTreeChildOrderChanged (juce::ValueTree&, int, int) override {}
152 void valueTreeParentChanged (juce::ValueTree&) override {}
153
154 void timerCallback() override
155 {
156 {
158 plugins.swapWith (pluginsToUpdate);
159
160 for (int i = plugins.size(); --i >= 0;)
161 {
162 auto v = plugins.getUnchecked (i);
163
164 if (auto mv = edit.getMasterVolumePlugin())
165 {
166 if (mv->state == v)
167 {
168 owner.updateVolumePlugin (*mv);
169 continue;
170 }
171 }
172
173 if (auto p = edit.getPluginCache().getPluginFor (v))
174 {
175 if (auto vp = dynamic_cast<VolumeAndPanPlugin*> (p.get()))
176 owner.updateVolumePlugin (*vp);
177 else if (auto vca = dynamic_cast<VCAPlugin*> (p.get()))
178 owner.updateVCAPlugin (*vca);
179 }
180 }
181 }
182
183 if (updateAux.compareAndSetBool (0, 1))
184 owner.auxSendLevelsChanged();
185
186 if (updatePads.compareAndSetBool (false, true))
187 owner.updatePadColours();
188 }
189
190 void handleAsyncUpdate() override
191 {
192 owner.updateMarkers();
193 }
194
196};
197
198//==============================================================================
199ExternalControllerManager::ExternalControllerManager (Engine& e) : engine (e)
200{
201 blinkTimer = std::make_unique<BlinkTimer> (*this);
202 masterLevelsTimer.setCallback ([this]
203 {
204 if (currentEdit == nullptr)
205 return;
206
207 auto ctx = currentEdit->getCurrentPlaybackContext();
208 if (ctx == nullptr)
209 return;
210
211 auto l = ctx->masterLevels.getLevelCache();
212 masterLevelsChanged (dbToGain (l.first), dbToGain (l.second));
213 });
214}
215
216ExternalControllerManager::~ExternalControllerManager()
217{
218 jassert (currentEdit == nullptr); // should be cleared by this point.
219
220 blinkTimer.reset();
221 setCurrentEdit (nullptr, nullptr);
222}
223
224void ExternalControllerManager::initialise()
225{
227 TRACKTION_LOG ("Creating Default Controllers...");
228
229 #if TRACKTION_ENABLE_CONTROL_SURFACES
230 auto controllers = engine.getEngineBehaviour().getDesiredControlSurfaces();
231
232 if (controllers.mackieMCU)
233 {
234 auto mcu = new MackieMCU (*this);
235 addNewController (mcu);
236
237 for (int i = 0; i < getXTCount (mcu->deviceDescription); ++i)
238 addNewController (new MackieXT (*this, *mcu, i));
239
240 refreshXTOrder();
241 }
242
243 #if TRACKTION_ENABLE_CONTROL_SURFACE_MACKIEC4
244 if (controllers.mackieC4) addNewController (new MackieC4 (*this));
245 #endif
246
247 if (controllers.iconProG2)
248 {
249 auto icon = new IconProG2 (*this);
250 addNewController (icon);
251 for (int i = 0; i < getXTCount (icon->deviceDescription); ++i)
252 addNewController (new MackieXT (*this, *icon, i));
253 }
254
255 if (controllers.tranzport) addNewController (new TranzportControlSurface (*this));
256 if (controllers.alphaTrack) addNewController (new AlphaTrackControlSurface (*this));
257 if (controllers.remoteSL) addNewController (new NovationRemoteSl (*this));
258
259 #if TRACKTION_ENABLE_AUTOMAP
260 if (controllers.automap)
261 {
262 automap = new NovationAutomap (*this);
263 addNewController (automap);
264 }
265 #endif
266
267 #if JUCE_DEBUG
268 if (controllers.remoteSLCompact) addNewController (new RemoteSLCompact (*this));
269 #endif
270
271 // load the custom midi controllers
272 TRACKTION_LOG ("Loading Custom Controllers...");
273
274 for (auto mc : CustomControlSurface::getCustomSurfaces (*this))
275 addNewController (mc);
276 #endif
277
279}
280
281void ExternalControllerManager::shutdown()
282{
284 jassert (currentEdit == nullptr && currentSelectionManager == nullptr);
285 setCurrentEdit (nullptr, nullptr);
286 devices.clear();
287 automap = nullptr;
288 currentEdit = nullptr;
289}
290
291ExternalController* ExternalControllerManager::addNewController (ControlSurface* cs)
292{
293 return devices.add (new ExternalController (engine, cs));
294}
295
296#define FOR_EACH_DEVICE(x) \
297 for (auto device : devices) { device->x; }
298
299#define FOR_EACH_ACTIVE_DEVICE(x) \
300 for (auto device : devices) { if (device->isEnabled()) device->x; }
301
302void ExternalControllerManager::setCurrentEdit (Edit* newEdit, SelectionManager* newSM)
303{
304 if (newEdit != currentEdit)
305 {
306 editTreeWatcher = nullptr;
307
308 if (currentEdit != nullptr)
309 currentEdit->getTransport().removeChangeListener (this);
310
311 currentEdit = newEdit;
312
313 if (currentEdit != nullptr)
314 {
315 masterLevelsTimer.startTimer (1000 / 50);
316 currentEdit->getTransport().addChangeListener (this);
317 editTreeWatcher = std::make_unique<EditTreeWatcher> (*this, *currentEdit);
318 }
319 else
320 {
321 masterLevelsTimer.stopTimer();
322 }
323 }
324
325 if (newSM != currentSelectionManager)
326 {
327 if (currentSelectionManager != nullptr)
328 currentSelectionManager->removeChangeListener (this);
329
330 currentSelectionManager = newSM;
331
332 if (currentSelectionManager != nullptr)
333 currentSelectionManager->addChangeListener (this);
334 }
335
336 FOR_EACH_ACTIVE_DEVICE (currentEditChanged (currentEdit));
337 FOR_EACH_ACTIVE_DEVICE (currentSelectionManagerChanged (currentSelectionManager));
338}
339
340bool ExternalControllerManager::isAttachedToEdit (const Edit* ed) const noexcept
341{
342 return currentEdit != nullptr && currentEdit == ed;
343}
344
345bool ExternalControllerManager::isAttachedToEdit (const Edit& e) const noexcept
346{
347 return isAttachedToEdit (&e);
348}
349
350SelectionManager* ExternalControllerManager::getSelectionManager() const noexcept
351{
352 return currentSelectionManager;
353}
354
355void ExternalControllerManager::detachFromEdit (Edit* ed)
356{
357 if (isAttachedToEdit (ed))
358 setCurrentEdit (nullptr, nullptr);
359}
360
361void ExternalControllerManager::detachFromSelectionManager (SelectionManager* sm)
362{
363 if (currentSelectionManager == sm)
364 setCurrentEdit (currentEdit, nullptr);
365}
366
367bool ExternalControllerManager::createCustomController (const juce::String& name, Protocol protocol)
368{
370
371 int outPort = 9000, inPort = 8000;
372 // Find free UDP ports for OSC input and output
373 if (protocol == osc)
374 {
375 for (auto device : devices)
376 {
377 if (device->needsOSCSocket())
378 {
379 outPort = std::max (outPort, device->getOSCOutputPort() + 1);
380 inPort = std::max (inPort, device->getOSCInputPort() + 1);
381 }
382 }
383 }
384
385 if (auto ec = addNewController (new CustomControlSurface (*this, name, protocol)))
386 {
387 if (protocol == osc)
388 {
389 ec->setOSCOutputPort (outPort);
390 ec->setOSCInputPort (inPort);
391 }
392 }
393
395 return true;
396}
397
398ExternalController* ExternalControllerManager::addController (ControlSurface* c)
399{
401
402 int outPort = 9000, inPort = 8000;
403 // Find free UDP ports for OSC input and output
404 if (c->needsOSCSocket)
405 {
406 for (auto device : devices)
407 {
408 if (device->needsOSCSocket())
409 {
410 outPort = std::max (outPort, device->getOSCOutputPort() + 1);
411 inPort = std::max (inPort, device->getOSCInputPort() + 1);
412 }
413 }
414 }
415
416 if (auto ec = addNewController (c))
417 {
418 if (c->needsOSCSocket)
419 {
420 ec->setOSCOutputPort (outPort);
421 ec->setOSCInputPort (inPort);
422 }
424 return ec;
425 }
426 return nullptr;
427}
428
429void ExternalControllerManager::deleteController (ExternalController* c)
430{
432
433 if (devices.contains (c))
434 {
435 #if TRACKTION_ENABLE_AUTOMAP && TRACKTION_ENABLE_CONTROL_SURFACES
436 if (c->controlSurface.get() == automap)
437 automap = nullptr;
438 #endif
439
440 c->deleteController();
441 devices.removeObject (c);
443 }
444}
445
446juce::StringArray ExternalControllerManager::getAllControllerNames()
447{
449
450 for (auto ec : devices)
451 s.add (ec->getName());
452
453 return s;
454}
455
456ExternalController* ExternalControllerManager::getActiveCustomController()
457{
458 for (auto ec : devices)
459 if (ec->isEnabled() && ec->isDeletable())
460 return ec;
461
462 return {};
463}
464
465void ExternalControllerManager::midiInOutDevicesChanged() { FOR_EACH_DEVICE (midiInOutDevicesChanged()); }
466void ExternalControllerManager::updateDeviceState() { FOR_EACH_ACTIVE_DEVICE (updateDeviceState()); }
467void ExternalControllerManager::updateParameters() { FOR_EACH_ACTIVE_DEVICE (updateParameters()); }
468void ExternalControllerManager::updateMarkers() { FOR_EACH_ACTIVE_DEVICE (updateMarkers()); }
469void ExternalControllerManager::updateTrackRecordLights() { FOR_EACH_ACTIVE_DEVICE (updateTrackRecordLights()); }
470void ExternalControllerManager::updatePunchLights() { FOR_EACH_ACTIVE_DEVICE (updatePunchLights()); }
471void ExternalControllerManager::updateScrollLights() { FOR_EACH_ACTIVE_DEVICE (updateScrollLights()); }
472void ExternalControllerManager::updateUndoLights() { FOR_EACH_ACTIVE_DEVICE (updateUndoLights()); }
473void ExternalControllerManager::updatePadColours() { FOR_EACH_ACTIVE_DEVICE (updatePadColours()); }
474
475void ExternalControllerManager::changeListenerCallback (ChangeBroadcaster* source)
476{
478
479 if (auto tc = dynamic_cast<TransportControl*> (source))
480 {
481 playStateChanged (tc->isPlaying());
482 recordStateChanged (tc->isRecording());
483 updatePadColours();
484 }
485 else if (currentSelectionManager != nullptr)
486 {
487 auto& selectionManager = *currentSelectionManager;
488
489 if (selectionManager.getNumObjectsSelected() == 1 && selectionManager.containsType<Plugin>())
490 {
491 FOR_EACH_ACTIVE_DEVICE (selectedPluginChanged());
492 }
493 else if (auto track = selectionManager.getFirstItemOfType<Track>())
494 {
495 int num = mapTrackNumToChannelNum (track->getIndexInEditTrackList());
496
497 for (auto device : devices)
498 if (device->isEnabled())
499 if (device->followsTrackSelection)
500 if (num != -1 && num != device->channelStart)
501 device->changeFaderBank (num - device->channelStart, false);
502 }
503 FOR_EACH_ACTIVE_DEVICE (updateTrackSelectLights());
504 }
505}
506
507void ExternalControllerManager::updateAllDevices()
508{
509 if (! isTimerRunning())
510 {
512
513 if (now - lastUpdate > 250)
514 {
515 lastUpdate = now;
516
517 updateDeviceState();
518 updateParameters();
519 updateMarkers();
520 }
521 else
522 {
523 startTimer (250 - (int) (now - lastUpdate));
524 }
525 }
526}
527
528void ExternalControllerManager::timerCallback()
529{
531 stopTimer();
532
534
535 updateDeviceState();
536 updateParameters();
537 updateMarkers();
538}
539
540//==============================================================================
541ExternalControllerManager::BlinkTimer::BlinkTimer (ExternalControllerManager& e) : ecm (e)
542{
543 startTimer (750);
544}
545
546void ExternalControllerManager::BlinkTimer::timerCallback()
547{
548 isBright = ! isBright;
549 ecm.blinkNow();
550}
551
552juce::Array<ExternalController*> ExternalControllerManager::getActiveDevices() const
553{
555
556 for (auto d : devices)
557 if (d->isEnabled())
558 activeDevices.add (d);
559
560 return activeDevices;
561}
562
563void ExternalControllerManager::blinkNow()
564{
565 updateMuteSoloLights (true);
566}
567
568void ExternalControllerManager::updateMuteSoloLights (bool onlyUpdateFlashingLights)
569{
570 if (currentEdit == nullptr)
571 return;
572
573 const auto activeDevices = getActiveDevices();
574
575 if (activeDevices.isEmpty())
576 return;
577
578 int i = 0;
579 const bool isBright = blinkTimer->isBright;
580 bool anySolo = false;
581
582 currentEdit->visitAllTracksRecursive ([&, this] (Track& t)
583 {
584 if (t.isSolo (false))
585 anySolo = true;
586
587 auto mappedChan = mapTrackNumToChannelNum (i);
588
589 if (mappedChan >= 0)
590 {
591 const auto flags = t.getMuteAndSoloLightState();
592
593 if ((flags & (Track::soloFlashing | Track::muteFlashing)) != 0
594 || ! onlyUpdateFlashingLights)
595 {
596 for (auto d : activeDevices)
597 d->updateSoloAndMute (mappedChan, flags, isBright);
598 }
599 }
600
601 ++i;
602 return true;
603 });
604
605 for (auto d : activeDevices)
606 d->soloCountChanged (blinkTimer->isBright && anySolo);
607}
608
609//==============================================================================
610void ExternalControllerManager::moveFader (int channelNum, float newSliderPos)
611{
612 auto chan = mapTrackNumToChannelNum (channelNum);
613
614 FOR_EACH_ACTIVE_DEVICE (moveFader (chan, newSliderPos));
615}
616
617void ExternalControllerManager::movePanPot (int channelNum, float newPan)
618{
619 auto chan = mapTrackNumToChannelNum (channelNum);
620
621 FOR_EACH_ACTIVE_DEVICE (movePanPot (chan, newPan));
622}
623
624void ExternalControllerManager::updateVolumePlugin (VolumeAndPanPlugin& vp)
625{
626 if (auto t = dynamic_cast<AudioTrack*> (vp.getOwnerTrack()))
627 {
628 if (t->getVolumePlugin() == &vp)
629 {
630 auto chan = mapTrackNumToChannelNum (t->getIndexInEditTrackList());
631
632 for (auto c : devices)
633 {
634 if (c->isEnabled())
635 {
636 c->moveFader (chan, vp.getSliderPos());
637 c->movePanPot (chan, vp.getPan());
638 }
639 }
640 }
641 }
642 else
643 {
644 if (vp.edit.getMasterVolumePlugin().get() == &vp)
645 {
646 FOR_EACH_ACTIVE_DEVICE (moveMasterFader (vp.getSliderPos()));
647 FOR_EACH_ACTIVE_DEVICE (moveMasterPanPot (vp.getPan()));
648 }
649 }
650}
651
652void ExternalControllerManager::updateVCAPlugin (VCAPlugin& vca)
653{
655
656 if (auto t = dynamic_cast<FolderTrack*> (vca.getOwnerTrack()))
657 {
658 if (t->getVCAPlugin() == &vca)
659 {
660 auto chan = mapTrackNumToChannelNum (t->getIndexInEditTrackList());
661
662 for (auto c : devices)
663 {
664 if (c->isEnabled())
665 {
666 c->moveFader (chan, vca.getSliderPos());
667 c->movePanPot (chan, 0.0f);
668 }
669 }
670 }
671 }
672}
673
674void ExternalControllerManager::moveMasterFader (float newPos)
675{
677 FOR_EACH_ACTIVE_DEVICE (moveMasterFader (newPos));
678}
679
680void ExternalControllerManager::moveMasterPanPot (float newPan)
681{
683 FOR_EACH_ACTIVE_DEVICE (moveMasterPanPot (newPan));
684}
685
686void ExternalControllerManager::soloCountChanged (bool anySoloTracks)
687{
689 FOR_EACH_ACTIVE_DEVICE (soloCountChanged (anySoloTracks));
690}
691
692void ExternalControllerManager::playStateChanged (bool isPlaying)
693{
695 FOR_EACH_ACTIVE_DEVICE (playStateChanged (isPlaying));
696}
697
698void ExternalControllerManager::recordStateChanged (bool isRecording)
699{
701 FOR_EACH_ACTIVE_DEVICE (recordStateChanged (isRecording));
702}
703
704void ExternalControllerManager::automationModeChanged (bool isReading, bool isWriting)
705{
707 FOR_EACH_ACTIVE_DEVICE (automationModeChanged (isReading, isWriting));
708}
709
710void ExternalControllerManager::channelLevelChanged (int channel, float l, float r)
711{
713 // This is an optimisation that avoids calling mapTrackNumToChannelNum if there are no enabled/active devices
714 std::optional<int> channelNum;
715
716 if (currentEdit == nullptr)
717 return;
718
719 auto getChannelNum = [&]
720 {
721 if (! channelNum)
722 channelNum = mapTrackNumToChannelNum (channel);
723
724 return *channelNum;
725 };
726
727 FOR_EACH_ACTIVE_DEVICE (channelLevelChanged (getChannelNum(), l, r));
728}
729
730void ExternalControllerManager::masterLevelsChanged (float leftLevel, float rightLevel)
731{
733 FOR_EACH_ACTIVE_DEVICE (masterLevelsChanged (leftLevel, rightLevel));
734}
735
736void ExternalControllerManager::timecodeChanged (int barsOrHours, int beatsOrMinutes, int ticksOrSeconds,
737 int millisecs, bool isBarsBeats, bool isFrames)
738{
740 FOR_EACH_ACTIVE_DEVICE (timecodeChanged (barsOrHours, beatsOrMinutes, ticksOrSeconds, millisecs, isBarsBeats, isFrames));
741}
742
743void ExternalControllerManager::snapChanged (bool isOn)
744{
746 FOR_EACH_ACTIVE_DEVICE (snapChanged (isOn));
747}
748
749void ExternalControllerManager::loopChanged (bool isOn)
750{
752 FOR_EACH_ACTIVE_DEVICE (loopChanged (isOn));
753}
754
755void ExternalControllerManager::clickChanged (bool isOn)
756{
758 FOR_EACH_ACTIVE_DEVICE (clickChanged (isOn));
759}
760
761void ExternalControllerManager::editPositionChanged (Edit* ed, TimePosition newCursorPosition)
762{
763 if (ed != nullptr)
764 {
766 juce::String parts[4];
767 ed->getTimecodeFormat().getPartStrings (TimecodeDuration::fromSecondsOnly (toDuration (newCursorPosition)),
768 ed->tempoSequence,
769 false, parts);
770
771 if (ed->getTimecodeFormat().isBarsBeats())
772 {
773 timecodeChanged (parts[2].getIntValue(),
774 parts[1].getIntValue(),
775 parts[0].getIntValue(),
776 0,
777 ed->getTimecodeFormat().isBarsBeats(),
778 ed->getTimecodeFormat().isSMPTE());
779 }
780 else
781 {
782 timecodeChanged (parts[3].getIntValue(),
783 parts[2].getIntValue(),
784 parts[1].getIntValue(),
785 parts[0].getIntValue(),
786 ed->getTimecodeFormat().isBarsBeats(),
787 ed->getTimecodeFormat().isSMPTE());
788 }
789 }
790}
791
792
793void ExternalControllerManager::auxSendLevelsChanged()
794{
796 FOR_EACH_ACTIVE_DEVICE (auxSendLevelsChanged());
797}
798
799//==============================================================================
800void ExternalControllerManager::userMovedFader (int channelNum, float newSliderPos, bool delta)
801{
803 auto track = getChannelTrack (channelNum);
804
805 if (auto at = dynamic_cast<AudioTrack*> (track))
806 {
807 if (auto vp = at->getVolumePlugin())
808 vp->setSliderPos (delta ? vp->getSliderPos() + newSliderPos : newSliderPos);
809 else
810 moveFader (mapTrackNumToChannelNum (channelNum), decibelsToVolumeFaderPosition (0.0f));
811 }
812
813 if (auto ft = dynamic_cast<FolderTrack*> (track))
814 {
815 if (auto vca = ft->getVCAPlugin())
816 vca->setSliderPos (delta ? vca->getSliderPos() + delta : newSliderPos);
817 else if (auto vp = ft->getVolumePlugin())
818 vp->setSliderPos (delta ? vp->getSliderPos() + delta : newSliderPos);
819 else
820 moveFader (mapTrackNumToChannelNum (channelNum), decibelsToVolumeFaderPosition (0.0f));
821 }
822}
823
824void ExternalControllerManager::userMovedMasterFader (Edit* ed, float newLevel, bool delta)
825{
826 if (ed != nullptr)
827 {
828 if (delta)
829 ed->setMasterVolumeSliderPos (ed->getMasterSliderPosParameter()->getCurrentValue() + newLevel);
830 else
831 ed->setMasterVolumeSliderPos (newLevel);
832 }
833}
834
835void ExternalControllerManager::userMovedMasterPanPot (Edit* ed, float newPos, bool delta)
836{
837 if (ed != nullptr)
838 ed->setMasterPanPos (delta ? ed->getMasterPanParameter()->getCurrentValue() + newPos : newPos);
839}
840
841void ExternalControllerManager::userMovedPanPot (int channelNum, float newPan, bool delta)
842{
843 auto track = getChannelTrack (channelNum);
844
845 if (auto t = dynamic_cast<AudioTrack*> (track))
846 {
847 if (auto vp = t->getVolumePlugin())
848 vp->setPan (delta ? vp->getPan() + newPan : newPan);
849 }
850 else if (auto ft = dynamic_cast<FolderTrack*> (track))
851 {
852 if (auto vp = ft->getVolumePlugin())
853 vp->setPan (delta ? vp->getPan() + newPan : newPan);
854 }
855}
856
857void ExternalControllerManager::userMovedAux (int channelNum, int auxNum, AuxPosition ap, float newPosition, bool delta)
858{
859 if (auto t = dynamic_cast<AudioTrack*> (getChannelTrack (channelNum)))
860 if (auto aux = t->getAuxSendPlugin (auxNum, ap))
861 aux->setGainDb (volumeFaderPositionToDB (delta ? decibelsToVolumeFaderPosition (aux->getGainDb()) + newPosition : newPosition));
862}
863
864void ExternalControllerManager::userPressedAux (int channelNum, int auxNum)
865{
866 if (auto t = dynamic_cast<AudioTrack*> (getChannelTrack (channelNum)))
867 if (auto aux = t->getAuxSendPlugin (auxNum))
868 aux->setMute (! aux->isMute());
869}
870
871void ExternalControllerManager::userLaunchedClip (int channelNum, int sceneNum)
872{
873 if (launchClip && currentEdit)
874 if (auto t = getChannelTrack (channelNum))
875 launchClip (*currentEdit, *t, sceneNum);
876}
877
878void ExternalControllerManager::userStoppedClip (int channelNum)
879{
880 if (stopClip && currentEdit)
881 {
882 if (channelNum >= 0)
883 {
884 auto t = getChannelTrack (channelNum);
885 stopClip (*currentEdit, t);
886 }
887 else
888 {
889 stopClip (*currentEdit, nullptr);
890 }
891 }
892}
893
894void ExternalControllerManager::userLaunchedScene (int sceneNum)
895{
896 if (launchScene && currentEdit)
897 launchScene (*currentEdit, sceneNum);
898}
899
900void ExternalControllerManager::userMovedQuickParam (float newLevel)
901{
902 if (currentSelectionManager != nullptr)
903 if (auto f = currentSelectionManager->getFirstItemOfType<Plugin>())
904 if (auto param = f->getQuickControlParameter())
905 param->midiControllerMoved (newLevel);
906}
907
908void ExternalControllerManager::userPressedSolo (int channelNum)
909{
910 if (auto t = getChannelTrack (channelNum))
911 t->setSolo (! t->isSolo (false));
912}
913
914void ExternalControllerManager::userPressedSoloIsolate (int channelNum)
915{
916 if (auto t = getChannelTrack (channelNum))
917 t->setSoloIsolate (! t->isSoloIsolate (false));
918}
919
920void ExternalControllerManager::userPressedMute (int channelNum, bool muteVolumeControl)
921{
922 if (auto t = getChannelTrack (channelNum))
923 {
924 if (muteVolumeControl)
925 {
926 if (auto at = dynamic_cast<AudioTrack*> (t))
927 if (auto vp = at->getVolumePlugin())
928 vp->muteOrUnmute();
929 }
930 else
931 {
932 t->setMute (! t->isMuted (false));
933 }
934 }
935}
936
937void ExternalControllerManager::userSelectedTrack (int channelNum)
938{
939 if (auto t = getChannelTrack (channelNum))
940 {
941 if (currentSelectionManager != nullptr)
942 {
943 if (currentSelectionManager->isSelected (t))
944 currentSelectionManager->deselect (t);
945 else
946 currentSelectionManager->addToSelection (t);
947 }
948 }
949}
950
951void ExternalControllerManager::userSelectedOneTrack (int channelNum)
952{
953 if (auto t = getChannelTrack (channelNum))
954 {
955 if (currentSelectionManager != nullptr)
956 currentSelectionManager->selectOnly (t);
957 }
958}
959
960
961void ExternalControllerManager::userSelectedClipInTrack (int channelNum)
962{
963 if (currentSelectionManager != nullptr)
964 if (auto t = getChannelTrack (channelNum))
965 if (t->getNumTrackItems() > 0)
966 if (auto ti = t->getTrackItem (0))
967 currentSelectionManager->selectOnly (ti);
968}
969
970void ExternalControllerManager::userSelectedPluginInTrack (int channelNum)
971{
972 if (currentSelectionManager != nullptr)
973 if (auto t = getChannelTrack (channelNum))
974 if (auto f = t->pluginList.getPlugins().getFirst())
975 currentSelectionManager->selectOnly (f.get());
976}
977
978
979int ExternalControllerManager::getNumChannelTracks() const
980{
981 int trackNum = 0;
982
983 if (currentEdit != nullptr && isVisibleOnControlSurface)
984 {
985 currentEdit->visitAllTracksRecursive ([&] (Track& t)
986 {
988 ++trackNum;
989
990 return true;
991 });
992 }
993
994 return trackNum;
995}
996
997Track* ExternalControllerManager::getChannelTrack (int index) const
998{
999 Track* result = nullptr;
1000
1001 if (currentEdit != nullptr && isVisibleOnControlSurface)
1002 {
1003 int i = 0, trackNum = 0;
1004
1005 currentEdit->visitAllTracksRecursive ([&] (Track& t)
1006 {
1008 {
1009 if (trackNum == index)
1010 {
1011 result = &t;
1012 return false;
1013 }
1014
1015 ++trackNum;
1016 }
1017
1018 ++i;
1019 return true;
1020 });
1021 }
1022
1023 return result;
1024}
1025
1026int ExternalControllerManager::mapTrackNumToChannelNum (int index) const
1027{
1028 if (currentEdit == nullptr || index < 0)
1029 return -1;
1030
1032 return mapEditTrackNumToControlSurfaceChannelNum (*currentEdit, index);
1033
1035 return -1;
1036
1037 int result = -1, i = 0, trackNum = 0;
1038
1039 currentEdit->visitAllTracksRecursive ([&] (Track& t)
1040 {
1042 {
1043 if (i == index)
1044 {
1045 result = trackNum;
1046 return false;
1047 }
1048
1049 ++trackNum;
1050 }
1051
1052 ++i;
1053 return true;
1054 });
1055
1056 return result;
1057}
1058
1059bool ExternalControllerManager::shouldTrackBeColoured (int channelNum)
1060{
1061 if (! devices.isEmpty())
1062 {
1063 auto cn = mapTrackNumToChannelNum (channelNum);
1064
1065 for (auto& d : devices)
1066 if (d->shouldTrackBeColoured (cn))
1067 return true;
1068 }
1069
1070 return false;
1071}
1072
1073std::vector<ColourArea> ExternalControllerManager::ExternalControllerManager::getColouredArea (const Edit& e)
1074{
1076
1077 for (auto device : devices)
1078 if (device->isEnabled())
1079 if (auto area = device->getColouredArea (e); area.has_value())
1080 areas.push_back (*area);
1081
1082 return areas;
1083}
1084
1085juce::Colour ExternalControllerManager::getTrackColour (int channelNum)
1086{
1087 juce::Colour c;
1088
1089 if (devices.isEmpty())
1090 return c;
1091
1092 auto activeDevices = getActiveDevices();
1093
1094 if (activeDevices.isEmpty())
1095 return {};
1096 {
1097 auto cn = mapTrackNumToChannelNum (channelNum);
1098
1099 for (auto d : activeDevices)
1100 d->getTrackColour (cn, c);
1101 }
1102
1103 return c;
1104}
1105
1106void ExternalControllerManager::repaintTrack (int channelNum)
1107{
1108 if (auto t = getChannelTrack (channelNum))
1109 t->changed();
1110}
1111
1112void ExternalControllerManager::repaintSlots (int channelNum)
1113{
1114 if (auto t = getChannelTrack (channelNum))
1115 t->changed();
1116}
1117
1118bool ExternalControllerManager::shouldPluginBeColoured (Plugin* plugin)
1119{
1120 for (auto d : devices)
1121 if (d->isEnabled())
1122 if (d->shouldPluginBeColoured (plugin))
1123 return true;
1124
1125 return false;
1126}
1127
1128juce::Colour ExternalControllerManager::getPluginColour (Plugin* plugin)
1129{
1130 juce::Colour c;
1131 FOR_EACH_ACTIVE_DEVICE (getPluginColour (plugin, c));
1132 return c;
1133}
1134
1135void ExternalControllerManager::repaintPlugin (Plugin& plugin)
1136{
1137 if (auto c = PluginComponent::getComponentFor (plugin))
1138 c->updateColour();
1139}
1140
1141int ExternalControllerManager::getXTCount (const juce::String& desc)
1142{
1143 if (desc == "Mackie Control Universal")
1144 return engine.getPropertyStorage().getProperty (SettingID::xtCount);
1145
1146 return engine.getPropertyStorage().getPropertyItem (SettingID::xtCount, desc);
1147}
1148
1149void ExternalControllerManager::setXTCount (const juce::String& desc, int after)
1150{
1152 juce::ignoreUnused (desc, after);
1153
1154 #if TRACKTION_ENABLE_CONTROL_SURFACES
1155 for (int devIdx = 0; devIdx < devices.size(); devIdx++)
1156 {
1157 auto device = devices[devIdx];
1158 if (auto mcu = device->getControlSurfaceIfType<MackieMCU>(); mcu != nullptr && mcu->deviceDescription == desc)
1159 {
1160 int before = getXTCount (desc);
1161 int diff = after - before;
1162
1163 if (diff > 0)
1164 {
1165 for (int i = 0; i < diff; ++i)
1166 devices.insert (devIdx + before + i + 1, new ExternalController (engine, new MackieXT (*this, *mcu, before + i)));
1167 }
1168 else if (diff < 0)
1169 {
1170 for (int i = 0; i < std::abs (diff); ++i)
1171 devices.remove (devIdx + before - i);
1172 }
1173
1174 if (desc == "Mackie Control Universal")
1175 engine.getPropertyStorage().setProperty (SettingID::xtCount, after);
1176 else
1177 engine.getPropertyStorage().setPropertyItem (SettingID::xtCount, desc, after);
1178 }
1179 refreshXTOrder();
1181 }
1182 #endif
1183}
1184
1185void ExternalControllerManager::refreshXTOrder()
1186{
1188
1189 #if TRACKTION_ENABLE_CONTROL_SURFACES
1190 for (auto device : devices)
1191 {
1192 if (auto mcu = device->getControlSurfaceIfType<MackieMCU>())
1193 {
1194 MackieXT* xt[MackieMCU::maxNumSurfaces - 1] = {};
1195
1196 int offset = devices.indexOf (device);
1197 for (int i = 0; i < getXTCount (mcu->deviceDescription); ++i)
1198 xt[i] = devices[offset + 1 + i] ? devices[offset + 1 + i]->getControlSurfaceIfType<MackieXT>() : nullptr;
1199
1200 juce::StringArray indices;
1201 if (mcu->deviceDescription == "Mackie Control Universal")
1202 indices.addTokens (engine.getPropertyStorage().getProperty (SettingID::xtIndices, "0 1 2 3").toString(), false);
1203 else
1204 indices.addTokens (engine.getPropertyStorage().getPropertyItem (SettingID::xtIndices, mcu->deviceDescription, "0 1 2 3").toString(), false);
1205
1206 for (int i = indices.size(); --i >= 0;)
1207 if (indices[i].getIntValue() > getXTCount (mcu->deviceDescription))
1208 indices.remove(i);
1209
1210 mcu->setDeviceIndex (indices.indexOf ("0"));
1211
1212 if (xt[0] != nullptr) xt[0]->setDeviceIndex (indices.indexOf ("1"));
1213 if (xt[1] != nullptr) xt[1]->setDeviceIndex (indices.indexOf ("2"));
1214 if (xt[2] != nullptr) xt[2]->setDeviceIndex (indices.indexOf ("3"));
1215 }
1216 }
1217 #endif
1218}
1219
1220}} // namespace tracktion { inline namespace engine
void swapWith(OtherArrayType &otherArray) noexcept
ElementType getUnchecked(int index) const
bool isEmpty() const noexcept
int size() const noexcept
bool addIfNotAlreadyThere(ParameterType newElement)
ChangeBroadcaster() noexcept
void addChangeListener(ChangeListener *listener)
void removeChangeListener(ChangeListener *listener)
int indexOf(StringRef stringToLookFor, bool ignoreCase=false, int startIndex=0) const
int size() const noexcept
void remove(int index)
int addTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
static uint32 getMillisecondCounter() noexcept
void stopTimer() noexcept
bool isTimerRunning() const noexcept
void startTimer(int intervalInMilliseconds) noexcept
void addListener(Listener *listener)
void removeListener(Listener *listener)
static bool isClipState(const juce::ValueTree &)
Checks whether a ValueTree is some kind of clip state.
The Tracktion Edit class!
VolumeAndPanPlugin::Ptr getMasterVolumePlugin() const
Returns the master VolumeAndPanPlugin.
juce::ValueTree state
The ValueTree of the Edit state.
TransportControl & getTransport() const noexcept
Returns the TransportControl which is used to stop/stop/position playback and recording.
SceneList & getSceneList()
Returns a list of Scenes in the Edit.
PluginCache & getPluginCache() noexcept
Returns the PluginCache which manages all active Plugin[s] for this Edit.
void visitAllTracksRecursive(std::function< bool(Track &)>) const
Visits all tracks in the Edit with the given function.
virtual ControlSurfaces getDesiredControlSurfaces()
Return the control surfaces you want enabled in the engine.
PropertyStorage & getPropertyStorage() const
Returns the PropertyStorage user settings customisable XML file.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
Keeps a list of external controllers and keeps them connected to the right MIDI in/out devices.
std::function< void(Edit &, int)> launchScene
Launch scene by index.
std::function< void(Edit &, Track &, int)> launchClip
Launch clip by track and index.
std::function< int(Edit &, int)> mapEditTrackNumToControlSurfaceChannelNum
Optional callback that can be set to map an Edit track number to a control surface channel number.
std::function< void(Edit &, Track *)> stopClip
Stop all clips on track.
std::function< bool(const Track &)> isVisibleOnControlSurface
Callback that can be set to determine if a track is visible on a controller or not.
SceneWatcher sceneWatcher
A SceneWatcher you can listen to in order to be notified of changes to Slots.
void removeListener(Listener *)
Removes a previously added Listener.
void addListener(Listener *)
Adds a Listener.
int getIndexInEditTrackList() const
Returns the index of this track in a flat list of tracks contained in an Edit.
@ muteFlashing
Track is implicitly muted.
@ soloFlashing
Track is implicitly soloed.
T is_pointer_v
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
T max(T... args)
void ignoreUnused(Types &&...) noexcept
juce::String getName(LaunchQType t)
Retuns the name of a LaunchQType for display purposes.
bool isRecording(EditPlaybackContext &epc)
Returns true if any inputs are currently recording.
T push_back(T... args)
T reset(T... args)
void set(Type newValue) noexcept
bool compareAndSetBool(Type newValue, Type valueToCompare) noexcept
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.