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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_EditInputDevices.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
14EditInputDevices::EditInputDevices (Edit& e, const juce::ValueTree& v)
15 : edit (e), state (v), editState (e.state)
16{
17 editState.addListener (this);
18 callBlocking ([this] { edit.engine.getDeviceManager().addChangeListener (this); });
19}
20
21EditInputDevices::~EditInputDevices()
22{
23 cancelPendingUpdate();
24 edit.engine.getDeviceManager().removeChangeListener (this);
25 removeNonExistantInputDeviceStates();
26}
27
28int EditInputDevices::getMaxNumInputs() const
29{
30 return 4;
31}
32
33static bool isForDevice (const juce::ValueTree& v, const InputDevice& d, bool fallbackToNameCheck)
34{
35 auto typeProp = v.getPropertyPointer (IDs::type);
36
37 if (d.getDeviceType() == InputDevice::DeviceType::trackWaveDevice)
38 return typeProp != nullptr && *typeProp == "audio" && v[IDs::sourceTrack] == d.getName();
39
40 if (d.getDeviceType() == InputDevice::DeviceType::trackMidiDevice)
41 return typeProp != nullptr && *typeProp == "MIDI" && v[IDs::sourceTrack] == d.getName();
42
43 if (v[IDs::deviceID] == d.getDeviceID())
44 return true;
45
46 return fallbackToNameCheck && v[IDs::name] == d.getName();
47}
48
49static juce::ValueTree findDeviceState (const juce::ValueTree& parent, const InputDevice& d)
50{
51 for (const auto& v : parent)
52 if (isForDevice (v, d, false))
53 return v;
54
55 for (const auto& v : parent)
56 if (isForDevice (v, d, true))
57 return v;
58
59 return {};
60}
61
62static bool isTrackInputDeviceMIDI (const juce::ValueTree& v)
63{
64 return v[IDs::type].toString().trim() == "MIDI";
65}
66
67bool EditInputDevices::isInputDeviceAssigned (const InputDevice& d)
68{
69 return findDeviceState (state, d).isValid();
70}
71
72void EditInputDevices::clearAllInputs (AudioTrack& at, juce::UndoManager* um)
73{
74 for (auto idi : getDevicesForTargetTrack (at))
75 [[ maybe_unused]] auto res = idi->removeTarget (at.itemID, um);
76}
77
78static bool isInstanceRecording (InputDeviceInstance* idi)
79{
80 if (idi->isRecording())
81 {
82 idi->edit.engine.getUIBehaviour().showWarningMessage (TRANS("Can't change tracks whilst recording is active"));
83 return false;
84 }
85
86 return true;
87}
88
89void EditInputDevices::clearInputsOfDevice (AudioTrack& at, const InputDevice& d, juce::UndoManager* um)
90{
91 for (auto idi : getDevicesForTargetTrack (at))
92 if (&idi->owner == &d)
93 if (! isInstanceRecording (idi))
94 [[ maybe_unused ]] auto res = idi->removeTarget (at.itemID, um);
95}
96
97InputDeviceInstance* EditInputDevices::getInputInstance (const AudioTrack& at, int index) const
98{
99 for (auto idi : getDevicesForTargetTrack (at))
100 if (isOnTargetTrack (*idi, at, index))
101 return idi;
102
103 return {};
104}
105
106juce::Array<InputDeviceInstance*> EditInputDevices::getDevicesForTargetTrack (const AudioTrack& at) const
107{
109
110 for (auto idi : edit.getAllInputDevices())
111 if (idi->getTargets().contains (at.itemID))
112 devices.add (idi);
113
114 return devices;
115}
116
117juce::ValueTree EditInputDevices::getInstanceStateForInputDevice (const InputDevice& d)
118{
119 if (auto v = findDeviceState (state, d); v.isValid())
120 {
121 // Refresh the ID to update from legacy edits
122 v.setProperty (IDs::deviceID, d.getDeviceID(), nullptr);
123 v.setProperty (IDs::name, d.getName(), nullptr);
124 return v;
125 }
126
127 juce::ValueTree v (IDs::INPUTDEVICE);
128
129 if (d.getDeviceType() == InputDevice::DeviceType::trackWaveDevice)
130 {
131 v.setProperty (IDs::sourceTrack, EditItemID::fromString (d.getName()), nullptr);
132 v.setProperty (IDs::type, "audio", nullptr);
133 }
134 else if (d.getDeviceType() == InputDevice::DeviceType::trackMidiDevice)
135 {
136 v.setProperty (IDs::sourceTrack, EditItemID::fromString (d.getName()), nullptr);
137 v.setProperty (IDs::type, "MIDI", nullptr);
138 }
139 else
140 {
141 v.setProperty (IDs::deviceID, d.getDeviceID(), nullptr);
142 v.setProperty (IDs::name, d.getName(), nullptr);
143 }
144
145 state.addChild (v, -1, nullptr);
146 return v;
147}
148
149//==============================================================================
150void EditInputDevices::removeNonExistantInputDeviceStates()
151{
152 auto& dm = edit.engine.getDeviceManager();
154
155 for (auto& d : dm.midiInputs)
156 devices.add (d.get());
157
158 devices.addArray (dm.waveInputs);
159
160 if (! edit.isLoading())
161 {
162 for (auto* at : getAudioTracks (edit))
163 {
164 if (auto* wid = &at->getWaveInputDevice())
165 if (wid->isEnabled())
166 devices.add (wid);
167
168 if (auto* mid = &at->getMidiInputDevice())
169 if (mid->isEnabled())
170 devices.add (mid);
171 }
172 }
173
174 auto isDevicePresent = [devices] (const juce::ValueTree& v)
175 {
176 for (auto* d : devices)
177 if (isForDevice (v, *d, true))
178 return true;
179
180 return false;
181 };
182
183 for (int i = state.getNumChildren(); --i >= 0;)
184 if (! isDevicePresent (state.getChild(i)))
185 state.removeChild (i, nullptr);
186}
187
188void EditInputDevices::addTrackDeviceInstanceToContext (const juce::ValueTree& v) const
189{
190 if (auto* id = getTrackDeviceForState (v))
191 {
192 if (auto* epc = edit.getCurrentPlaybackContext())
193 {
194 if (isTrackInputDeviceMIDI (v))
195 epc->addMidiInputDeviceInstance (*id);
196 else
197 epc->addWaveInputDeviceInstance (*id);
198 }
199 }
200}
201
202void EditInputDevices::removeTrackDeviceInstanceToContext (const juce::ValueTree& v) const
203{
204 if (auto* id = getTrackDeviceForState (v))
205 if (auto* epc = edit.getCurrentPlaybackContext())
206 epc->removeInstanceForDevice (*id);
207}
208
209InputDevice* EditInputDevices::getTrackDeviceForState (const juce::ValueTree& v) const
210{
211 auto trackID = EditItemID::fromProperty (v, IDs::sourceTrack);
212
213 if (trackID.isValid())
214 {
215 if (auto at = findAudioTrackForID (edit, trackID))
216 {
217 if (isTrackInputDeviceMIDI (v))
218 return &at->getMidiInputDevice();
219
220 return &at->getWaveInputDevice();
221 }
222 }
223
224 return {};
225}
226
227void EditInputDevices::changeListenerCallback (juce::ChangeBroadcaster*)
228{
229 removeNonExistantInputDeviceStates();
230}
231
232void EditInputDevices::handleAsyncUpdate()
233{
234 removeNonExistantInputDeviceStates();
235}
236
237static bool isTrackDevice (const juce::ValueTree& v)
238{
239 jassert (v.hasType (IDs::INPUTDEVICE));
240 return v.hasProperty (IDs::sourceTrack);
241}
242
243void EditInputDevices::valueTreeChildAdded (juce::ValueTree& p, juce::ValueTree& c)
244{
245 if (p == state)
246 if (c.hasType (IDs::INPUTDEVICE) && isTrackDevice (c))
247 addTrackDeviceInstanceToContext (c);
248}
249
250void EditInputDevices::valueTreeChildRemoved (juce::ValueTree& p, juce::ValueTree& c, int)
251{
252 if (p == state)
253 if (c.hasType (IDs::INPUTDEVICE) && isTrackDevice (c))
254 removeTrackDeviceInstanceToContext (c);
255
256 if (TrackList::isTrack (c))
257 triggerAsyncUpdate();
258}
259
260}} // namespace tracktion { inline namespace engine
void addArray(const Type *elementsToAdd, int numElementsToAdd)
void add(const ElementType &newElement)
bool isValid() const noexcept
const String & toString() const noexcept
String trim() const
bool isValid() const noexcept
#define TRANS(stringLiteral)
#define jassert(expression)
juce::Array< AudioTrack * > getAudioTracks(const Edit &edit)
Returns all the AudioTracks in an Edit.
bool isOnTargetTrack(InputDeviceInstance &instance, const Track &track, int index)
Returns true if this instance is assigned to the given Track at the given index .