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