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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_InputDevice.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
14InputDevice::InputDevice (Engine& e, juce::String t, juce::String n, juce::String idToUse)
15 : engine (e), type (t), deviceID (std::move (idToUse)), name (n)
16{
17 alias = e.getPropertyStorage().getPropertyItem (SettingID::invalid, getAliasPropName());
18}
19
20InputDevice::~InputDevice()
21{
22}
23
24juce::String InputDevice::getAliasPropName() const
25{
26 return type + "in_" + name + "_alias";
27}
28
29bool InputDevice::isTrackDevice() const
30{
31 return getDeviceType() == trackWaveDevice
32 || getDeviceType() == trackMidiDevice;
33}
34
35juce::String InputDevice::getAlias() const
36{
37 if (alias.trim().isNotEmpty())
38 return alias;
39
40 return getName();
41}
42
43void InputDevice::setAlias (const juce::String& a)
44{
45 if (alias != a)
46 {
47 alias = a.substring (0, 40).trim();
48
49 if (alias == getName())
50 alias = {};
51
52 if (alias.isNotEmpty())
53 engine.getPropertyStorage().setPropertyItem (SettingID::invalid, getAliasPropName(), alias);
54 else
55 engine.getPropertyStorage().removePropertyItem (SettingID::invalid, getAliasPropName());
56 }
57}
58
59bool InputDevice::isEnabled() const
60{
61 return enabled;
62}
63
64void InputDevice::setMonitorMode (MonitorMode newMode)
65{
66 if (monitorMode == newMode)
67 return;
68
69 monitorMode = newMode;
70 TransportControl::restartAllTransports (engine, false);
71 changed();
72 saveProps();
73}
74
75juce::String InputDevice::getSelectableDescription()
76{
77 return name + " (" + type + ")";
78}
79
80void InputDevice::setRetrospectiveLock (Engine& e, const juce::Array<InputDeviceInstance*>& devices, bool lock)
81{
82 const juce::ScopedLock sl (e.getDeviceManager().deviceManager.getAudioCallbackLock());
83
84 for (auto* idi : devices)
85 idi->getInputDevice().retrospectiveRecordLock = lock;
86}
87
88//==============================================================================
89InputDeviceInstance::InputDeviceInstance (InputDevice& d, EditPlaybackContext& c)
90 : state (c.edit.getEditInputDevices().getInstanceStateForInputDevice (d)), owner (d), context (c), edit (c.edit)
91{
92 trackDeviceEnabler.setFunction ([this]
93 {
94 // if we're a track device we also need to disable our owner as there will only
95 // be one instance and this stops the device getting added to the EditPlaybackContext
96 if (owner.isTrackDevice())
97 {
98 owner.setEnabled (destinations.size() > 0);
99
100 if (! owner.isEnabled())
101 state.getParent().removeChild (state, nullptr);
102 }
103 });
104
105 recordStatusUpdater.setFunction ([this] { updateRecordingStatus(); });
106
107 state.addListener (this);
108}
109
114
116{
118 trackDeviceEnabler.handleUpdateNowIfNeeded();
119
120 if (ref.wasObjectDeleted())
121 return {};
122
123 if (! owner.isEnabled())
124 return {};
125
127
128 for (auto dest : destinations)
129 if (auto tID = dest->targetID; tID.isValid())
130 targets.add (tID);
131
132 return targets;
133}
134
135tl::expected<InputDeviceInstance::Destination*, juce::String>
137 std::optional<int> index)
138{
139 auto track = findTrackForID (edit, targetID);
140 ClipSlot* clipSlot = track != nullptr ? nullptr
141 : findClipSlotForID (edit, targetID);
142
143 if (! (track || clipSlot))
144 return {};
145
146 if (isRecording())
147 return tl::unexpected (TRANS("Can't change tracks whilst recording is active"));
148
149 if (owner.isTrackDevice() || move)
150 {
151 for (auto t : getTargets())
152 [[ maybe_unused ]] auto res = removeTarget (t, um);
153 }
154 else
155 {
156 [[ maybe_unused ]] auto res = removeTarget (targetID, um);
157 }
158
159 auto v = juce::ValueTree (IDs::INPUTDEVICEDESTINATION);
160 v.setProperty (IDs::targetID, targetID, nullptr);
161
162 if (index.has_value())
163 v.setProperty (IDs::targetIndex, *index, nullptr);
164
165 state.addChild (v, -1, um);
166 jassert (destinations[destinations.size() - 1]->targetID == targetID);
167
168 trackDeviceEnabler.triggerAsyncUpdate();
169
170 return destinations[destinations.size() - 1];
171}
172
174{
175 if (isRecording())
176 return juce::Result::fail (TRANS("Can't change tracks whilst recording is active"));
177
178 for (int i = destinations.size(); --i >= 0;)
179 {
180 auto& dt = *destinations[i];
181
182 if (dt.targetID == targetID)
183 state.removeChild (dt.state, um);
184 }
185
186 return juce::Result::ok();
187}
188
190{
191 for (auto dest : destinations)
192 if (dest->recordEnabled)
193 return true;
194
195 return false;
196}
197
199{
200 for (auto dest : destinations)
201 if (targetID == dest->targetID)
202 return dest->recordEnabled;
203
204 return false;
205}
206
208{
209 for (auto dest : destinations)
210 if (dest->targetID == targetID)
211 if (dest->recordEnabled)
212 return true;
213
214 return false;
215}
216
218{
219 for (auto dest : destinations)
220 if (dest->targetID == targetID)
221 dest->recordEnabled = b;
222}
223
225{
226 if (! t.acceptsInput())
227 return false;
228
229 for (auto dest : destinations)
230 {
231 if (dest->targetID != t.itemID)
232 continue;
233
234 switch (owner.getMonitorMode())
235 {
236 case InputDevice::MonitorMode::on: return true;
238 case InputDevice::MonitorMode::off: return false;
239 };
240 }
241
242 return false;
243}
244
245void InputDeviceInstance::valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& id)
246{
247 if (v.getParent() == state)
248 {
249 if (id == IDs::armed || id == IDs::targetID)
250 {
251 const auto targetID = EditItemID::fromVar (v[IDs::targetID]);
252
253 if (id == IDs::targetID)
254 if (auto t = findTrackForID (edit, targetID))
255 t->changed();
256
257 changedTargetTrackIDs.push_back (targetID);
258 recordStatusUpdater.triggerAsyncUpdate();
259 }
260 }
261}
262
263void InputDeviceInstance::valueTreeChildAdded (juce::ValueTree& p, juce::ValueTree& c)
264{
265 if (p == state && c.hasType (IDs::INPUTDEVICEDESTINATION))
266 {
267 const auto targetID = EditItemID::fromVar (c[IDs::targetID]);
268
269 if (auto t = findTrackForID (edit, targetID))
270 t->changed();
271
272 changedTargetTrackIDs.push_back (targetID);
273 recordStatusUpdater.triggerAsyncUpdate();
274 }
275}
276
277void InputDeviceInstance::valueTreeChildRemoved (juce::ValueTree& p, juce::ValueTree& c, int)
278{
279 if (p == state && c.hasType (IDs::INPUTDEVICEDESTINATION))
280 {
281 const auto targetID = EditItemID::fromVar (c[IDs::targetID]);
282
283 if (auto t = findTrackForID (edit, targetID))
284 t->changed();
285
286 changedTargetTrackIDs.push_back (targetID);
287 recordStatusUpdater.triggerAsyncUpdate();
288 }
289}
290
291ClipSlot* InputDeviceInstance::getFreeSlot (AudioTrack& t)
292{
293 for (auto slot : t.getClipSlotList().getClipSlots())
294 if (slot->getClip() == nullptr)
295 return slot;
296
297 auto& sl = edit.getSceneList();
298 sl.ensureNumberOfScenes (sl.getNumScenes() + 1);
299
300 return t.getClipSlotList().getClipSlots().getLast();
301}
302
303void InputDeviceInstance::updateRecordingStatus()
304{
305 for (auto dest : destinations)
306 {
307 auto targetID = dest->targetID;
308
309 if (std::find (changedTargetTrackIDs.begin(), changedTargetTrackIDs.end(), targetID) == changedTargetTrackIDs.end())
310 continue;
311
312 auto track = findTrackForID (edit, targetID);
313
314 if (! track)
315 continue;
316
317 InputDeviceInstance::StopRecordingParameters params;
318 params.targetsToStop = { targetID };
319 params.unloopedTimeToEndRecording = context.getUnloopedPosition();
320 params.isLooping = context.transport.looping;
321 params.markedRange = context.transport.getLoopRange();
322 params.discardRecordings = false;
323 auto res = stopRecording (params);
324 Clip::Array clips = res.value_or (Clip::Array());
325
326 const bool wasRecording = ! clips.isEmpty();
327 const bool isLivePlayActive = isLivePlayEnabled (*track);
328
329 // This logic rebuilds the audio graph if the monitor state changes as it's a static node-type in the graph
330 if (! wasRecording && wasLivePlayActive != isLivePlayActive)
332
333 wasLivePlayActive = isLivePlayActive;
334
335 if (! wasRecording && isRecordingEnabled (targetID))
336 prepareAndPunchRecord (*this, targetID);
337 }
338
339 changedTargetTrackIDs.clear();
340}
341
342//==============================================================================
344{
346
347 for (auto dest : instance.destinations)
348 if (auto at = findAudioTrackForID (instance.edit, dest->targetID))
349 tracks.add ({ at, dest->targetIndex });
350
351 return tracks;
352}
353
355{
357
358 for (auto [at, idx] : getTargetTracksAndIndexes (instance))
359 tracks.add (at);
360
361 return tracks;
362}
363
364bool isOnTargetTrack (InputDeviceInstance& instance, const Track& track, int index)
365{
366 for (auto [at, idx] : getTargetTracksAndIndexes (instance))
367 if (at == &track && idx == index)
368 return true;
369
370 return false;
371}
372
374{
375 if (instance.isRecording())
376 return juce::Result::fail (TRANS("Can't change tracks whilst recording is active"));
377
378 auto result = juce::Result::ok();
379
380 for (auto targetID : instance.getTargets())
381 if (auto res = instance.removeTarget (targetID, um); ! res)
382 result = res;
383
384 return juce::Result::ok();
385}
386
388{
389 return ! instance.getTargets().isEmpty();
390}
391
392//==============================================================================
393InputDeviceInstance::Destination* getDestination (InputDeviceInstance& instance, const Track& track, int index)
394{
395 for (auto dest : instance.destinations)
396 if (dest->targetID == track.itemID && dest->targetIndex == index)
397 return dest;
398
399 return {};
400}
401
402InputDeviceInstance::Destination* getDestination (InputDeviceInstance& instance, const ClipSlot& cs)
403{
404 for (auto dest : instance.destinations)
405 if (dest->targetID == cs.itemID)
406 return dest;
407
408 return {};
409}
410
411InputDeviceInstance::Destination* getDestination (InputDeviceInstance& instance, const juce::ValueTree& destinationState)
412{
413 for (auto dest : instance.destinations)
414 if (dest->state == destinationState)
415 return dest;
416
417 return {};
418}
419
420
421}} // namespace tracktion { inline namespace engine
void add(const ElementType &newElement)
void handleUpdateNowIfNeeded()
CriticalSection & getAudioCallbackLock() noexcept
static Result fail(const String &errorMessage) noexcept
static Result ok() noexcept
String trim() const
String substring(int startIndex, int endIndex) const
void removeChild(const ValueTree &child, UndoManager *undoManager)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
Represents a slot on a track that a Clip can live in to be played as a launched clip.
const EditItemID itemID
Every EditItem has an ID which is unique within the edit.
void restartPlayback()
Use this to tell the play engine to rebuild the audio graph if the toplogy has changed.
SceneList & getSceneList()
Returns a list of Scenes in the Edit.
The Engine is the central class for all tracktion sessions.
DeviceManager & getDeviceManager() const
Returns the DeviceManager instance for handling audio / MIDI devices.
An instance of an InputDevice that's available to an Edit.
virtual bool isLivePlayEnabled(const Track &) const
Whether the track should monitor the input or not.
virtual bool isRecordingActive() const
Returns true if recording is enabled and the input is connected to any target.
Edit & edit
The Edit this instance belongs to.
bool isRecordingEnabled(EditItemID) const
Returns true if recording is enabled for the given target.
juce::Array< EditItemID > getTargets() const
Returns the targets this instance is assigned to.
virtual bool isRecording(EditItemID)=0
Returns true if there are any active recordings for this device.
virtual bool isRecording()=0
Returns true if there are any active recordings for this device.
void setRecordingEnabled(EditItemID, bool)
Enabled/disables recording for a given target.
EditPlaybackContext & context
The EditPlaybackContext this instance belongs to.
InputDevice & owner
The state of this instance.
DestinationList destinations
The list of assigned destinations.
juce::Result removeTarget(EditItemID targetID, juce::UndoManager *)
Removes the destination with the given targetID.
tl::expected< Destination *, juce::String > setTarget(EditItemID targetID, bool moveToTrack, juce::UndoManager *, std::optional< int > index=std::nullopt)
Assigns this input to either an AudioTrack or a ClipSlot.
virtual tl::expected< Clip::Array, juce::String > stopRecording(StopRecordingParameters)=0
Stops a recording.
Represents an input device.
@ automatic
Live input is audible when record is enabled.
@ off
Live input is never audible.
@ on
Live input is always audible.
void ensureNumberOfScenes(int numScenes)
Adds Scenes to ensure numScenes are preset in the list.
static bool isSelectableValid(const Selectable *) noexcept
checks whether this object has been deleted.
Base class for tracks which contain clips and plugins and can be added to Edit[s].
TimeRange getLoopRange() const noexcept
Returns the loop range.
T find(T... args)
#define TRANS(stringLiteral)
#define jassert(expression)
T move(T... args)
juce::String getName(LaunchQType t)
Retuns the name of a LaunchQType for display purposes.
bool isAttached(InputDeviceInstance &instance)
Returns true if this input is assigned to a target.
juce::Array< std::pair< AudioTrack *, int > > getTargetTracksAndIndexes(InputDeviceInstance &instance)
Returns the AudioTracks and their indexes this instance is assigned to.
juce::Result clearFromTargets(InputDeviceInstance &instance, juce::UndoManager *um)
Removes this instance from all assigned targets.
ClipSlot * findClipSlotForID(const Edit &edit, EditItemID id)
Returns the ClipSlot for the given ID.
bool isOnTargetTrack(InputDeviceInstance &instance, const Track &track, int index)
Returns true if this instance is assigned to the given Track at the given index .
juce::Result prepareAndPunchRecord(InputDeviceInstance &instance, EditItemID targetID)
Starts an InputDeviceInstance recording to the given target without any count-in etc.
Track * findTrackForID(const Edit &edit, EditItemID id)
Returns the Track with a given ID if contained in the Edit.
AudioTrack * findAudioTrackForID(const Edit &edit, EditItemID id)
Returns the AudioTrack with a given ID if contained in the Edit.
InputDeviceInstance::Destination * getDestination(InputDeviceInstance &instance, const Track &track, int index)
Returns the destination if one has been assigned for the given arguments.
juce::Array< AudioTrack * > getTargetTracks(InputDeviceInstance &instance)
Returns the AudioTracks this instance is assigned to.
ID for objects of type EditElement - e.g.