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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_HostedAudioDevice.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2019
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
14//==============================================================================
16{
17public:
19 : AudioIODevice ("Hosted Device", "Hosted Device"), audioIf (aif), onDestroy (onDestroy_)
20 {}
21
22 ~HostedAudioDevice() override
23 {
24 if (onDestroy)
25 onDestroy (this);
26 }
27
28 juce::StringArray getOutputChannelNames() override { return audioIf.getOutputChannelNames(); }
29 juce::StringArray getInputChannelNames() override { return audioIf.getInputChannelNames(); }
30 juce::Array<double> getAvailableSampleRates() override { return { audioIf.parameters.sampleRate }; }
31 juce::Array<int> getAvailableBufferSizes() override { return { audioIf.parameters.blockSize }; }
32 int getDefaultBufferSize() override { return audioIf.parameters.blockSize; }
33
34 juce::String open (const juce::BigInteger& inputChannels,
35 const juce::BigInteger& outputChannels,
36 double sampleRate, int bufferSizeSamples) override
37 {
38 ignoreUnused (inputChannels, outputChannels, sampleRate, bufferSizeSamples);
39 return {};
40 }
41
42 void close() override {}
43 void start (juce::AudioIODeviceCallback* callback_) override
44 {
45 callback = callback_;
46 callback->audioDeviceAboutToStart (this);
47 }
48
49 void stop() override
50 {
51 callback->audioDeviceStopped();
52 callback = nullptr;
53 }
54
55 bool isOpen() override { return true; }
56 bool isPlaying() override { return true; }
57 juce::String getLastError() override { return {}; }
58 int getCurrentBitDepth() override { return 16; }
59 int getOutputLatencyInSamples() override { return 0; }
60 int getInputLatencyInSamples() override { return 0; }
61 bool hasControlPanel() const override { return false; }
62 bool showControlPanel() override { return false; }
63 bool setAudioPreprocessingEnabled (bool) override { return false; }
64 int getCurrentBufferSizeSamples() override { return audioIf.parameters.blockSize; }
65 double getCurrentSampleRate() override { return audioIf.parameters.sampleRate; }
66
67 juce::BigInteger getActiveOutputChannels() const override
68 {
70 res.setRange (0, audioIf.parameters.outputChannels, true);
71 return res;
72 }
73
74 juce::BigInteger getActiveInputChannels() const override
75 {
77 res.setRange (0, audioIf.parameters.inputChannels, true);
78 return res;
79 }
80
81 void processBlock (juce::AudioBuffer<float>& buffer)
82 {
83 if (callback != nullptr)
85 std::min (buffer.getNumChannels(), audioIf.parameters.inputChannels),
87 std::min (buffer.getNumChannels(), audioIf.parameters.outputChannels),
88 buffer.getNumSamples(),
89 {});
90 }
91
92 void settingsChanged()
93 {
94 if (callback != nullptr)
95 callback->audioDeviceAboutToStart (this);
96 }
97
98private:
100 std::function<void (HostedAudioDevice*)> onDestroy;
101 juce::AudioIODeviceCallback* callback = nullptr;
102};
103
104//==============================================================================
106{
107public:
109 : AudioIODeviceType ("Hosted Device"), audioIf (aif)
110 {}
111
112 ~HostedAudioDeviceType() override
113 {
114 if (audioIf.deviceType == this)
115 audioIf.deviceType = nullptr;
116 }
117
118 void scanForDevices() override {}
119 juce::StringArray getDeviceNames (bool = false) const override { return { "Hosted Device" }; }
120 int getDefaultDeviceIndex (bool) const override { return 0; }
121 int getIndexOfDevice (juce::AudioIODevice*, bool) const override { return 0; }
122 bool hasSeparateInputsAndOutputs() const override { return false; }
123
124 juce::AudioIODevice* createDevice (const juce::String&, const juce::String&) override
125 {
126 auto device = new HostedAudioDevice (audioIf, [ptr = juce::WeakReference<HostedAudioDeviceType> (this)] (HostedAudioDevice* d)
127 {
128 if (ptr != nullptr)
129 ptr->devices.removeFirstMatchingValue (d);
130 });
131 devices.add (device);
132 return device;
133 }
134
135 void processBlock (juce::AudioBuffer<float>& buffer)
136 {
137 for (auto device : devices)
138 device->processBlock (buffer);
139 }
140
141 void settingsChanged()
142 {
143 for (auto device : devices)
144 device->settingsChanged();
145 }
146
147private:
150
152};
153
154//==============================================================================
156{
157public:
159 : MidiInputDevice (aif.engine, TRANS("MIDI Input"), TRANS("MIDI Input"), "MIDI Input"), audioIf (aif)
160 {
161 }
162
163 ~HostedMidiInputDevice() override
164 {
165 audioIf.midiInputs.removeFirstMatchingValue (this);
166 }
167
168 DeviceType getDeviceType() const override
169 {
170 return virtualMidiDevice;
171 }
172
174 {
175 return new HostedMidiInputDeviceInstance (*this, epc);
176 }
177
178 void loadProps() override
179 {
180 auto n = engine.getPropertyStorage().getXmlPropertyItem (SettingID::midiin, getName());
181 MidiInputDevice::loadMidiProps (n.get());
182 }
183
184 void saveProps() override
185 {
186 juce::XmlElement n ("SETTINGS");
187
188 MidiInputDevice::saveMidiProps (n);
189
190 engine.getPropertyStorage().setXmlPropertyItem (SettingID::midiin, getName(), n);
191 }
192
193 void processBlock (juce::MidiBuffer& midi)
194 {
195 // Process the messages via the base class to update the keyboard state
196 for (auto mm : midi)
197 MidiInputDevice::handleIncomingMidiMessage (nullptr, mm.getMessage());
198
199 // Pending messages will then be filled with the messages to process
200 const juce::ScopedLock sl (pendingMidiMessagesMutex);
201
202 for (auto instance : instances)
203 if (auto hostedInstance = dynamic_cast<HostedMidiInputDeviceInstance*> (instance))
204 hostedInstance->processBlock (pendingMidiMessages);
205
206 pendingMidiMessages.clear();
207 }
208
209 using MidiInputDevice::handleIncomingMidiMessage;
210 void handleIncomingMidiMessage (const juce::MidiMessage& m) override
211 {
212 const juce::ScopedLock sl (pendingMidiMessagesMutex);
213 pendingMidiMessages.addEvent (m, 0);
214 }
215
216 juce::String openDevice() override { return {}; }
217 void closeDevice() override {}
218
219private:
220 //==============================================================================
221 class HostedMidiInputDeviceInstance : public MidiInputDeviceInstanceBase
222 {
223 public:
224 HostedMidiInputDeviceInstance (HostedMidiInputDevice& owner_, EditPlaybackContext& epc)
225 : MidiInputDeviceInstanceBase (owner_, epc)
226 {
227 }
228
229 void processBlock (juce::MidiBuffer& midi)
230 {
231 const auto globalStreamTime = edit.engine.getDeviceManager().getCurrentStreamTime();
232
233 // N.B. This assumes that the number of samples processed per block is constant.
234 // I.e. that there is no speed compensation set (which shouldn't be the case when
235 // running as a plugin)
236 for (auto mmm : midi)
237 {
238 const auto blockStreamTime = tracktion::graph::sampleToTime (mmm.samplePosition, sampleRate);
239
240 auto msg = mmm.getMessage();
241 msg.setTimeStamp (globalStreamTime + blockStreamTime);
242 handleIncomingMidiMessage (std::move (msg));
243 }
244 }
245
246 private:
247 const double sampleRate = context.getSampleRate();
248
249 HostedMidiInputDevice& getHostedMidiInputDevice() const { return static_cast<HostedMidiInputDevice&> (owner); }
250 };
251
252 //==============================================================================
253 HostedAudioDeviceInterface& audioIf;
254 juce::MidiBuffer pendingMidiMessages;
255 juce::CriticalSection pendingMidiMessagesMutex;
256};
257
258//==============================================================================
260{
261public:
263 : MidiOutputDevice (aif.engine, { TRANS("MIDI Output"), juce::String() }),
264 audioIf (aif)
265 {
266 }
267
268 ~HostedMidiOutputDevice() override
269 {
270 audioIf.midiOutputs.removeFirstMatchingValue (this);
271 }
272
273 MidiOutputDeviceInstance* createInstance (EditPlaybackContext& epc) override
274 {
275 return new HostedMidiOutputDeviceInstance (*this, epc);
276 }
277
278 void processBlock (juce::MidiBuffer& midi)
279 {
280 for (auto m : toSend)
281 {
282 auto t = m.getTimeStamp() * audioIf.parameters.sampleRate;
283 midi.addEvent (m, int (t));
284 }
285
286 toSend.clear();
287 }
288
289 void sendMessageNow (const juce::MidiMessage& message) override
290 {
291 toSend.addMidiMessage (message, 0, MidiMessageArray::notMPE);
292 toSend.sortByTimestamp();
293 }
294
295private:
296 struct HostedMidiOutputDeviceInstance : public MidiOutputDeviceInstance
297 {
298 HostedMidiOutputDeviceInstance (HostedMidiOutputDevice& o, EditPlaybackContext& epc)
299 : MidiOutputDeviceInstance (o, epc), outputDevice (o)
300 {
301 }
302
303 bool sendMessages (MidiMessageArray& mma, TimePosition editTime) override
304 {
305 // Adjust these messages to be relative to time 0.0 which will be the next call to processBlock
306 // The device delay is also subtracted as this will have been added when rendering
307 const auto deltaTime = -editTime - outputDevice.getDeviceDelay();
308 outputDevice.toSend.mergeFromAndClearWithOffset (mma, deltaTime.inSeconds());
309 return true;
310 }
311
312 HostedMidiOutputDevice& outputDevice;
313 };
314
316 MidiMessageArray toSend;
317};
318
319//==============================================================================
320HostedAudioDeviceInterface::HostedAudioDeviceInterface (Engine& e)
321 : engine (e)
322{
323}
324
325void HostedAudioDeviceInterface::initialise (const Parameters& p)
326{
327 parameters = p;
328
329 auto& dm = engine.getDeviceManager();
330
331 // First check for an existing hosted device
332 if (deviceType == nullptr)
333 for (auto device : dm.deviceManager.getAvailableDeviceTypes())
334 if (auto hostedAudioDeviceType = dynamic_cast<HostedAudioDeviceType*> (device))
335 deviceType = hostedAudioDeviceType;
336
337 // Otherwise add a new hosted device type
338 if (deviceType == nullptr)
339 {
340 deviceType = new HostedAudioDeviceType (*this);
341 dm.deviceManager.addAudioDeviceType (std::unique_ptr<HostedAudioDeviceType> (deviceType));
342 }
343
344 dm.deviceManager.setCurrentAudioDeviceType ("Hosted Device", true);
345 dm.initialise (parameters.inputChannels, parameters.outputChannels);
346 jassert (dm.deviceManager.getCurrentAudioDeviceType() == "Hosted Device");
347 jassert (dm.deviceManager.getCurrentDeviceTypeObject() == deviceType);
348
349 for (int i = 0; i < dm.getNumWaveOutDevices(); i++)
350 if (auto wo = dm.getWaveOutDevice (i))
351 wo->setEnabled (true);
352
353 for (int i = 0; i < dm.getNumWaveInDevices(); i++)
354 if (auto wi = dm.getWaveInDevice (i))
355 wi->setStereoPair (false);
356
357 for (int i = 0; i < dm.getNumWaveInDevices(); i++)
358 {
359 if (auto wi = dm.getWaveInDevice (i))
360 {
361 wi->setMonitorMode (InputDevice::MonitorMode::on);
362 wi->setEnabled (true);
363 }
364 }
365}
366
367void HostedAudioDeviceInterface::prepareToPlay (double sampleRate, int blockSize)
368{
369 auto newMaxChannels = std::max (parameters.inputChannels,
370 parameters.outputChannels);
371
372 if (parameters.sampleRate != sampleRate
373 || parameters.blockSize != blockSize
374 || maxChannels != newMaxChannels)
375 {
376 maxChannels = newMaxChannels;
377 parameters.sampleRate = sampleRate;
378 parameters.blockSize = blockSize;
379
380 if (deviceType != nullptr)
381 deviceType->settingsChanged();
382 }
383}
384
385void HostedAudioDeviceInterface::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midi)
386{
387 for (auto input : midiInputs)
388 if (auto hostedInput = dynamic_cast<HostedMidiInputDevice*> (input))
389 hostedInput->processBlock (midi);
390
391 midi.clear();
392
393 if (deviceType != nullptr)
394 deviceType->processBlock (buffer);
395
396 for (auto output : midiOutputs)
397 if (auto hostedOutput = dynamic_cast<HostedMidiOutputDevice*> (output))
398 hostedOutput->processBlock (midi);
399}
400
402{
403 return dynamic_cast<const HostedMidiInputDevice*> (&d) != nullptr;
404}
405
406juce::StringArray HostedAudioDeviceInterface::getInputChannelNames()
407{
409
410 for (int i = 0; i < parameters.inputChannels; i++)
411 {
412 if (i < parameters.inputNames.size())
413 res.add (parameters.inputNames[i]);
414 else
415 res.add (juce::String (i + 1));
416 }
417
418 return res;
419}
420
421juce::StringArray HostedAudioDeviceInterface::getOutputChannelNames()
422{
424
425 for (int i = 0; i < parameters.outputChannels; i++)
426 {
427 if (i < parameters.outputNames.size())
428 res.add (parameters.outputNames[i]);
429 else
430 res.add (juce::String (i + 1));
431 }
432
433 return res;
434}
435
436MidiOutputDevice* HostedAudioDeviceInterface::createMidiOutput()
437{
438 auto device = new HostedMidiOutputDevice (*this);
439 midiOutputs.add (device);
440 return device;
441}
442
443MidiInputDevice* HostedAudioDeviceInterface::createMidiInput()
444{
445 auto device = new HostedMidiInputDevice (*this);
446 midiInputs.add (device);
447 return device;
448}
449
450}} // namespace tracktion { inline namespace engine
int getNumChannels() const noexcept
int getNumSamples() const noexcept
const Type *const * getArrayOfReadPointers() const noexcept
Type *const * getArrayOfWritePointers() noexcept
virtual void audioDeviceIOCallbackWithContext(const float *const *inputChannelData, int numInputChannels, float *const *outputChannelData, int numOutputChannels, int numSamples, const AudioIODeviceCallbackContext &context)
virtual void audioDeviceAboutToStart(AudioIODevice *device)=0
virtual void audioDeviceStopped()=0
AudioIODevice(const String &deviceName, const String &typeName)
BigInteger & setRange(int startBit, int numBits, bool shouldBeSet)
bool addEvent(const MidiMessage &midiMessage, int sampleNumber)
void clear() noexcept
int size() const noexcept
void add(String stringToAdd)
double getCurrentStreamTime() const noexcept
Returns the current block's stream time.
Engine & engine
A reference to the Engine.
The Engine is the central class for all tracktion sessions.
PropertyStorage & getPropertyStorage() const
Returns the PropertyStorage user settings customisable XML file.
DeviceManager & getDeviceManager() const
Returns the DeviceManager instance for handling audio / MIDI devices.
The HostedAudioDeviceInterface allows an application or plugin to pass audio and midi buffers to the ...
static bool isHostedMidiInputDevice(const MidiInputDevice &)
Returns true if the MidiInput device is a HostedMidiInputDevice.
juce::StringArray inputNames
Names of your audio channels.
InputDeviceInstance * createInstance(EditPlaybackContext &epc) override
Creates an instance to use for a given playback context.
An instance of an InputDevice that's available to an Edit.
Edit & edit
The Edit this instance belongs to.
EditPlaybackContext & context
The EditPlaybackContext this instance belongs to.
InputDevice & owner
The state of this instance.
@ on
Live input is always audible.
DeviceType
enum to allow quick querying of the device type.
#define TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_WEAK_REFERENCEABLE(Class)
T max(T... args)
T min(T... args)
Represents a position in real-life time.