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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_TrackOutput.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
14TrackOutput::TrackOutput (Track& t)
15 : owner (t)
16{
17 auto um = &t.edit.getUndoManager();
18 state = t.state.getOrCreateChildWithName (IDs::OUTPUTDEVICES, um);
19 auto devicesTree = state.getOrCreateChildWithName (IDs::DEVICE, um);
20 outputDevice.referTo (devicesTree, IDs::name, um);
21
22 state.addListener (this);
23}
24
25TrackOutput::~TrackOutput()
26{
27 state.removeListener (this);
28}
29
30//==============================================================================
31void TrackOutput::initialise()
32{
33 updateOutput();
34
35 if (outputDevice.get().isEmpty() && getDestinationTrack() == nullptr)
36 setOutputToDefaultDevice (false);
37}
38
39void TrackOutput::flushStateToValueTree()
40{
41 if (! destTrackID.isValid())
42 return;
43
44 if (auto at = findAudioTrackForID (owner.edit, destTrackID))
45 setOutputToTrack (at);
46}
47
48void TrackOutput::updateOutput()
49{
50 // Also called after edit has created all the tracks - check our output isn't dangling
51 for (auto t = getDestinationTrack(); t != nullptr; t = t->getOutput().getDestinationTrack())
52 t->setFrozen (false, Track::groupFreeze);
53
54 auto oldTrackID = destTrackID;
55 destTrackID = {};
56
57 if (outputDevice.get().startsWith ("track "))
58 {
59 auto trackNum = outputDevice.get().upToFirstOccurrenceOf ("(", false, false).trim().getTrailingIntValue();
60
61 for (auto at : getAudioTracks (owner.edit))
62 {
63 if (at->getAudioTrackNumber() == trackNum)
64 {
65 if (! at->getOutput().feedsInto (&owner)) // check for recursion
66 destTrackID = at->itemID;
67
68 break;
69 }
70 }
71
72 if (oldTrackID == destTrackID)
73 return;
74 }
75
76 owner.edit.restartPlayback();
77 owner.changed();
78 owner.setFrozen (false, Track::groupFreeze);
79}
80
81//==============================================================================
82bool TrackOutput::outputsToDevice (const juce::String& deviceName, bool compareDefaultDevices) const
83{
84 if (auto destTrack = getDestinationTrack())
85 return deviceName.startsWithIgnoreCase (TRANS("Track") + " ")
86 && (deviceName.upToFirstOccurrenceOf ("(", false, false).trim().getTrailingIntValue()
87 == destTrack->getAudioTrackNumber());
88
89 if (outputDevice.get().equalsIgnoreCase (deviceName))
90 return true;
91
92 if (compareDefaultDevices)
93 {
94 auto& dm = owner.edit.engine.getDeviceManager();
95
96 if (auto defaultWave = dm.getDefaultWaveOutDevice())
97 {
98 if (defaultWave->isEnabled())
99 {
100 auto defWaveName = defaultWave->getName();
101
102 bool b1 = deviceName.equalsIgnoreCase (DeviceManager::getDefaultAudioOutDeviceName (false))
103 && outputDevice.get().equalsIgnoreCase (defWaveName);
104
105 bool b2 = deviceName.equalsIgnoreCase (defWaveName)
106 && outputDevice.get().equalsIgnoreCase (DeviceManager::getDefaultAudioOutDeviceName (false));
107
108 if (b1 || b2)
109 return true;
110 }
111 }
112
113 if (auto defaultMIDI = dm.getDefaultMidiOutDevice())
114 {
115 if (defaultMIDI->isEnabled())
116 {
117 auto defMidiName = defaultMIDI->getName();
118
119 if ((deviceName.equalsIgnoreCase (DeviceManager::getDefaultMidiOutDeviceName (false))
120 && outputDevice.get().equalsIgnoreCase (defMidiName))
121 || (deviceName.equalsIgnoreCase (defMidiName)
122 && outputDevice.get().equalsIgnoreCase (DeviceManager::getDefaultMidiOutDeviceName (false))))
123 return true;
124 }
125 }
126 }
127
128 return false;
129}
130
131AudioTrack* TrackOutput::getDestinationTrack() const
132{
133 if (destTrackID.isValid())
134 {
135 auto t = findTrackForID (owner.edit, destTrackID);
136
137 if (t != &owner) // recursion check
138 return dynamic_cast<AudioTrack*> (t);
139 }
140
141 return {};
142}
143
144bool TrackOutput::outputsToDestTrack (AudioTrack& t) const
145{
146 return t.itemID == destTrackID;
147}
148
149OutputDevice* TrackOutput::getOutputDevice (bool traceThroughDestTracks) const
150{
151 auto dev = owner.edit.engine.getDeviceManager().findOutputDeviceWithName (outputDevice);
152
153 if ((dev == nullptr || ! dev->isEnabled()) && traceThroughDestTracks)
154 if (auto t = getDestinationTrack())
155 dev = t->getOutput().getOutputDevice (true);
156
157 return dev;
158}
159
160juce::String TrackOutput::getOutputName() const
161{
162 if (auto t = getDestinationTrack())
163 return t->getNameAsTrackNumber();
164
165 return outputDevice;
166}
167
168juce::String TrackOutput::getDescriptiveOutputName() const
169{
170 if (auto t = getDestinationTrack())
171 {
172 if (! t->getName().startsWithIgnoreCase (TRANS("Track") + " "))
173 return TRANS("Feeds into track 123 (XZZX)")
174 .replace ("123", juce::String (t->getAudioTrackNumber()))
175 .replace ("XZZX", "(" + t->getName() + ")");
176
177 return TRANS("Feeds into track 123")
178 .replace ("123", juce::String (t->getAudioTrackNumber()));
179 }
180
181 if (auto dev = owner.edit.engine.getDeviceManager().findOutputDeviceWithName (outputDevice))
182 return dev->getAlias();
183
184 return outputDevice;
185}
186
187void TrackOutput::setOutputByName (const juce::String& name)
188{
189 if (name.startsWith (TRANS("Track") + " "))
190 outputDevice = juce::String ("track ") + juce::String (name.upToFirstOccurrenceOf ("(", false, false).trim().getTrailingIntValue());
191 else
192 outputDevice = name;
193}
194
195bool TrackOutput::canPlayAudio() const
196{
197 if (auto out = getOutputDevice (false))
198 if (! out->isMidi())
199 return true;
200
201 if (auto t = getDestinationTrack())
202 return t->canPlayAudio();
203
204 return false;
205}
206
207bool TrackOutput::canPlayMidi() const
208{
209 if (auto out = getOutputDevice (false))
210 if (out->isMidi())
211 return true;
212
213 if (auto t = getDestinationTrack())
214 return t->canPlayMidi();
215
216 return false;
217}
218
219void TrackOutput::setOutputToTrack (AudioTrack* track)
220{
221 outputDevice = track != nullptr ? juce::String ("track") + " " + juce::String (track->getAudioTrackNumber())
222 : juce::String();
223}
224
225void TrackOutput::setOutputToDefaultDevice (bool isMidi)
226{
227 outputDevice = isMidi ? DeviceManager::getDefaultMidiOutDeviceName (false)
228 : DeviceManager::getDefaultAudioOutDeviceName (false);
229}
230
231static bool feedsIntoAnyOf (AudioTrack* t, const juce::Array<AudioTrack*>& tracks)
232{
233 if (tracks.contains (t))
234 return true;
235
236 auto& output = t->getOutput();
237
238 for (auto track : tracks)
239 if (output.feedsInto (track))
240 return true;
241
242 return false;
243}
244
245void TrackOutput::getPossibleOutputDeviceNames (const juce::Array<AudioTrack*>& tracks,
247 juce::BigInteger& hasAudio,
248 juce::BigInteger& hasMidi)
249{
250 if (tracks.isEmpty())
251 return;
252
253 s.add (DeviceManager::getDefaultAudioOutDeviceName (false));
254 a.add (DeviceManager::getDefaultAudioOutDeviceName (true));
255 hasAudio.setBit (0);
256
257 s.add (DeviceManager::getDefaultMidiOutDeviceName (false));
258 a.add (DeviceManager::getDefaultMidiOutDeviceName (true));
259 hasMidi.setBit (1);
260
261 auto& dm = tracks.getFirst()->edit.engine.getDeviceManager();
262
263 for (int i = 0; i < dm.getNumOutputDevices(); ++i)
264 {
265 if (auto out = dm.getOutputDeviceAt (i))
266 {
267 if (out->isEnabled())
268 {
269 if (auto m = dynamic_cast<MidiOutputDevice*> (out))
270 {
271 if (m->isConnectedToExternalController())
272 continue;
273
274 hasMidi.setBit (s.size(), true);
275 }
276 else
277 {
278 hasAudio.setBit (s.size(), true);
279 }
280
281 s.add (out->getName());
282 a.add (out->getAlias());
283 }
284 }
285 }
286}
287
288void TrackOutput::getPossibleOutputNames (const juce::Array<AudioTrack*>& tracks,
290 juce::BigInteger& hasAudio,
291 juce::BigInteger& hasMidi)
292{
293 if (tracks.isEmpty())
294 return;
295
296 getPossibleOutputDeviceNames (tracks, s, a, hasAudio, hasMidi);
297
298 auto& edit = tracks[0]->edit;
299
300 for (auto t : getAudioTracks (edit))
301 {
302 if (t->createsOutput() && ! feedsIntoAnyOf (t, tracks))
303 {
304 auto trackName = t->getNameAsTrackNumberWithDescription();
305 s.add (trackName);
306 a.add (trackName);
307 }
308 }
309}
310
311bool TrackOutput::feedsInto (const Track* dest) const
312{
313 if (auto t = getDestinationTrack())
314 return t == dest || t->getOutput().feedsInto (dest);
315
316 return false;
317}
318
319bool TrackOutput::injectLiveMidiMessage (const juce::MidiMessage& message)
320{
321 auto dev = owner.edit.engine.getDeviceManager().findOutputDeviceWithName (outputDevice);
322
323 if (dev != nullptr && dev->isMidi() && dev->isEnabled())
324 {
325 if (auto midiDev = dynamic_cast<MidiOutputDevice*> (dev))
326 {
327 midiDev->fireMessage (message);
328 return true;
329 }
330 }
331 else
332 {
333 if (auto t = getDestinationTrack())
334 {
335 t->getOutput().injectLiveMidiMessage (message);
336 return true;
337 }
338 }
339
340 return false;
341}
342
343void TrackOutput::valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& ident)
344{
345 if (v.hasType (IDs::DEVICE) && ident == IDs::name)
346 {
347 outputDevice.forceUpdateOfCachedValue();
348 updateOutput();
349 }
350}
351
352}} // namespace tracktion { inline namespace engine
bool isEmpty() const noexcept
ElementType getFirst() const noexcept
bool contains(ParameterType elementToLookFor) const
BigInteger & setBit(int bitNumber)
int size() const noexcept
void add(String stringToAdd)
bool equalsIgnoreCase(const String &other) const noexcept
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) const
String trim() const
bool startsWithIgnoreCase(StringRef text) const noexcept
int getTrailingIntValue() const noexcept
DeviceManager & getDeviceManager() const
Returns the DeviceManager instance for handling audio / MIDI devices.
Base class for audio or midi output devices, to which a track's output can be sent.
Base class for tracks which contain clips and plugins and can be added to Edit[s].
#define TRANS(stringLiteral)
juce::Array< AudioTrack * > getAudioTracks(const Edit &edit)
Returns all the AudioTracks in an Edit.
Track * findTrackForID(const Edit &edit, EditItemID id)
Returns the Track with a given ID if contained in the Edit.