1 /*
2  * Carla Plugin Host
3  * Copyright (C) 2011-2020 Filipe Coelho <falktx@falktx.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of
8  * the License, or any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * For a full copy of the GNU General Public License see the doc/GPL.txt file.
16  */
17 
18 #include "CarlaEngineGraph.hpp"
19 #include "CarlaEngineInit.hpp"
20 #include "CarlaEngineInternal.hpp"
21 #include "CarlaBackendUtils.hpp"
22 #include "CarlaMathUtils.hpp"
23 #include "CarlaStringList.hpp"
24 
25 #include "RtLinkedList.hpp"
26 
27 #include "jackbridge/JackBridge.hpp"
28 
29 #if defined(__clang__)
30 # pragma clang diagnostic push
31 # pragma clang diagnostic ignored "-Wconversion"
32 # pragma clang diagnostic ignored "-Wdeprecated-copy"
33 # pragma clang diagnostic ignored "-Weffc++"
34 # pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
35 #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
36 # pragma GCC diagnostic push
37 # pragma GCC diagnostic ignored "-Wconversion"
38 # pragma GCC diagnostic ignored "-Weffc++"
39 # pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
40 #endif
41 
42 #include "rtaudio/RtAudio.h"
43 #include "rtmidi/RtMidi.h"
44 
45 #if defined(__clang__)
46 # pragma clang diagnostic pop
47 #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
48 # pragma GCC diagnostic pop
49 #endif
50 
51 CARLA_BACKEND_START_NAMESPACE
52 
53 // -------------------------------------------------------------------------------------------------------------------
54 // Global static data
55 
56 static CharStringListPtr         gDeviceNames;
57 static std::vector<RtAudio::Api> gRtAudioApis;
58 
59 // -------------------------------------------------------------------------------------------------------------------
60 
initRtAudioAPIsIfNeeded()61 static void initRtAudioAPIsIfNeeded()
62 {
63     static bool needsInit = true;
64 
65     if (! needsInit)
66         return;
67 
68     needsInit = false;
69 
70     // get APIs in a local var, and pass wanted ones into gRtAudioApis
71 
72     std::vector<RtAudio::Api> apis;
73     RtAudio::getCompiledApi(apis);
74 
75     for (std::vector<RtAudio::Api>::const_iterator it = apis.begin(), end=apis.end(); it != end; ++it)
76     {
77         const RtAudio::Api& api(*it);
78 
79         if (api == RtAudio::UNIX_JACK)
80         {
81 #if defined(CARLA_OS_LINUX) || defined(CARLA_OS_MAC) || defined(CARLA_OS_WIN)
82             if ( ! jackbridge_is_ok())
83                 continue;
84 #else
85             /* NOTE
86              * RtMidi doesn't have a native MIDI backend for these OSes,
87              * Using RtAudio JACK funcitonality is only useful when we need to access the native MIDI APIs.
88              * (JACK audio + ALSA MIDI, or JACK audio + CoreMidi, or JACK audio + Windows MIDI)
89              * Because RtMidi has no native MIDI support outside of win/mac/linux, we skip these RtAudio APIs.
90              * Those OSes can use Carla's JACK support directly, which is much better than RtAudio classes.
91              */
92             continue;
93 #endif
94         }
95 
96         gRtAudioApis.push_back(api);
97     }
98 }
99 
getRtAudioApiName(const RtAudio::Api api)100 static const char* getRtAudioApiName(const RtAudio::Api api) noexcept
101 {
102     switch (api)
103     {
104     case RtAudio::UNSPECIFIED:
105         return "Unspecified";
106     case RtAudio::LINUX_ALSA:
107         return "ALSA";
108     case RtAudio::LINUX_OSS:
109         return "OSS";
110     case RtAudio::UNIX_PULSE:
111         return "PulseAudio";
112     case RtAudio::UNIX_JACK:
113 #if defined(CARLA_OS_LINUX) && defined(__LINUX_ALSA__)
114         return "JACK with ALSA-MIDI";
115 #elif defined(CARLA_OS_MAC)
116         return "JACK with CoreMidi";
117 #elif defined(CARLA_OS_WIN)
118         return "JACK with WinMM";
119 #else
120         return "JACK (RtAudio)";
121 #endif
122     case RtAudio::MACOSX_CORE:
123         return "CoreAudio";
124     case RtAudio::WINDOWS_ASIO:
125         return "ASIO";
126     case RtAudio::WINDOWS_DS:
127         return "DirectSound";
128     case RtAudio::WINDOWS_WASAPI:
129         return "WASAPI";
130     case RtAudio::RTAUDIO_DUMMY:
131         return "Dummy";
132     }
133 
134     carla_stderr("CarlaBackend::getRtAudioApiName(%i) - invalid API", api);
135     return nullptr;
136 }
137 
getMatchedAudioMidiAPI(const RtAudio::Api rtApi)138 static RtMidi::Api getMatchedAudioMidiAPI(const RtAudio::Api rtApi) noexcept
139 {
140     switch (rtApi)
141     {
142     case RtAudio::UNSPECIFIED:
143         return RtMidi::UNSPECIFIED;
144 
145     case RtAudio::LINUX_ALSA:
146     case RtAudio::LINUX_OSS:
147         return RtMidi::LINUX_ALSA;
148 
149     case RtAudio::UNIX_PULSE:
150     case RtAudio::UNIX_JACK:
151 #if defined(CARLA_OS_LINUX) && defined(__LINUX_ALSA__)
152         return RtMidi::LINUX_ALSA;
153 #elif defined(CARLA_OS_MAC)
154         return RtMidi::MACOSX_CORE;
155 #elif defined(CARLA_OS_WIN)
156         return RtMidi::WINDOWS_MM;
157 #else
158         return RtMidi::RTMIDI_DUMMY;
159 #endif
160 
161     case RtAudio::MACOSX_CORE:
162         return RtMidi::MACOSX_CORE;
163 
164     case RtAudio::WINDOWS_ASIO:
165     case RtAudio::WINDOWS_DS:
166     case RtAudio::WINDOWS_WASAPI:
167         return RtMidi::WINDOWS_MM;
168 
169     case RtAudio::RTAUDIO_DUMMY:
170         return RtMidi::RTMIDI_DUMMY;
171     }
172 
173     return RtMidi::UNSPECIFIED;
174 }
175 
176 // -------------------------------------------------------------------------------------------------------------------
177 // RtAudio Engine
178 
179 class CarlaEngineRtAudio : public CarlaEngine
180 {
181 public:
CarlaEngineRtAudio(const RtAudio::Api api)182     CarlaEngineRtAudio(const RtAudio::Api api)
183         : CarlaEngine(),
184           fAudio(api),
185           fAudioInterleaved(false),
186           fAudioInCount(0),
187           fAudioOutCount(0),
188           fLastEventTime(0),
189           fDeviceName(),
190           fAudioIntBufIn(nullptr),
191           fAudioIntBufOut(nullptr),
192           fMidiIns(),
193           fMidiInEvents(),
194           fMidiOuts(),
195           fMidiOutMutex(),
196           fMidiOutVector(EngineMidiEvent::kDataSize)
197     {
198         carla_debug("CarlaEngineRtAudio::CarlaEngineRtAudio(%i)", api);
199 
200         // just to make sure
201         pData->options.transportMode = ENGINE_TRANSPORT_MODE_INTERNAL;
202     }
203 
~CarlaEngineRtAudio()204     ~CarlaEngineRtAudio() override
205     {
206         CARLA_SAFE_ASSERT(fAudioInCount == 0);
207         CARLA_SAFE_ASSERT(fAudioOutCount == 0);
208         CARLA_SAFE_ASSERT(fLastEventTime == 0);
209         carla_debug("CarlaEngineRtAudio::~CarlaEngineRtAudio()");
210     }
211 
212     // -------------------------------------
213 
init(const char * const clientName)214     bool init(const char* const clientName) override
215     {
216         CARLA_SAFE_ASSERT_RETURN(fAudioInCount == 0, false);
217         CARLA_SAFE_ASSERT_RETURN(fAudioOutCount == 0, false);
218         CARLA_SAFE_ASSERT_RETURN(fLastEventTime == 0, false);
219         CARLA_SAFE_ASSERT_RETURN(clientName != nullptr && clientName[0] != '\0', false);
220         carla_debug("CarlaEngineRtAudio::init(\"%s\")", clientName);
221 
222         if (pData->options.processMode != ENGINE_PROCESS_MODE_CONTINUOUS_RACK && pData->options.processMode != ENGINE_PROCESS_MODE_PATCHBAY)
223         {
224             setLastError("Invalid process mode");
225             return false;
226         }
227 
228         const bool isDummy(fAudio.getCurrentApi() == RtAudio::RtAudio::RTAUDIO_DUMMY);
229         bool deviceSet = false;
230         RtAudio::StreamParameters iParams, oParams;
231 
232         if (isDummy)
233         {
234             if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK)
235             {
236                 setLastError("Cannot use dummy driver in Rack mode");
237                 return false;
238             }
239 
240             fDeviceName = "Dummy";
241         }
242         else
243         {
244             const uint devCount(fAudio.getDeviceCount());
245 
246             if (devCount == 0)
247             {
248                 setLastError("No audio devices available for this driver");
249                 return false;
250             }
251 
252             if (pData->options.audioDevice != nullptr && pData->options.audioDevice[0] != '\0')
253             {
254                 for (uint i=0; i < devCount; ++i)
255                 {
256                     RtAudio::DeviceInfo devInfo(fAudio.getDeviceInfo(i));
257 
258                     if (devInfo.probed && devInfo.outputChannels > 0 && devInfo.name == pData->options.audioDevice)
259                     {
260                         deviceSet   = true;
261                         fDeviceName = devInfo.name.c_str();
262                         iParams.deviceId  = i;
263                         oParams.deviceId  = i;
264                         iParams.nChannels = devInfo.inputChannels;
265                         oParams.nChannels = devInfo.outputChannels;
266                         break;
267                     }
268                 }
269             }
270 
271             if (! deviceSet)
272             {
273                 iParams.deviceId  = fAudio.getDefaultInputDevice();
274                 oParams.deviceId  = fAudio.getDefaultOutputDevice();
275                 iParams.nChannels = fAudio.getDeviceInfo(iParams.deviceId).inputChannels;
276                 oParams.nChannels = fAudio.getDeviceInfo(oParams.deviceId).outputChannels;
277 
278                 carla_stdout("No device set, using %i inputs and %i outputs", iParams.nChannels, oParams.nChannels);
279             }
280 
281             if (oParams.nChannels == 0 && pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK)
282             {
283                 setLastError("Current audio setup has no outputs, cannot continue");
284                 return false;
285             }
286 
287             iParams.nChannels = carla_fixedValue(0U, 128U, iParams.nChannels);
288             oParams.nChannels = carla_fixedValue(0U, 128U, oParams.nChannels);
289             fAudioInterleaved = fAudio.getCurrentApi() == RtAudio::UNIX_PULSE;
290         }
291 
292         RtAudio::StreamOptions rtOptions;
293         rtOptions.flags = RTAUDIO_MINIMIZE_LATENCY | RTAUDIO_SCHEDULE_REALTIME;
294         rtOptions.numberOfBuffers = pData->options.audioTripleBuffer ? 3 : 2;
295         rtOptions.streamName = clientName;
296         rtOptions.priority = 85;
297 
298         if (fAudio.getCurrentApi() == RtAudio::LINUX_ALSA && ! deviceSet)
299             rtOptions.flags |= RTAUDIO_ALSA_USE_DEFAULT;
300         if (! fAudioInterleaved)
301             rtOptions.flags |= RTAUDIO_NONINTERLEAVED;
302 
303         uint bufferFrames = pData->options.audioBufferSize;
304 
305         try {
306             fAudio.openStream(oParams.nChannels > 0 ? &oParams : nullptr,
307                               iParams.nChannels > 0 ? &iParams : nullptr,
308                               RTAUDIO_FLOAT32, pData->options.audioSampleRate, &bufferFrames,
309                               carla_rtaudio_process_callback, this, &rtOptions,
310                               carla_rtaudio_buffer_size_callback);
311         }
312         catch (const RtAudioError& e) {
313             setLastError(e.what());
314             return false;
315         }
316 
317         if (! pData->init(clientName))
318         {
319             close();
320             setLastError("Failed to init internal data");
321             return false;
322         }
323 
324         pData->bufferSize = bufferFrames;
325         pData->sampleRate = isDummy ? 44100.0 : fAudio.getStreamSampleRate();
326         pData->initTime(pData->options.transportExtra);
327 
328         fAudioInCount  = iParams.nChannels;
329         fAudioOutCount = oParams.nChannels;
330         fLastEventTime = 0;
331 
332         if (fAudioInCount > 0)
333             fAudioIntBufIn = new float[fAudioInCount*bufferFrames];
334 
335         if (fAudioOutCount > 0)
336             fAudioIntBufOut = new float[fAudioOutCount*bufferFrames];
337 
338         pData->graph.create(fAudioInCount, fAudioOutCount, 0, 0);
339 
340         try {
341             fAudio.startStream();
342         }
343         catch (const RtAudioError& e)
344         {
345             close();
346             setLastError(e.what());
347             return false;
348         }
349 
350         patchbayRefresh(true, false, false);
351 
352         if (pData->options.processMode == ENGINE_PROCESS_MODE_PATCHBAY)
353             refreshExternalGraphPorts<PatchbayGraph>(pData->graph.getPatchbayGraph(), false, false);
354 
355         callback(true, true,
356                  ENGINE_CALLBACK_ENGINE_STARTED,
357                  0,
358                  pData->options.processMode,
359                  pData->options.transportMode,
360                  static_cast<int>(pData->bufferSize),
361                  static_cast<float>(pData->sampleRate),
362                  getCurrentDriverName());
363         return true;
364     }
365 
close()366     bool close() override
367     {
368         carla_debug("CarlaEngineRtAudio::close()");
369 
370         bool hasError = false;
371 
372         // stop stream first
373         if (fAudio.isStreamOpen() && fAudio.isStreamRunning())
374         {
375             try {
376                 fAudio.stopStream();
377             }
378             catch (const RtAudioError& e)
379             {
380                 setLastError(e.what());
381                 hasError = true;
382             }
383         }
384 
385         // clear engine data
386         CarlaEngine::close();
387 
388         pData->graph.destroy();
389 
390         for (LinkedList<MidiInPort>::Itenerator it = fMidiIns.begin2(); it.valid(); it.next())
391         {
392             static MidiInPort fallback = { nullptr, { '\0' } };
393 
394             MidiInPort& inPort(it.getValue(fallback));
395             CARLA_SAFE_ASSERT_CONTINUE(inPort.port != nullptr);
396 
397             inPort.port->cancelCallback();
398             inPort.port->closePort();
399             delete inPort.port;
400         }
401 
402         fMidiIns.clear();
403         fMidiInEvents.clear();
404 
405         fMidiOutMutex.lock();
406 
407         for (LinkedList<MidiOutPort>::Itenerator it = fMidiOuts.begin2(); it.valid(); it.next())
408         {
409             static MidiOutPort fallback = { nullptr, { '\0' } };
410 
411             MidiOutPort& outPort(it.getValue(fallback));
412             CARLA_SAFE_ASSERT_CONTINUE(outPort.port != nullptr);
413 
414             outPort.port->closePort();
415             delete outPort.port;
416         }
417 
418         fMidiOuts.clear();
419         fMidiOutMutex.unlock();
420 
421         fAudioInCount  = 0;
422         fAudioOutCount = 0;
423         fLastEventTime = 0;
424         fDeviceName.clear();
425 
426         if (fAudioIntBufIn != nullptr)
427         {
428             delete[] fAudioIntBufIn;
429             fAudioIntBufIn = nullptr;
430         }
431 
432         if (fAudioIntBufOut != nullptr)
433         {
434             delete[] fAudioIntBufOut;
435             fAudioIntBufOut = nullptr;
436         }
437 
438         // close stream
439         if (fAudio.isStreamOpen())
440             fAudio.closeStream();
441 
442         return !hasError;
443     }
444 
isRunning() const445     bool isRunning() const noexcept override
446     {
447         return fAudio.isStreamOpen();
448     }
449 
isOffline() const450     bool isOffline() const noexcept override
451     {
452         return false;
453     }
454 
getType() const455     EngineType getType() const noexcept override
456     {
457         return kEngineTypeRtAudio;
458     }
459 
getCurrentDriverName() const460     const char* getCurrentDriverName() const noexcept override
461     {
462         return CarlaBackend::getRtAudioApiName(fAudio.getCurrentApi());
463     }
464 
465     // -------------------------------------------------------------------
466     // Patchbay
467 
468     template<class Graph>
refreshExternalGraphPorts(Graph * const graph,const bool sendHost,const bool sendOSC)469     bool refreshExternalGraphPorts(Graph* const graph, const bool sendHost, const bool sendOSC)
470     {
471         CARLA_SAFE_ASSERT_RETURN(graph != nullptr, false);
472 
473         char strBuf[STR_MAX+1U];
474         strBuf[STR_MAX] = '\0';
475 
476         ExternalGraph& extGraph(graph->extGraph);
477 
478         // ---------------------------------------------------------------
479         // clear last ports
480 
481         extGraph.clear();
482 
483         // ---------------------------------------------------------------
484         // fill in new ones
485 
486         // Audio In
487         for (uint i=0; i < fAudioInCount; ++i)
488         {
489             std::snprintf(strBuf, STR_MAX, "capture_%i", i+1);
490 
491             PortNameToId portNameToId;
492             portNameToId.setData(kExternalGraphGroupAudioIn, i+1, strBuf, "");
493 
494             extGraph.audioPorts.ins.append(portNameToId);
495         }
496 
497         // Audio Out
498         for (uint i=0; i < fAudioOutCount; ++i)
499         {
500             std::snprintf(strBuf, STR_MAX, "playback_%i", i+1);
501 
502             PortNameToId portNameToId;
503             portNameToId.setData(kExternalGraphGroupAudioOut, i+1, strBuf, "");
504 
505             extGraph.audioPorts.outs.append(portNameToId);
506         }
507 
508         // MIDI In
509         try
510         {
511             RtMidiIn midiIn(getMatchedAudioMidiAPI(fAudio.getCurrentApi()), "carla-discovery-in");
512 
513             for (uint i=0, count = midiIn.getPortCount(); i < count; ++i)
514             {
515                 PortNameToId portNameToId;
516                 portNameToId.setData(kExternalGraphGroupMidiIn, i+1, midiIn.getPortName(i).c_str(), "");
517 
518                 extGraph.midiPorts.ins.append(portNameToId);
519             }
520         } CARLA_SAFE_EXCEPTION("RtMidiIn discovery");
521 
522         // MIDI Out
523         try
524         {
525             RtMidiOut midiOut(getMatchedAudioMidiAPI(fAudio.getCurrentApi()), "carla-discovery-out");
526 
527             for (uint i=0, count = midiOut.getPortCount(); i < count; ++i)
528             {
529                 PortNameToId portNameToId;
530                 portNameToId.setData(kExternalGraphGroupMidiOut, i+1, midiOut.getPortName(i).c_str(), "");
531 
532                 extGraph.midiPorts.outs.append(portNameToId);
533             }
534         } CARLA_SAFE_EXCEPTION("RtMidiOut discovery");
535 
536         // ---------------------------------------------------------------
537         // now refresh
538 
539         if (sendHost || sendOSC)
540             graph->refresh(sendHost, sendOSC, true, fDeviceName.buffer());
541 
542         // ---------------------------------------------------------------
543         // add midi connections
544 
545         for (LinkedList<MidiInPort>::Itenerator it=fMidiIns.begin2(); it.valid(); it.next())
546         {
547             static const MidiInPort fallback = { nullptr, { '\0' } };
548 
549             const MidiInPort& inPort(it.getValue(fallback));
550             CARLA_SAFE_ASSERT_CONTINUE(inPort.port != nullptr);
551 
552             const uint portId(extGraph.midiPorts.getPortId(true, inPort.name));
553             CARLA_SAFE_ASSERT_CONTINUE(portId < extGraph.midiPorts.ins.count());
554 
555             ConnectionToId connectionToId;
556             connectionToId.setData(++(extGraph.connections.lastId), kExternalGraphGroupMidiIn, portId, kExternalGraphGroupCarla, kExternalGraphCarlaPortMidiIn);
557 
558             std::snprintf(strBuf, STR_MAX, "%i:%i:%i:%i", connectionToId.groupA, connectionToId.portA, connectionToId.groupB, connectionToId.portB);
559 
560             extGraph.connections.list.append(connectionToId);
561 
562             callback(sendHost, sendOSC,
563                       ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED,
564                       connectionToId.id,
565                       0, 0, 0, 0.0f,
566                       strBuf);
567         }
568 
569         fMidiOutMutex.lock();
570 
571         for (LinkedList<MidiOutPort>::Itenerator it=fMidiOuts.begin2(); it.valid(); it.next())
572         {
573             static const MidiOutPort fallback = { nullptr, { '\0' } };
574 
575             const MidiOutPort& outPort(it.getValue(fallback));
576             CARLA_SAFE_ASSERT_CONTINUE(outPort.port != nullptr);
577 
578             const uint portId(extGraph.midiPorts.getPortId(false, outPort.name));
579             CARLA_SAFE_ASSERT_CONTINUE(portId < extGraph.midiPorts.outs.count());
580 
581             ConnectionToId connectionToId;
582             connectionToId.setData(++(extGraph.connections.lastId), kExternalGraphGroupCarla, kExternalGraphCarlaPortMidiOut, kExternalGraphGroupMidiOut, portId);
583 
584             std::snprintf(strBuf, STR_MAX, "%i:%i:%i:%i", connectionToId.groupA, connectionToId.portA, connectionToId.groupB, connectionToId.portB);
585 
586             extGraph.connections.list.append(connectionToId);
587 
588             callback(sendHost, sendOSC,
589                       ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED,
590                       connectionToId.id,
591                       0, 0, 0, 0.0f,
592                       strBuf);
593         }
594 
595         fMidiOutMutex.unlock();
596 
597         return true;
598     }
599 
patchbayRefresh(const bool sendHost,const bool sendOSC,const bool external)600     bool patchbayRefresh(const bool sendHost, const bool sendOSC, const bool external) override
601     {
602         CARLA_SAFE_ASSERT_RETURN(pData->graph.isReady(), false);
603 
604         if (pData->options.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK)
605             return refreshExternalGraphPorts<RackGraph>(pData->graph.getRackGraph(), sendHost, sendOSC);
606 
607         if (sendHost)
608             pData->graph.setUsingExternalHost(external);
609         if (sendOSC)
610             pData->graph.setUsingExternalOSC(external);
611 
612         if (external)
613             return refreshExternalGraphPorts<PatchbayGraph>(pData->graph.getPatchbayGraph(), sendHost, sendOSC);
614 
615         return CarlaEngine::patchbayRefresh(sendHost, sendOSC, false);
616     }
617 
618     // -------------------------------------------------------------------
619 
620 protected:
handleAudioProcessCallback(void * outputBuffer,void * inputBuffer,uint nframes,double streamTime,RtAudioStreamStatus status)621     void handleAudioProcessCallback(void* outputBuffer, void* inputBuffer,
622                                     uint nframes, double streamTime, RtAudioStreamStatus status)
623     {
624         const PendingRtEventsRunner prt(this, nframes, true);
625 
626         if (status & RTAUDIO_INPUT_OVERFLOW)
627             ++pData->xruns;
628         if (status & RTAUDIO_OUTPUT_UNDERFLOW)
629             ++pData->xruns;
630 
631         // get buffers from RtAudio
632         const float* const insPtr  = (const float*)inputBuffer;
633         /* */ float* const outsPtr =       (float*)outputBuffer;
634 
635         // assert rtaudio buffers
636         CARLA_SAFE_ASSERT_RETURN(outputBuffer != nullptr,);
637 
638         // set rtaudio buffers as non-interleaved
639         const float* inBuf[fAudioInCount];
640         /* */ float* outBuf[fAudioOutCount];
641 
642         if (fAudioInterleaved)
643         {
644             // FIXME - this looks completely wrong!
645             float* inBuf2[fAudioInCount];
646 
647             for (uint i=0, count=fAudioInCount; i<count; ++i)
648             {
649                 inBuf [i] = fAudioIntBufIn + (nframes*i);
650                 inBuf2[i] = fAudioIntBufIn + (nframes*i);
651             }
652             for (uint i=0, count=fAudioOutCount; i<count; ++i)
653                 outBuf[i] = fAudioIntBufOut + (nframes*i);
654 
655             // init input
656             for (uint i=0; i<nframes; ++i)
657                 for (uint j=0; j<fAudioInCount; ++j)
658                     inBuf2[j][i] = insPtr[i*fAudioInCount+j];
659 
660             // clear output
661             carla_zeroFloats(fAudioIntBufOut, fAudioOutCount*nframes);
662         }
663         else
664         {
665             for (uint i=0; i < fAudioInCount; ++i)
666                 inBuf[i] = insPtr+(nframes*i);
667             for (uint i=0; i < fAudioOutCount; ++i)
668                 outBuf[i] = outsPtr+(nframes*i);
669 
670             // clear output
671             carla_zeroFloats(outsPtr, nframes*fAudioOutCount);
672         }
673 
674         // initialize events
675         carla_zeroStructs(pData->events.in,  kMaxEngineEventInternalCount);
676         carla_zeroStructs(pData->events.out, kMaxEngineEventInternalCount);
677 
678         if (fMidiInEvents.mutex.tryLock())
679         {
680             uint32_t engineEventIndex = 0;
681             fMidiInEvents.splice();
682 
683             for (LinkedList<RtMidiEvent>::Itenerator it = fMidiInEvents.data.begin2(); it.valid(); it.next())
684             {
685                 static const RtMidiEvent fallback = { 0, 0, { 0 } };
686 
687                 const RtMidiEvent& midiEvent(it.getValue(fallback));
688                 CARLA_SAFE_ASSERT_CONTINUE(midiEvent.size > 0);
689 
690                 EngineEvent& engineEvent(pData->events.in[engineEventIndex++]);
691 
692                 if (midiEvent.time < pData->timeInfo.frame)
693                 {
694                     engineEvent.time = 0;
695                 }
696                 else if (midiEvent.time >= pData->timeInfo.frame + nframes)
697                 {
698                     carla_stderr("MIDI Event in the future!, %i vs %i", engineEvent.time, pData->timeInfo.frame);
699                     engineEvent.time = static_cast<uint32_t>(pData->timeInfo.frame) + nframes - 1;
700                 }
701                 else
702                     engineEvent.time = static_cast<uint32_t>(midiEvent.time - pData->timeInfo.frame);
703 
704                 engineEvent.fillFromMidiData(midiEvent.size, midiEvent.data, 0);
705 
706                 if (engineEventIndex >= kMaxEngineEventInternalCount)
707                     break;
708             }
709 
710             fMidiInEvents.data.clear();
711             fMidiInEvents.mutex.unlock();
712         }
713 
714         pData->graph.process(pData, inBuf, outBuf, nframes);
715 
716         fMidiOutMutex.lock();
717 
718         if (fMidiOuts.count() > 0)
719         {
720             uint8_t size     = 0;
721             uint8_t mdata[3] = { 0, 0, 0 };
722             uint8_t mdataTmp[EngineMidiEvent::kDataSize];
723             const uint8_t* mdataPtr;
724 
725             for (ushort i=0; i < kMaxEngineEventInternalCount; ++i)
726             {
727                 const EngineEvent& engineEvent(pData->events.out[i]);
728 
729                 /**/ if (engineEvent.type == kEngineEventTypeNull)
730                 {
731                     break;
732                 }
733                 else if (engineEvent.type == kEngineEventTypeControl)
734                 {
735                     const EngineControlEvent& ctrlEvent(engineEvent.ctrl);
736 
737                     size = ctrlEvent.convertToMidiData(engineEvent.channel, mdata);
738                     mdataPtr = mdata;
739                 }
740                 else if (engineEvent.type == kEngineEventTypeMidi)
741                 {
742                     const EngineMidiEvent& midiEvent(engineEvent.midi);
743 
744                     size = midiEvent.size;
745                     CARLA_SAFE_ASSERT_CONTINUE(size > 0);
746 
747                     if (size > EngineMidiEvent::kDataSize)
748                     {
749                         CARLA_SAFE_ASSERT_CONTINUE(midiEvent.dataExt != nullptr);
750                         mdataPtr = midiEvent.dataExt;
751                     }
752                     else
753                     {
754                         // set first byte
755                         mdataTmp[0] = static_cast<uint8_t>(midiEvent.data[0] | (engineEvent.channel & MIDI_CHANNEL_BIT));
756 
757                         // copy rest
758                         carla_copy<uint8_t>(mdataTmp+1, midiEvent.data+1, size-1U);
759 
760                         // done
761                         mdataPtr = mdataTmp;
762                     }
763                 }
764                 else
765                 {
766                     continue;
767                 }
768 
769                 if (size > 0)
770                 {
771                     fMidiOutVector.assign(mdataPtr, mdataPtr + size);
772 
773                     for (LinkedList<MidiOutPort>::Itenerator it=fMidiOuts.begin2(); it.valid(); it.next())
774                     {
775                         static MidiOutPort fallback = { nullptr, { '\0' } };
776 
777                         MidiOutPort& outPort(it.getValue(fallback));
778                         CARLA_SAFE_ASSERT_CONTINUE(outPort.port != nullptr);
779 
780                         outPort.port->sendMessage(&fMidiOutVector);
781                     }
782                 }
783             }
784         }
785 
786         fMidiOutMutex.unlock();
787 
788         if (fAudioInterleaved)
789         {
790             for (uint i=0; i < nframes; ++i)
791                 for (uint j=0; j<fAudioOutCount; ++j)
792                     outsPtr[i*fAudioOutCount+j] = outBuf[j][i];
793         }
794 
795         return; // unused
796         (void)streamTime;
797     }
798 
handleBufferSizeCallback(const uint newBufferSize)799     void handleBufferSizeCallback(const uint newBufferSize)
800     {
801         carla_stdout("bufferSize callback %u %u", pData->bufferSize, newBufferSize);
802         if (pData->bufferSize == newBufferSize)
803             return;
804 
805         if (fAudioInCount > 0)
806         {
807             delete[] fAudioIntBufIn;
808             fAudioIntBufIn = new float[fAudioInCount*newBufferSize];
809         }
810 
811         if (fAudioOutCount > 0)
812         {
813             delete[] fAudioIntBufOut;
814             fAudioIntBufOut = new float[fAudioOutCount*newBufferSize];
815         }
816 
817         pData->bufferSize = newBufferSize;
818         bufferSizeChanged(newBufferSize);
819     }
820 
handleMidiCallback(double timeStamp,std::vector<uchar> * const message)821     void handleMidiCallback(double timeStamp, std::vector<uchar>* const message)
822     {
823         const size_t messageSize(message->size());
824 
825         if (messageSize == 0 || messageSize > EngineMidiEvent::kDataSize)
826             return;
827 
828         timeStamp /= 2;
829 
830         if (timeStamp > 0.95)
831             timeStamp = 0.95;
832         else if (timeStamp < 0.0)
833             timeStamp = 0.0;
834 
835         RtMidiEvent midiEvent;
836         midiEvent.time = pData->timeInfo.frame + uint64_t(timeStamp * (double)pData->bufferSize);
837 
838         if (midiEvent.time < fLastEventTime)
839             midiEvent.time = fLastEventTime;
840         else
841             fLastEventTime = midiEvent.time;
842 
843         midiEvent.size = static_cast<uint8_t>(messageSize);
844 
845         size_t i=0;
846         for (; i < messageSize; ++i)
847             midiEvent.data[i] = message->at(i);
848         for (; i < EngineMidiEvent::kDataSize; ++i)
849             midiEvent.data[i] = 0;
850 
851         fMidiInEvents.append(midiEvent);
852     }
853 
854     // -------------------------------------------------------------------
855 
connectExternalGraphPort(const uint connectionType,const uint portId,const char * const portName)856     bool connectExternalGraphPort(const uint connectionType, const uint portId, const char* const portName) override
857     {
858         CARLA_SAFE_ASSERT_RETURN(connectionType != 0 || (portName != nullptr && portName[0] != '\0'), false);
859         carla_debug("CarlaEngineRtAudio::connectExternalGraphPort(%u, %u, \"%s\")", connectionType, portId, portName);
860 
861         switch (connectionType)
862         {
863         case kExternalGraphConnectionAudioIn1:
864         case kExternalGraphConnectionAudioIn2:
865         case kExternalGraphConnectionAudioOut1:
866         case kExternalGraphConnectionAudioOut2:
867             return CarlaEngine::connectExternalGraphPort(connectionType, portId, portName);
868 
869         case kExternalGraphConnectionMidiInput: {
870             CarlaString newRtMidiPortName;
871             newRtMidiPortName += getName();
872             newRtMidiPortName += ":";
873             newRtMidiPortName += portName;
874 
875             RtMidiIn* rtMidiIn;
876 
877             try {
878                 rtMidiIn = new RtMidiIn(getMatchedAudioMidiAPI(fAudio.getCurrentApi()), newRtMidiPortName.buffer(), 512);
879             } CARLA_SAFE_EXCEPTION_RETURN("new RtMidiIn", false);
880 
881             rtMidiIn->ignoreTypes();
882             rtMidiIn->setCallback(carla_rtmidi_callback, this);
883 
884             bool found = false;
885             uint rtMidiPortIndex;
886 
887             for (uint i=0, count=rtMidiIn->getPortCount(); i < count; ++i)
888             {
889                 if (rtMidiIn->getPortName(i) == portName)
890                 {
891                     found = true;
892                     rtMidiPortIndex = i;
893                     break;
894                 }
895             }
896 
897             if (! found)
898             {
899                 delete rtMidiIn;
900                 return false;
901             }
902 
903             try {
904                 rtMidiIn->openPort(rtMidiPortIndex, portName);
905             }
906             catch(...) {
907                 delete rtMidiIn;
908                 return false;
909             };
910 
911             MidiInPort midiPort;
912             midiPort.port = rtMidiIn;
913 
914             std::strncpy(midiPort.name, portName, STR_MAX);
915             midiPort.name[STR_MAX] = '\0';
916 
917             fMidiIns.append(midiPort);
918             return true;
919         }   break;
920 
921         case kExternalGraphConnectionMidiOutput: {
922             CarlaString newRtMidiPortName;
923             newRtMidiPortName += getName();
924             newRtMidiPortName += ":";
925             newRtMidiPortName += portName;
926 
927             RtMidiOut* rtMidiOut;
928 
929             try {
930                 rtMidiOut = new RtMidiOut(getMatchedAudioMidiAPI(fAudio.getCurrentApi()), newRtMidiPortName.buffer());
931             } CARLA_SAFE_EXCEPTION_RETURN("new RtMidiOut", false);
932 
933             bool found = false;
934             uint rtMidiPortIndex;
935 
936             for (uint i=0, count=rtMidiOut->getPortCount(); i < count; ++i)
937             {
938                 if (rtMidiOut->getPortName(i) == portName)
939                 {
940                     found = true;
941                     rtMidiPortIndex = i;
942                     break;
943                 }
944             }
945 
946             if (! found)
947             {
948                 delete rtMidiOut;
949                 return false;
950             }
951 
952             try {
953                 rtMidiOut->openPort(rtMidiPortIndex, portName);
954             }
955             catch(...) {
956                 delete rtMidiOut;
957                 return false;
958             };
959 
960             MidiOutPort midiPort;
961             midiPort.port = rtMidiOut;
962 
963             std::strncpy(midiPort.name, portName, STR_MAX);
964             midiPort.name[STR_MAX] = '\0';
965 
966             const CarlaMutexLocker cml(fMidiOutMutex);
967 
968             fMidiOuts.append(midiPort);
969             return true;
970         }   break;
971         }
972 
973         return false;
974     }
975 
disconnectExternalGraphPort(const uint connectionType,const uint portId,const char * const portName)976     bool disconnectExternalGraphPort(const uint connectionType, const uint portId, const char* const portName) override
977     {
978         CARLA_SAFE_ASSERT_RETURN(connectionType != 0 || (portName != nullptr && portName[0] != '\0'), false);
979         carla_debug("CarlaEngineRtAudio::disconnectExternalGraphPort(%u, %u, \"%s\")", connectionType, portId, portName);
980 
981         switch (connectionType)
982         {
983         case kExternalGraphConnectionAudioIn1:
984         case kExternalGraphConnectionAudioIn2:
985         case kExternalGraphConnectionAudioOut1:
986         case kExternalGraphConnectionAudioOut2:
987             return CarlaEngine::disconnectExternalGraphPort(connectionType, portId, portName);
988 
989         case kExternalGraphConnectionMidiInput:
990             for (LinkedList<MidiInPort>::Itenerator it=fMidiIns.begin2(); it.valid(); it.next())
991             {
992                 static MidiInPort fallback = { nullptr, { '\0' } };
993 
994                 MidiInPort& inPort(it.getValue(fallback));
995                 CARLA_SAFE_ASSERT_CONTINUE(inPort.port != nullptr);
996 
997                 if (std::strncmp(inPort.name, portName, STR_MAX) != 0)
998                     continue;
999 
1000                 inPort.port->cancelCallback();
1001                 inPort.port->closePort();
1002                 delete inPort.port;
1003 
1004                 fMidiIns.remove(it);
1005                 return true;
1006             }
1007             break;
1008 
1009         case kExternalGraphConnectionMidiOutput: {
1010             const CarlaMutexLocker cml(fMidiOutMutex);
1011 
1012             for (LinkedList<MidiOutPort>::Itenerator it=fMidiOuts.begin2(); it.valid(); it.next())
1013             {
1014                 static MidiOutPort fallback = { nullptr, { '\0' } };
1015 
1016                 MidiOutPort& outPort(it.getValue(fallback));
1017                 CARLA_SAFE_ASSERT_CONTINUE(outPort.port != nullptr);
1018 
1019                 if (std::strncmp(outPort.name, portName, STR_MAX) != 0)
1020                     continue;
1021 
1022                 outPort.port->closePort();
1023                 delete outPort.port;
1024 
1025                 fMidiOuts.remove(it);
1026                 return true;
1027             }
1028         }   break;
1029         }
1030 
1031         return false;
1032     }
1033 
1034     // -------------------------------------------------------------------
1035 
1036 private:
1037     RtAudio fAudio;
1038 
1039     // useful info
1040     bool fAudioInterleaved;
1041     uint fAudioInCount;
1042     uint fAudioOutCount;
1043     uint64_t fLastEventTime;
1044 
1045     // current device name
1046     CarlaString fDeviceName;
1047 
1048     // temp buffer for interleaved audio
1049     float* fAudioIntBufIn;
1050     float* fAudioIntBufOut;
1051 
1052     struct MidiInPort {
1053         RtMidiIn* port;
1054         char name[STR_MAX+1];
1055     };
1056 
1057     struct MidiOutPort {
1058         RtMidiOut* port;
1059         char name[STR_MAX+1];
1060     };
1061 
1062     struct RtMidiEvent {
1063         uint64_t time; // needs to compare to internal time
1064         uint8_t  size;
1065         uint8_t  data[EngineMidiEvent::kDataSize];
1066     };
1067 
1068     struct RtMidiEvents {
1069         CarlaMutex mutex;
1070         RtLinkedList<RtMidiEvent>::Pool dataPool;
1071         RtLinkedList<RtMidiEvent> data;
1072         RtLinkedList<RtMidiEvent> dataPending;
1073 
RtMidiEventsCarlaEngineRtAudio::RtMidiEvents1074         RtMidiEvents()
1075             : mutex(),
1076               dataPool("RtMidiEvents", 512, 512),
1077               data(dataPool),
1078               dataPending(dataPool) {}
1079 
~RtMidiEventsCarlaEngineRtAudio::RtMidiEvents1080         ~RtMidiEvents()
1081         {
1082             clear();
1083         }
1084 
appendCarlaEngineRtAudio::RtMidiEvents1085         void append(const RtMidiEvent& event)
1086         {
1087             mutex.lock();
1088             dataPending.append(event);
1089             mutex.unlock();
1090         }
1091 
clearCarlaEngineRtAudio::RtMidiEvents1092         void clear()
1093         {
1094             mutex.lock();
1095             data.clear();
1096             dataPending.clear();
1097             mutex.unlock();
1098         }
1099 
spliceCarlaEngineRtAudio::RtMidiEvents1100         void splice()
1101         {
1102             if (dataPending.count() > 0)
1103                 dataPending.moveTo(data, true /* append */);
1104         }
1105     };
1106 
1107     LinkedList<MidiInPort> fMidiIns;
1108     RtMidiEvents           fMidiInEvents;
1109 
1110     LinkedList<MidiOutPort> fMidiOuts;
1111     CarlaMutex              fMidiOutMutex;
1112     std::vector<uint8_t>    fMidiOutVector;
1113 
1114     #define handlePtr ((CarlaEngineRtAudio*)userData)
1115 
carla_rtaudio_process_callback(void * outputBuffer,void * inputBuffer,uint nframes,double streamTime,RtAudioStreamStatus status,void * userData)1116     static int carla_rtaudio_process_callback(void* outputBuffer, void* inputBuffer, uint nframes, double streamTime, RtAudioStreamStatus status, void* userData)
1117     {
1118         handlePtr->handleAudioProcessCallback(outputBuffer, inputBuffer, nframes, streamTime, status);
1119         return 0;
1120     }
1121 
carla_rtaudio_buffer_size_callback(unsigned int bufferSize,void * userData)1122     static bool carla_rtaudio_buffer_size_callback(unsigned int bufferSize, void* userData)
1123     {
1124         handlePtr->handleBufferSizeCallback(bufferSize);
1125         return true;
1126     }
1127 
carla_rtmidi_callback(double timeStamp,std::vector<uchar> * message,void * userData)1128     static void carla_rtmidi_callback(double timeStamp, std::vector<uchar>* message, void* userData)
1129     {
1130         handlePtr->handleMidiCallback(timeStamp, message);
1131     }
1132 
1133     #undef handlePtr
1134 
1135     CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CarlaEngineRtAudio)
1136 };
1137 
1138 // -----------------------------------------
1139 
1140 namespace EngineInit {
1141 
newRtAudio(const AudioApi api)1142 CarlaEngine* newRtAudio(const AudioApi api)
1143 {
1144     initRtAudioAPIsIfNeeded();
1145 
1146     RtAudio::Api rtApi = RtAudio::UNSPECIFIED;
1147 
1148     switch (api)
1149     {
1150     case AUDIO_API_NULL:
1151         rtApi = RtAudio::RTAUDIO_DUMMY;
1152         break;
1153     case AUDIO_API_JACK:
1154         rtApi = RtAudio::UNIX_JACK;
1155         break;
1156     case AUDIO_API_OSS:
1157         rtApi = RtAudio::LINUX_OSS;
1158         break;
1159     case AUDIO_API_ALSA:
1160         rtApi = RtAudio::LINUX_ALSA;
1161         break;
1162     case AUDIO_API_PULSEAUDIO:
1163         rtApi = RtAudio::UNIX_PULSE;
1164         break;
1165     case AUDIO_API_COREAUDIO:
1166         rtApi = RtAudio::MACOSX_CORE;
1167         break;
1168     case AUDIO_API_ASIO:
1169         rtApi = RtAudio::WINDOWS_ASIO;
1170         break;
1171     case AUDIO_API_DIRECTSOUND:
1172         rtApi = RtAudio::WINDOWS_DS;
1173         break;
1174     case AUDIO_API_WASAPI:
1175         rtApi = RtAudio::WINDOWS_WASAPI;
1176         break;
1177     }
1178 
1179     return new CarlaEngineRtAudio(rtApi);
1180 }
1181 
getRtAudioApiCount()1182 uint getRtAudioApiCount()
1183 {
1184     initRtAudioAPIsIfNeeded();
1185 
1186     return static_cast<uint>(gRtAudioApis.size());
1187 }
1188 
getRtAudioApiName(const uint index)1189 const char* getRtAudioApiName(const uint index)
1190 {
1191     initRtAudioAPIsIfNeeded();
1192 
1193     CARLA_SAFE_ASSERT_RETURN(index < gRtAudioApis.size(), nullptr);
1194 
1195     return CarlaBackend::getRtAudioApiName(gRtAudioApis[index]);
1196 }
1197 
getRtAudioApiDeviceNames(const uint index)1198 const char* const* getRtAudioApiDeviceNames(const uint index)
1199 {
1200     initRtAudioAPIsIfNeeded();
1201 
1202     if (index >= gRtAudioApis.size())
1203         return nullptr;
1204 
1205     const RtAudio::Api& api(gRtAudioApis[index]);
1206     CarlaStringList devNames;
1207 
1208     try {
1209         RtAudio rtAudio(api);
1210 
1211         const uint devCount(rtAudio.getDeviceCount());
1212 
1213         if (devCount == 0)
1214             return nullptr;
1215 
1216         for (uint i=0; i < devCount; ++i)
1217         {
1218             RtAudio::DeviceInfo devInfo(rtAudio.getDeviceInfo(i));
1219 
1220             if (devInfo.probed && devInfo.outputChannels > 0 /*&& (devInfo.nativeFormats & RTAUDIO_FLOAT32) != 0*/)
1221                 devNames.append(devInfo.name.c_str());
1222         }
1223 
1224     } CARLA_SAFE_EXCEPTION_RETURN("RtAudio device names", nullptr);
1225 
1226     gDeviceNames = devNames.toCharStringListPtr();
1227 
1228     return gDeviceNames;
1229 }
1230 
getRtAudioDeviceInfo(const uint index,const char * const deviceName)1231 const EngineDriverDeviceInfo* getRtAudioDeviceInfo(const uint index, const char* const deviceName)
1232 {
1233     initRtAudioAPIsIfNeeded();
1234 
1235     if (index >= gRtAudioApis.size())
1236         return nullptr;
1237 
1238     static EngineDriverDeviceInfo devInfo = { 0x0, nullptr, nullptr };
1239     static uint32_t dummyBufferSizes[]    = { 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 0 };
1240     static double   dummySampleRates[]    = { 22050.0, 32000.0, 44100.0, 48000.0, 88200.0, 96000.0, 176400.0, 192000.0, 0.0 };
1241 
1242     // reset
1243     devInfo.hints = 0x0;
1244 
1245     // cleanup
1246     if (devInfo.bufferSizes != nullptr && devInfo.bufferSizes != dummyBufferSizes)
1247     {
1248         delete[] devInfo.bufferSizes;
1249         devInfo.bufferSizes = nullptr;
1250     }
1251     if (devInfo.sampleRates != nullptr && devInfo.sampleRates != dummySampleRates)
1252     {
1253         delete[] devInfo.sampleRates;
1254         devInfo.sampleRates = nullptr;
1255     }
1256 
1257     const RtAudio::Api& api(gRtAudioApis[index]);
1258 
1259     if (api == RtAudio::UNIX_JACK)
1260     {
1261         devInfo.bufferSizes = nullptr;
1262         devInfo.sampleRates = nullptr;
1263         return &devInfo;
1264     }
1265 
1266     RtAudio::DeviceInfo rtAudioDevInfo;
1267 
1268     try {
1269         RtAudio rtAudio(api);
1270 
1271         const uint devCount(rtAudio.getDeviceCount());
1272 
1273         if (devCount == 0)
1274             return nullptr;
1275 
1276         uint i;
1277         for (i=0; i < devCount; ++i)
1278         {
1279             rtAudioDevInfo = rtAudio.getDeviceInfo(i);
1280 
1281             if (rtAudioDevInfo.name == deviceName)
1282                 break;
1283         }
1284 
1285         if (i == devCount)
1286             rtAudioDevInfo = rtAudio.getDeviceInfo(rtAudio.getDefaultOutputDevice());
1287 
1288     } CARLA_SAFE_EXCEPTION_RETURN("RtAudio device discovery", nullptr);
1289 
1290     // a few APIs can do triple buffer
1291     switch (api)
1292     {
1293     case RtAudio::LINUX_ALSA:
1294     case RtAudio::LINUX_OSS:
1295     case RtAudio::WINDOWS_DS:
1296         devInfo.hints |= ENGINE_DRIVER_DEVICE_CAN_TRIPLE_BUFFER;
1297         break;
1298     default:
1299         break;
1300     }
1301 
1302     // always use default buffer sizes
1303     devInfo.bufferSizes = dummyBufferSizes;
1304 
1305     // valid sample rates
1306     if (const size_t sampleRatesCount = rtAudioDevInfo.sampleRates.size())
1307     {
1308         double* const sampleRates(new double[sampleRatesCount+1]);
1309 
1310         for (size_t i=0; i < sampleRatesCount; ++i)
1311             sampleRates[i] = rtAudioDevInfo.sampleRates[i];
1312         sampleRates[sampleRatesCount] = 0.0;
1313 
1314         devInfo.sampleRates = sampleRates;
1315     }
1316     else
1317     {
1318         devInfo.sampleRates = dummySampleRates;
1319     }
1320 
1321     return &devInfo;
1322 }
1323 
1324 }
1325 
1326 // -----------------------------------------
1327 
1328 CARLA_BACKEND_END_NAMESPACE
1329