DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

RtAudioBridge.hpp (13456B)


      1 /*
      2  * RtAudio Bridge for DPF
      3  * Copyright (C) 2021-2023 Filipe Coelho <falktx@falktx.com>
      4  *
      5  * Permission to use, copy, modify, and/or distribute this software for any purpose with
      6  * or without fee is hereby granted, provided that the above copyright notice and this
      7  * permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
     10  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
     11  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
     12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
     13  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
     14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #ifndef RTAUDIO_BRIDGE_HPP_INCLUDED
     18 #define RTAUDIO_BRIDGE_HPP_INCLUDED
     19 
     20 #include "NativeBridge.hpp"
     21 
     22 #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS == 0
     23 # error RtAudio without audio does not make sense
     24 #endif
     25 
     26 #if defined(DISTRHO_OS_MAC)
     27 # define __MACOSX_CORE__
     28 # define RTAUDIO_API_TYPE MACOSX_CORE
     29 # define RTMIDI_API_TYPE MACOSX_CORE
     30 #elif defined(DISTRHO_OS_WINDOWS) && !defined(_MSC_VER)
     31 # define __WINDOWS_WASAPI__
     32 # define __WINDOWS_MM__
     33 # define RTAUDIO_API_TYPE WINDOWS_WASAPI
     34 # define RTMIDI_API_TYPE WINDOWS_MM
     35 #else
     36 # if defined(HAVE_PULSEAUDIO)
     37 #  define __LINUX_PULSE__
     38 #  define RTAUDIO_API_TYPE LINUX_PULSE
     39 # elif defined(HAVE_ALSA)
     40 #  define RTAUDIO_API_TYPE LINUX_ALSA
     41 # endif
     42 # ifdef HAVE_ALSA
     43 #  define __LINUX_ALSA__
     44 #  define RTMIDI_API_TYPE LINUX_ALSA
     45 # endif
     46 #endif
     47 
     48 #ifdef RTAUDIO_API_TYPE
     49 # include "rtaudio/RtAudio.h"
     50 # include "rtmidi/RtMidi.h"
     51 # include "../../extra/ScopedPointer.hpp"
     52 # include "../../extra/String.hpp"
     53 # include "../../extra/ScopedDenormalDisable.hpp"
     54 
     55 using DISTRHO_NAMESPACE::ScopedDenormalDisable;
     56 using DISTRHO_NAMESPACE::ScopedPointer;
     57 using DISTRHO_NAMESPACE::String;
     58 
     59 struct RtAudioBridge : NativeBridge {
     60     // pointer to RtAudio instance
     61     ScopedPointer<RtAudio> handle;
     62     bool captureEnabled = false;
     63    #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
     64     std::vector<RtMidiIn> midiIns;
     65    #endif
     66    #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
     67     std::vector<RtMidiOut> midiOuts;
     68    #endif
     69 
     70     // caching
     71     String name;
     72     uint nextBufferSize = 512;
     73 
     74     RtAudioBridge()
     75     {
     76        #if defined(RTMIDI_API_TYPE) && (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT)
     77         midiAvailable = true;
     78        #endif
     79     }
     80 
     81     const char* getVersion() const noexcept
     82     {
     83         return RTAUDIO_VERSION;
     84     }
     85 
     86     bool open(const char* const clientName) override
     87     {
     88         name = clientName;
     89         return _open(false);
     90     }
     91 
     92     bool close() override
     93     {
     94         DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false);
     95 
     96         if (handle->isStreamRunning())
     97         {
     98             try {
     99                 handle->abortStream();
    100             } DISTRHO_SAFE_EXCEPTION("handle->abortStream()");
    101         }
    102 
    103        #if DISTRHO_PLUGIN_NUM_INPUTS > 0
    104         freeBuffers();
    105        #endif
    106         handle = nullptr;
    107         return true;
    108     }
    109 
    110     bool activate() override
    111     {
    112         DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false);
    113 
    114         try {
    115             handle->startStream();
    116         } DISTRHO_SAFE_EXCEPTION_RETURN("handle->startStream()", false);
    117 
    118         return true;
    119     }
    120 
    121     bool deactivate() override
    122     {
    123         DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false);
    124 
    125         try {
    126             handle->stopStream();
    127         } DISTRHO_SAFE_EXCEPTION_RETURN("handle->stopStream()", false);
    128 
    129         return true;
    130     }
    131 
    132     bool isAudioInputEnabled() const override
    133     {
    134        #if DISTRHO_PLUGIN_NUM_INPUTS > 0
    135         return captureEnabled;
    136        #else
    137         return false;
    138        #endif
    139     }
    140 
    141     bool requestAudioInput() override
    142     {
    143        #if DISTRHO_PLUGIN_NUM_INPUTS > 0
    144         // stop audio first
    145         deactivate();
    146         close();
    147 
    148         // try to open with capture enabled
    149         const bool ok = _open(true);
    150 
    151         if (ok)
    152             captureEnabled = true;
    153         else
    154             _open(false);
    155 
    156         activate();
    157         return ok;
    158        #else
    159         return false;
    160        #endif
    161     }
    162 
    163     bool isMIDIEnabled() const override
    164     {
    165        #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
    166         if (!midiIns.empty())
    167             return true;
    168        #endif
    169        #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    170         if (!midiOuts.empty())
    171             return true;
    172        #endif
    173         return false;
    174     }
    175 
    176     bool requestMIDI() override
    177     {
    178         d_stdout("%s %d", __PRETTY_FUNCTION__, __LINE__);
    179         // clear ports in use first
    180        #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
    181         if (!midiIns.empty())
    182         {
    183             try {
    184                 midiIns.clear();
    185             } catch (const RtMidiError& err) {
    186                 d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
    187                 return false;
    188             } DISTRHO_SAFE_EXCEPTION_RETURN("midiIns.clear()", false);
    189         }
    190        #endif
    191        #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    192         if (!midiOuts.size())
    193         {
    194             try {
    195                 midiOuts.clear();
    196             } catch (const RtMidiError& err) {
    197                 d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
    198                 return false;
    199             } DISTRHO_SAFE_EXCEPTION_RETURN("midiOuts.clear()", false);
    200         }
    201        #endif
    202 
    203         // query port count
    204        #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
    205         uint midiInCount;
    206         try {
    207             RtMidiIn midiIn(RtMidi::RTMIDI_API_TYPE, name.buffer());
    208             midiInCount = midiIn.getPortCount();
    209         } catch (const RtMidiError& err) {
    210             d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
    211             return false;
    212         } DISTRHO_SAFE_EXCEPTION_RETURN("midiIn.getPortCount()", false);
    213        #endif
    214        #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    215         uint midiOutCount;
    216         try {
    217             RtMidiOut midiOut(RtMidi::RTMIDI_API_TYPE, name.buffer());
    218             midiOutCount = midiOut.getPortCount();
    219         } catch (const RtMidiError& err) {
    220             d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
    221             return false;
    222         } DISTRHO_SAFE_EXCEPTION_RETURN("midiOut.getPortCount()", false);
    223        #endif
    224 
    225         // open all possible ports
    226        #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
    227         for (uint i=0; i<midiInCount; ++i)
    228         {
    229             try {
    230                 RtMidiIn midiIn(RtMidi::RTMIDI_API_TYPE, name.buffer());
    231                 midiIn.setCallback(RtMidiCallback, this);
    232                 midiIn.openPort(i);
    233                 midiIns.push_back(std::move(midiIn));
    234             } catch (const RtMidiError& err) {
    235                 d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
    236             } DISTRHO_SAFE_EXCEPTION("midiIn.openPort()");
    237         }
    238        #endif
    239        #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
    240         for (uint i=0; i<midiOutCount; ++i)
    241         {
    242             try {
    243                 RtMidiOut midiOut(RtMidi::RTMIDI_API_TYPE, name.buffer());
    244                 midiOut.openPort(i);
    245                 midiOuts.push_back(std::move(midiOut));
    246             } catch (const RtMidiError& err) {
    247                 d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
    248             } DISTRHO_SAFE_EXCEPTION("midiOut.openPort()");
    249         }
    250        #endif
    251 
    252         return true;
    253     }
    254 
    255     bool supportsBufferSizeChanges() const override
    256     {
    257         return true;
    258     }
    259 
    260     bool requestBufferSizeChange(const uint32_t newBufferSize) override
    261     {
    262         // stop audio first
    263         deactivate();
    264         close();
    265 
    266         // try to open with new buffer size
    267         nextBufferSize = newBufferSize;
    268 
    269         const bool ok = _open(captureEnabled);
    270 
    271         if (!ok)
    272         {
    273             // revert to old buffer size if new one failed
    274             nextBufferSize = bufferSize;
    275             _open(captureEnabled);
    276         }
    277 
    278         if (bufferSizeCallback != nullptr)
    279             bufferSizeCallback(bufferSize, jackBufferSizeArg);
    280 
    281         activate();
    282         return ok;
    283     }
    284 
    285     bool _open(const bool withInput, RtAudio* tryingAgain = nullptr)
    286     {
    287         ScopedPointer<RtAudio> rtAudio;
    288 
    289         if (tryingAgain == nullptr)
    290         {
    291             try {
    292                 rtAudio = new RtAudio(RtAudio::RTAUDIO_API_TYPE);
    293             } DISTRHO_SAFE_EXCEPTION_RETURN("new RtAudio()", false);
    294         }
    295         else
    296         {
    297             rtAudio = tryingAgain;
    298         }
    299 
    300         uint rtAudioBufferFrames = nextBufferSize;
    301 
    302        #if DISTRHO_PLUGIN_NUM_INPUTS > 0
    303         RtAudio::StreamParameters inParams;
    304        #endif
    305         RtAudio::StreamParameters* inParamsPtr = nullptr;
    306 
    307        #if DISTRHO_PLUGIN_NUM_INPUTS > 0
    308         if (withInput)
    309         {
    310             inParams.deviceId = rtAudio->getDefaultInputDevice();
    311             inParams.nChannels = DISTRHO_PLUGIN_NUM_INPUTS_2;
    312             inParamsPtr = &inParams;
    313         }
    314        #endif
    315 
    316        #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
    317         RtAudio::StreamParameters outParams;
    318         outParams.deviceId = tryingAgain != nullptr ? 1 : rtAudio->getDefaultOutputDevice();
    319         outParams.nChannels = DISTRHO_PLUGIN_NUM_OUTPUTS_2;
    320         RtAudio::StreamParameters* const outParamsPtr = &outParams;
    321        #else
    322         RtAudio::StreamParameters* const outParamsPtr = nullptr;
    323        #endif
    324 
    325         RtAudio::StreamOptions opts;
    326         opts.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_ALSA_USE_DEFAULT;
    327        #ifndef DISTRHO_OS_MAC
    328        /* RtAudio in macOS uses a different than usual way to handle audio block size,
    329         * where RTAUDIO_MINIMIZE_LATENCY makes CoreAudio use very low latencies (around 15 samples).
    330         * That has serious performance drawbacks, so we skip that here.
    331         */
    332         opts.flags |= RTAUDIO_MINIMIZE_LATENCY;
    333        #endif
    334         opts.numberOfBuffers = 2;
    335         opts.streamName = name.buffer();
    336 
    337         try {
    338             rtAudio->openStream(outParamsPtr, inParamsPtr, RTAUDIO_FLOAT32, 48000, &rtAudioBufferFrames,
    339                                 RtAudioCallback, this, &opts, nullptr);
    340         } catch (const RtAudioError& err) {
    341            #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
    342             if (outParams.deviceId == 0 && rtAudio->getDeviceCount() > 1)
    343                 return _open(withInput, rtAudio.release());
    344            #endif
    345             d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__);
    346             return false;
    347         } DISTRHO_SAFE_EXCEPTION_RETURN("rtAudio->openStream()", false);
    348 
    349         handle = rtAudio;
    350         bufferSize = rtAudioBufferFrames;
    351         sampleRate = handle->getStreamSampleRate();
    352         allocBuffers(!withInput, true);
    353         return true;
    354     }
    355 
    356     static int RtAudioCallback(void* const outputBuffer,
    357                               #if DISTRHO_PLUGIN_NUM_INPUTS > 0
    358                                void* const inputBuffer,
    359                               #else
    360                                void*,
    361                               #endif
    362                                const uint numFrames,
    363                                const double /* streamTime */,
    364                                const RtAudioStreamStatus /* status */,
    365                                void* const userData)
    366     {
    367         RtAudioBridge* const self = static_cast<RtAudioBridge*>(userData);
    368 
    369         if (self->jackProcessCallback == nullptr)
    370         {
    371             if (outputBuffer != nullptr)
    372                 std::memset((float*)outputBuffer, 0, sizeof(float)*numFrames*DISTRHO_PLUGIN_NUM_OUTPUTS_2);
    373             return 0;
    374         }
    375 
    376        #if DISTRHO_PLUGIN_NUM_INPUTS > 0
    377         if (float* const insPtr = static_cast<float*>(inputBuffer))
    378         {
    379             for (uint i=0; i<DISTRHO_PLUGIN_NUM_INPUTS_2; ++i)
    380                 self->audioBuffers[i] = insPtr + (i * numFrames);
    381         }
    382        #endif
    383 
    384        #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0
    385         if (float* const outsPtr = static_cast<float*>(outputBuffer))
    386         {
    387             for (uint i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS_2; ++i)
    388                 self->audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + i] = outsPtr + (i * numFrames);
    389         }
    390        #endif
    391 
    392         const ScopedDenormalDisable sdd;
    393         self->jackProcessCallback(numFrames, self->jackProcessArg);
    394 
    395         return 0;
    396     }
    397 
    398    #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT
    399     static void RtMidiCallback(double /*timestamp*/, std::vector<uchar>* const message, void* const userData)
    400     {
    401         const size_t len = message->size();
    402         DISTRHO_SAFE_ASSERT_RETURN(len > 0 && len <= kMaxMIDIInputMessageSize,);
    403 
    404         RtAudioBridge* const self = static_cast<RtAudioBridge*>(userData);
    405 
    406         self->midiInBufferPending.writeByte(static_cast<uint8_t>(len));
    407         // TODO timestamp
    408         // self->midiInBufferPending.writeDouble(timestamp);
    409         self->midiInBufferPending.writeCustomData(message->data(), len);
    410         for (uint8_t i=len; i<kMaxMIDIInputMessageSize; ++i)
    411             self->midiInBufferPending.writeByte(0);
    412         self->midiInBufferPending.commitWrite();
    413     }
    414    #endif
    415 };
    416 
    417 #endif // RTAUDIO_API_TYPE
    418 #endif // RTAUDIO_BRIDGE_HPP_INCLUDED