Signed-off-by: falkTX <falktx@falktx.com>pull/321/head
| @@ -324,9 +324,9 @@ endif | |||
| HAVE_LIBLO = $(shell $(PKG_CONFIG) --exists liblo && echo true) | |||
| ifeq ($(SKIP_RTAUDIO_FALLBACK),true) | |||
| CXXFLAGS += -DDPF_JACK_STANDALONE_SKIP_RTAUDIO_FALLBACK | |||
| else | |||
| ifneq ($(SKIP_NATIVE_AUDIO_FALLBACK),true) | |||
| ifneq ($(SKIP_RTAUDIO_FALLBACK),true) | |||
| ifeq ($(MACOS),true) | |||
| HAVE_RTAUDIO = true | |||
| else ifeq ($(WINDOWS),true) | |||
| @@ -334,11 +334,14 @@ HAVE_RTAUDIO = true | |||
| else | |||
| HAVE_ALSA = $(shell $(PKG_CONFIG) --exists alsa && echo true) | |||
| HAVE_PULSEAUDIO = $(shell $(PKG_CONFIG) --exists libpulse-simple && echo true) | |||
| HAVE_SDL2 = $(shell $(PKG_CONFIG) --exists sdl2 && echo true) | |||
| ifeq ($(HAVE_ALSA),true) | |||
| HAVE_RTAUDIO = true | |||
| else ifeq ($(HAVE_PULSEAUDIO),true) | |||
| HAVE_RTAUDIO = true | |||
| endif | |||
| endif | |||
| endif | |||
| endif | |||
| @@ -466,6 +469,11 @@ PULSEAUDIO_FLAGS = $(shell $(PKG_CONFIG) --cflags libpulse-simple) | |||
| PULSEAUDIO_LIBS = $(shell $(PKG_CONFIG) --libs libpulse-simple) | |||
| endif | |||
| ifeq ($(HAVE_SDL2),true) | |||
| SDL2_FLAGS = $(shell $(PKG_CONFIG) --cflags sdl2) | |||
| SDL2_LIBS = $(shell $(PKG_CONFIG) --libs sdl2) | |||
| endif | |||
| ifeq ($(HAVE_JACK),true) | |||
| ifeq ($(STATIC_BUILD),true) | |||
| JACK_FLAGS = $(shell $(PKG_CONFIG) --cflags jack) | |||
| @@ -627,6 +635,7 @@ features: | |||
| $(call print_available,HAVE_OPENGL) | |||
| $(call print_available,HAVE_PULSEAUDIO) | |||
| $(call print_available,HAVE_RTAUDIO) | |||
| $(call print_available,HAVE_SDL2) | |||
| $(call print_available,HAVE_STUB) | |||
| $(call print_available,HAVE_VULKAN) | |||
| $(call print_available,HAVE_X11) | |||
| @@ -50,6 +50,14 @@ ifeq ($(HAVE_PULSEAUDIO),true) | |||
| BASE_FLAGS += -DHAVE_PULSEAUDIO | |||
| endif | |||
| ifeq ($(HAVE_RTAUDIO),true) | |||
| BASE_FLAGS += -DHAVE_RTAUDIO | |||
| endif | |||
| ifeq ($(HAVE_SDL2),true) | |||
| BASE_FLAGS += -DHAVE_SDL2 | |||
| endif | |||
| # always needed | |||
| ifneq ($(HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS),true) | |||
| ifneq ($(STATIC_BUILD),true) | |||
| @@ -94,6 +102,11 @@ JACK_LIBS += -lpthread | |||
| endif | |||
| endif | |||
| ifeq ($(HAVE_SDL2),true) | |||
| JACK_FLAGS += $(SDL2_FLAGS) | |||
| JACK_LIBS += $(SDL2_LIBS) | |||
| endif | |||
| endif | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| @@ -203,11 +203,23 @@ public: | |||
| /* | |||
| * Get the size of the data available to read. | |||
| */ | |||
| uint32_t getAvailableDataSize() const noexcept | |||
| uint32_t getReadableDataSize() const noexcept | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, 0); | |||
| const uint32_t wrap((buffer->tail > buffer->wrtn) ? 0 : buffer->size); | |||
| const uint32_t wrap = buffer->head > buffer->tail ? 0 : buffer->size; | |||
| return wrap + buffer->head - buffer->tail; | |||
| } | |||
| /* | |||
| * Get the size of the data available to write. | |||
| */ | |||
| uint32_t getWritableDataSize() const noexcept | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, 0); | |||
| const uint32_t wrap = (buffer->tail > buffer->wrtn) ? 0 : buffer->size; | |||
| return wrap + buffer->tail - buffer->wrtn; | |||
| } | |||
| @@ -724,6 +736,15 @@ public: | |||
| heapBuffer.size = 0; | |||
| } | |||
| void copyFromAndClearOther(HeapRingBuffer& other) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(other.heapBuffer.size == heapBuffer.size,); | |||
| std::memcpy(&heapBuffer, &other.heapBuffer, sizeof(HeapBuffer) - sizeof(uint8_t*)); | |||
| std::memcpy(heapBuffer.buf, other.heapBuffer.buf, sizeof(uint8_t) * heapBuffer.size); | |||
| other.clearData(); | |||
| } | |||
| private: | |||
| /** The heap buffer used for this class. */ | |||
| HeapBuffer heapBuffer; | |||
| @@ -0,0 +1,213 @@ | |||
| /* | |||
| * Native Bridge for DPF | |||
| * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| * permission notice appear in all copies. | |||
| * | |||
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| #ifndef NATIVE_BRIDGE_HPP_INCLUDED | |||
| #define NATIVE_BRIDGE_HPP_INCLUDED | |||
| #include "JackBridge.hpp" | |||
| #include "../../extra/RingBuffer.hpp" | |||
| using DISTRHO_NAMESPACE::HeapRingBuffer; | |||
| struct NativeBridge { | |||
| // Current status information | |||
| uint bufferSize = 0; | |||
| uint sampleRate = 0; | |||
| // Port caching information | |||
| uint numAudioIns = 0; | |||
| uint numAudioOuts = 0; | |||
| uint numMidiIns = 0; | |||
| uint numMidiOuts = 0; | |||
| // JACK callbacks | |||
| JackProcessCallback jackProcessCallback = nullptr; | |||
| void* jackProcessArg = nullptr; | |||
| // Runtime buffers | |||
| enum PortMask { | |||
| kPortMaskAudio = 0x1000, | |||
| kPortMaskMIDI = 0x2000, | |||
| kPortMaskInput = 0x4000, | |||
| kPortMaskOutput = 0x8000, | |||
| kPortMaskInputMIDI = kPortMaskInput|kPortMaskMIDI, | |||
| kPortMaskOutputMIDI = kPortMaskOutput|kPortMaskMIDI, | |||
| }; | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| float* audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS] = {}; | |||
| float* audioBufferStorage = nullptr; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| static constexpr const uint32_t kMaxMIDIInputMessageSize = 3; | |||
| uint8_t midiDataStorage[kMaxMIDIInputMessageSize]; | |||
| HeapRingBuffer midiInBufferCurrent; | |||
| HeapRingBuffer midiInBufferPending; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| HeapRingBuffer midiOutBuffer; | |||
| #endif | |||
| virtual ~NativeBridge() {} | |||
| virtual bool open(const char* const clientName) = 0; | |||
| virtual bool close() = 0; | |||
| virtual bool activate() = 0; | |||
| virtual bool deactivate() = 0; | |||
| uint32_t getEventCount() | |||
| { | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| // NOTE: this function is only called once per run | |||
| midiInBufferCurrent.copyFromAndClearOther(midiInBufferPending); | |||
| return midiInBufferCurrent.getReadableDataSize() / (kMaxMIDIInputMessageSize + 1u); | |||
| #else | |||
| return 0; | |||
| #endif | |||
| } | |||
| bool getEvent(jack_midi_event_t* const event) | |||
| { | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| // NOTE: this function is called for all events in index succession | |||
| if (midiInBufferCurrent.getReadableDataSize() >= (kMaxMIDIInputMessageSize + 1u)) | |||
| { | |||
| event->time = 0; // TODO | |||
| event->size = midiInBufferCurrent.readByte(); | |||
| event->buffer = midiDataStorage; | |||
| return midiInBufferCurrent.readCustomData(midiDataStorage, kMaxMIDIInputMessageSize); | |||
| } | |||
| #endif | |||
| return false; | |||
| } | |||
| void clearEventBuffer() | |||
| { | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| midiOutBuffer.clearData(); | |||
| #endif | |||
| } | |||
| bool writeEvent(const jack_nframes_t time, const jack_midi_data_t* const data, const uint32_t size) | |||
| { | |||
| if (size > 3) | |||
| return false; | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| if (midiOutBuffer.writeByte(size) && midiOutBuffer.writeCustomData(data, size)) | |||
| { | |||
| bool fail = false; | |||
| // align | |||
| switch (size) | |||
| { | |||
| case 1: fail |= !midiOutBuffer.writeByte(0); | |||
| // fall-through | |||
| case 2: fail |= !midiOutBuffer.writeByte(0); | |||
| } | |||
| fail |= !midiOutBuffer.writeUInt(time); | |||
| midiOutBuffer.commitWrite(); | |||
| return !fail; | |||
| } | |||
| midiOutBuffer.commitWrite(); | |||
| #endif | |||
| return false; | |||
| } | |||
| void allocBuffers() | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(bufferSize != 0,); | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| audioBufferStorage = new float[bufferSize*(DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS)]; | |||
| for (uint i=0; i<DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) | |||
| audioBuffers[i] = audioBufferStorage + (bufferSize * i); | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| std::memset(audioBufferStorage, 0, sizeof(float)*bufferSize*DISTRHO_PLUGIN_NUM_INPUTS); | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| midiInBufferCurrent.createBuffer(kMaxMIDIInputMessageSize * 512); | |||
| midiInBufferPending.createBuffer(kMaxMIDIInputMessageSize * 512); | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| midiOutBuffer.createBuffer(2048); | |||
| #endif | |||
| } | |||
| void freeBuffers() | |||
| { | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| delete[] audioBufferStorage; | |||
| audioBufferStorage = nullptr; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| midiInBufferCurrent.deleteBuffer(); | |||
| midiInBufferPending.deleteBuffer(); | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| midiOutBuffer.deleteBuffer(); | |||
| #endif | |||
| } | |||
| jack_port_t* registerPort(const char* const type, const ulong flags) | |||
| { | |||
| bool isAudio, isInput; | |||
| /**/ if (std::strcmp(type, JACK_DEFAULT_AUDIO_TYPE) == 0) | |||
| isAudio = true; | |||
| else if (std::strcmp(type, JACK_DEFAULT_MIDI_TYPE) == 0) | |||
| isAudio = false; | |||
| else | |||
| return nullptr; | |||
| /**/ if (flags & JackPortIsInput) | |||
| isInput = true; | |||
| else if (flags & JackPortIsOutput) | |||
| isInput = false; | |||
| else | |||
| return nullptr; | |||
| const uintptr_t ret = (isAudio ? kPortMaskAudio : kPortMaskMIDI) | |||
| | (isInput ? kPortMaskInput : kPortMaskOutput); | |||
| return (jack_port_t*)(ret + (isAudio ? (isInput ? numAudioIns++ : numAudioOuts++) | |||
| : (isInput ? numMidiIns++ : numMidiOuts++))); | |||
| } | |||
| void* getPortBuffer(jack_port_t* const port) | |||
| { | |||
| const uintptr_t portMask = (uintptr_t)port; | |||
| DISTRHO_SAFE_ASSERT_RETURN(portMask != 0x0, nullptr); | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| if (portMask & kPortMaskAudio) | |||
| return audioBuffers[(portMask & kPortMaskInput ? 0 : DISTRHO_PLUGIN_NUM_INPUTS) + (portMask & 0x0fff)]; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| if ((portMask & kPortMaskInputMIDI) == kPortMaskInputMIDI) | |||
| return (void*)0x1; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| if ((portMask & kPortMaskOutputMIDI) == kPortMaskOutputMIDI) | |||
| return (void*)0x2; | |||
| #endif | |||
| return nullptr; | |||
| } | |||
| }; | |||
| #endif // NATIVE_BRIDGE_HPP_INCLUDED | |||
| @@ -1,5 +1,5 @@ | |||
| /* | |||
| * RtAudioBridge for DPF | |||
| * RtAudio Bridge for DPF | |||
| * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| @@ -14,10 +14,14 @@ | |||
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| #ifndef RTAUDIOBRIDGE_HPP_INCLUDED | |||
| #define RTAUDIOBRIDGE_HPP_INCLUDED | |||
| #ifndef RTAUDIO_BRIDGE_HPP_INCLUDED | |||
| #define RTAUDIO_BRIDGE_HPP_INCLUDED | |||
| #include "JackBridge.hpp" | |||
| #include "NativeBridge.hpp" | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS == 0 | |||
| # error RtAudio without audio does not make sense | |||
| #endif | |||
| #if defined(DISTRHO_OS_MAC) | |||
| # define __MACOSX_CORE__ | |||
| @@ -37,50 +41,20 @@ | |||
| # define Point CorePoint /* fix conflict between DGL and macOS Point name */ | |||
| # include "rtaudio/RtAudio.h" | |||
| # undef Point | |||
| # include "../../extra/RingBuffer.hpp" | |||
| # include "../../extra/ScopedPointer.hpp" | |||
| using DISTRHO_NAMESPACE::HeapRingBuffer; | |||
| using DISTRHO_NAMESPACE::ScopedPointer; | |||
| struct RtAudioBridge { | |||
| struct RtAudioBridge : NativeBridge { | |||
| // pointer to RtAudio instance | |||
| ScopedPointer<RtAudio> handle; | |||
| // RtAudio information | |||
| uint bufferSize = 0; | |||
| uint sampleRate = 0; | |||
| // Port caching information | |||
| uint numAudioIns = 0; | |||
| uint numAudioOuts = 0; | |||
| uint numMidiIns = 0; | |||
| uint numMidiOuts = 0; | |||
| // JACK callbacks | |||
| JackProcessCallback jackProcessCallback = nullptr; | |||
| void* jackProcessArg = nullptr; | |||
| // Runtime buffers | |||
| enum PortMask { | |||
| kPortMaskAudio = 0x1000, | |||
| kPortMaskMIDI = 0x2000, | |||
| kPortMaskInput = 0x4000, | |||
| kPortMaskOutput = 0x8000, | |||
| kPortMaskInputMIDI = kPortMaskInput|kPortMaskMIDI, | |||
| kPortMaskOutputMIDI = kPortMaskOutput|kPortMaskMIDI, | |||
| }; | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| float* audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS]; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| HeapRingBuffer midiInBuffer; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| HeapRingBuffer midiOutBuffer; | |||
| #endif | |||
| const char* getVersion() const noexcept | |||
| { | |||
| return RTAUDIO_VERSION; | |||
| } | |||
| bool open() | |||
| bool open(const char* const clientName) override | |||
| { | |||
| ScopedPointer<RtAudio> rtAudio; | |||
| @@ -90,24 +64,31 @@ struct RtAudioBridge { | |||
| uint rtAudioBufferFrames = 512; | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| RtAudio::StreamParameters inParams; | |||
| RtAudio::StreamParameters* const inParamsPtr = &inParams; | |||
| inParams.deviceId = rtAudio->getDefaultInputDevice(); | |||
| inParams.nChannels = DISTRHO_PLUGIN_NUM_INPUTS; | |||
| #else | |||
| RtAudio::StreamParameters* const inParamsPtr = &inParams; | |||
| #else | |||
| RtAudio::StreamParameters* const inParamsPtr = nullptr; | |||
| #endif | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| RtAudio::StreamParameters outParams; | |||
| outParams.deviceId = rtAudio->getDefaultOutputDevice(); | |||
| outParams.nChannels = DISTRHO_PLUGIN_NUM_OUTPUTS; | |||
| RtAudio::StreamParameters* const outParamsPtr = &outParams; | |||
| #else | |||
| RtAudio::StreamParameters* const outParamsPtr = nullptr; | |||
| #endif | |||
| RtAudio::StreamOptions opts; | |||
| opts.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_MINIMIZE_LATENCY | RTAUDIO_ALSA_USE_DEFAULT; | |||
| opts.streamName = clientName; | |||
| try { | |||
| rtAudio->openStream(&outParams, inParamsPtr, RTAUDIO_FLOAT32, 48000, &rtAudioBufferFrames, RtAudioCallback, this, &opts, nullptr); | |||
| rtAudio->openStream(outParamsPtr, inParamsPtr, RTAUDIO_FLOAT32, 48000, &rtAudioBufferFrames, | |||
| RtAudioCallback, this, &opts, nullptr); | |||
| } catch (const RtAudioError& err) { | |||
| d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__); | |||
| return false; | |||
| @@ -116,17 +97,10 @@ struct RtAudioBridge { | |||
| handle = rtAudio; | |||
| bufferSize = rtAudioBufferFrames; | |||
| sampleRate = handle->getStreamSampleRate(); | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| midiInBuffer.createBuffer(128); | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| midiOutBuffer.createBuffer(128); | |||
| #endif | |||
| return true; | |||
| } | |||
| bool close() | |||
| bool close() override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false); | |||
| @@ -141,82 +115,40 @@ struct RtAudioBridge { | |||
| return true; | |||
| } | |||
| bool activate() | |||
| bool activate() override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false); | |||
| try { | |||
| handle->startStream(); | |||
| } DISTRHO_SAFE_EXCEPTION("handle->startStream()"); | |||
| } DISTRHO_SAFE_EXCEPTION_RETURN("handle->startStream()", false); | |||
| return true; | |||
| } | |||
| bool deactivate() | |||
| bool deactivate() override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false); | |||
| try { | |||
| handle->stopStream(); | |||
| } DISTRHO_SAFE_EXCEPTION("handle->stopStream()"); | |||
| } DISTRHO_SAFE_EXCEPTION_RETURN("handle->stopStream()", false); | |||
| return true; | |||
| } | |||
| jack_port_t* registerPort(const char* const type, const ulong flags) | |||
| { | |||
| bool isAudio, isInput; | |||
| /**/ if (std::strcmp(type, JACK_DEFAULT_AUDIO_TYPE) == 0) | |||
| isAudio = true; | |||
| else if (std::strcmp(type, JACK_DEFAULT_MIDI_TYPE) == 0) | |||
| isAudio = false; | |||
| else | |||
| return nullptr; | |||
| /**/ if (flags & JackPortIsInput) | |||
| isInput = true; | |||
| else if (flags & JackPortIsOutput) | |||
| isInput = false; | |||
| else | |||
| return nullptr; | |||
| const uintptr_t ret = (isAudio ? kPortMaskAudio : kPortMaskMIDI) | |||
| | (isInput ? kPortMaskInput : kPortMaskOutput); | |||
| return (jack_port_t*)(ret + (isAudio ? (isInput ? numAudioIns++ : numAudioOuts++) | |||
| : (isInput ? numMidiIns++ : numMidiOuts++))); | |||
| } | |||
| void* getPortBuffer(jack_port_t* const port) | |||
| { | |||
| const uintptr_t portMask = (uintptr_t)port; | |||
| DISTRHO_SAFE_ASSERT_RETURN(portMask != 0x0, nullptr); | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| if (portMask & kPortMaskAudio) | |||
| return audioBuffers[(portMask & kPortMaskInput ? 0 : DISTRHO_PLUGIN_NUM_INPUTS) + (portMask & 0x0fff)]; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| if ((portMask & kPortMaskInputMIDI) == kPortMaskInputMIDI) | |||
| return &midiInBuffer; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| if ((portMask & kPortMaskOutputMIDI) == kPortMaskOutputMIDI) | |||
| return &midiOutBuffer; | |||
| #endif | |||
| return nullptr; | |||
| } | |||
| static int RtAudioCallback(void* const outputBuffer, | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| void* const inputBuffer, | |||
| #else | |||
| void*, | |||
| #endif | |||
| const uint numFrames, | |||
| const double /* streamTime */, | |||
| const RtAudioStreamStatus /* status */, | |||
| void* const userData) | |||
| { | |||
| RtAudioBridge* const self = (RtAudioBridge*)userData; | |||
| RtAudioBridge* const self = static_cast<RtAudioBridge*>(userData); | |||
| if (self->jackProcessCallback == nullptr) | |||
| { | |||
| @@ -225,35 +157,27 @@ struct RtAudioBridge { | |||
| return 0; | |||
| } | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| float** const selfAudioBuffers = self->audioBuffers; | |||
| uint i = 0; | |||
| # if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| if (float* const insPtr = (float*)inputBuffer) | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| if (float* const insPtr = static_cast<float*>(inputBuffer)) | |||
| { | |||
| for (uint j=0; j<DISTRHO_PLUGIN_NUM_INPUTS; ++j, ++i) | |||
| selfAudioBuffers[i] = insPtr + (j * numFrames); | |||
| for (uint i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) | |||
| self->audioBuffers[i] = insPtr + (i * numFrames); | |||
| } | |||
| # endif | |||
| # if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| if (float* const outsPtr = (float*)outputBuffer) | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| if (float* const outsPtr = static_cast<float*>(outputBuffer)) | |||
| { | |||
| for (uint j=0; j<DISTRHO_PLUGIN_NUM_OUTPUTS; ++j, ++i) | |||
| selfAudioBuffers[i] = outsPtr + (j * numFrames); | |||
| for (uint i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) | |||
| self->audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + i] = outsPtr + (i * numFrames); | |||
| } | |||
| # endif | |||
| #endif | |||
| #endif | |||
| self->jackProcessCallback(numFrames, self->jackProcessArg); | |||
| return 0; | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS == 0 | |||
| // unused | |||
| (void)inputBuffer; | |||
| #endif | |||
| return 0; | |||
| } | |||
| }; | |||
| #endif // RTAUDIO_API_TYPE | |||
| #endif // RTAUDIOBRIDGE_HPP_INCLUDED | |||
| #endif // RTAUDIO_BRIDGE_HPP_INCLUDED | |||
| @@ -1,5 +1,5 @@ | |||
| /* | |||
| * SDLBridge for DPF | |||
| * SDL Bridge for DPF | |||
| * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| @@ -14,15 +14,18 @@ | |||
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| #ifndef SDLBRIDGE_HPP_INCLUDED | |||
| #define SDLBRIDGE_HPP_INCLUDED | |||
| #ifndef SDL_BRIDGE_HPP_INCLUDED | |||
| #define SDL_BRIDGE_HPP_INCLUDED | |||
| #include "JackBridge.hpp" | |||
| #include "../../extra/RingBuffer.hpp" | |||
| #include "NativeBridge.hpp" | |||
| #include <SDL.h> | |||
| struct SDLBridge { | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS == 0 | |||
| # error SDL without audio does not make sense | |||
| #endif | |||
| struct SDL2Bridge : NativeBridge { | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| SDL_AudioDeviceID captureDeviceId = 0; | |||
| #endif | |||
| @@ -30,43 +33,8 @@ struct SDLBridge { | |||
| SDL_AudioDeviceID playbackDeviceId = 0; | |||
| #endif | |||
| // SDL information | |||
| uint bufferSize = 0; | |||
| uint sampleRate = 0; | |||
| // Port caching information | |||
| uint numAudioIns = 0; | |||
| uint numAudioOuts = 0; | |||
| uint numMidiIns = 0; | |||
| uint numMidiOuts = 0; | |||
| // JACK callbacks | |||
| JackProcessCallback jackProcessCallback = nullptr; | |||
| void* jackProcessArg = nullptr; | |||
| // Runtime buffers | |||
| enum PortMask { | |||
| kPortMaskAudio = 0x1000, | |||
| kPortMaskMIDI = 0x2000, | |||
| kPortMaskInput = 0x4000, | |||
| kPortMaskOutput = 0x8000, | |||
| kPortMaskInputMIDI = kPortMaskInput|kPortMaskMIDI, | |||
| kPortMaskOutputMIDI = kPortMaskOutput|kPortMaskMIDI, | |||
| }; | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| float* audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS]; | |||
| float* audioBufferStorage; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| HeapRingBuffer midiInBuffer; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| HeapRingBuffer midiOutBuffer; | |||
| #endif | |||
| bool open(const char* const clientName) | |||
| bool open(const char* const clientName) override | |||
| { | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| SDL_InitSubSystem(SDL_INIT_AUDIO); | |||
| SDL_AudioSpec requested; | |||
| @@ -78,7 +46,6 @@ struct SDLBridge { | |||
| SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, clientName); | |||
| // SDL_SetHint(SDL_HINT_AUDIO_RESAMPLING_MODE, "1"); | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| SDL_SetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME, "Capure"); | |||
| @@ -144,130 +111,67 @@ struct SDLBridge { | |||
| d_stderr2("Mismatch sample rate %u vs %u", receivedCapture.freq, receivedCapture.freq); | |||
| return false; | |||
| } | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| bufferSize = receivedCapture.samples; | |||
| sampleRate = receivedCapture.freq; | |||
| #elif DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| bufferSize = receivedCapture.samples; | |||
| sampleRate = receivedCapture.freq; | |||
| #elif DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| #else | |||
| bufferSize = receivedPlayback.samples; | |||
| sampleRate = receivedPlayback.freq; | |||
| #else | |||
| d_stderr2("SDL without audio, unsupported for now"); | |||
| return false; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| audioBufferStorage = new float[bufferSize*(DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS)]; | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| std::memset(audioBufferStorage, 0, sizeof(float)*bufferSize*DISTRHO_PLUGIN_NUM_INPUTS); | |||
| #endif | |||
| for (uint i=0; i<DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) | |||
| audioBuffers[i] = audioBufferStorage + (bufferSize * i); | |||
| #endif | |||
| allocBuffers(); | |||
| return true; | |||
| } | |||
| bool close() | |||
| bool close() override | |||
| { | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| DISTRHO_SAFE_ASSERT_RETURN(captureDeviceId != 0, false); | |||
| SDL_CloseAudioDevice(captureDeviceId); | |||
| captureDeviceId = 0; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| DISTRHO_SAFE_ASSERT_RETURN(playbackDeviceId != 0, false); | |||
| SDL_CloseAudioDevice(playbackDeviceId); | |||
| playbackDeviceId = 0; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| delete[] audioBufferStorage; | |||
| audioBufferStorage = nullptr; | |||
| #endif | |||
| #endif | |||
| freeBuffers(); | |||
| return true; | |||
| } | |||
| bool activate() | |||
| bool activate() override | |||
| { | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| DISTRHO_SAFE_ASSERT_RETURN(captureDeviceId != 0, false); | |||
| SDL_PauseAudioDevice(captureDeviceId, 0); | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| DISTRHO_SAFE_ASSERT_RETURN(playbackDeviceId != 0, false); | |||
| SDL_PauseAudioDevice(playbackDeviceId, 0); | |||
| #endif | |||
| #endif | |||
| return true; | |||
| } | |||
| bool deactivate() | |||
| bool deactivate() override | |||
| { | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| DISTRHO_SAFE_ASSERT_RETURN(captureDeviceId != 0, false); | |||
| SDL_PauseAudioDevice(captureDeviceId, 1); | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| DISTRHO_SAFE_ASSERT_RETURN(playbackDeviceId != 0, false); | |||
| SDL_PauseAudioDevice(playbackDeviceId, 1); | |||
| #endif | |||
| #endif | |||
| return true; | |||
| } | |||
| jack_port_t* registerPort(const char* const type, const ulong flags) | |||
| { | |||
| bool isAudio, isInput; | |||
| /**/ if (std::strcmp(type, JACK_DEFAULT_AUDIO_TYPE) == 0) | |||
| isAudio = true; | |||
| else if (std::strcmp(type, JACK_DEFAULT_MIDI_TYPE) == 0) | |||
| isAudio = false; | |||
| else | |||
| return nullptr; | |||
| /**/ if (flags & JackPortIsInput) | |||
| isInput = true; | |||
| else if (flags & JackPortIsOutput) | |||
| isInput = false; | |||
| else | |||
| return nullptr; | |||
| const uintptr_t ret = (isAudio ? kPortMaskAudio : kPortMaskMIDI) | |||
| | (isInput ? kPortMaskInput : kPortMaskOutput); | |||
| return (jack_port_t*)(ret + (isAudio ? (isInput ? numAudioIns++ : numAudioOuts++) | |||
| : (isInput ? numMidiIns++ : numMidiOuts++))); | |||
| } | |||
| void* getPortBuffer(jack_port_t* const port) | |||
| { | |||
| const uintptr_t portMask = (uintptr_t)port; | |||
| DISTRHO_SAFE_ASSERT_RETURN(portMask != 0x0, nullptr); | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| if (portMask & kPortMaskAudio) | |||
| return audioBuffers[(portMask & kPortMaskInput ? 0 : DISTRHO_PLUGIN_NUM_INPUTS) + (portMask & 0x0fff)]; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| if ((portMask & kPortMaskInputMIDI) == kPortMaskInputMIDI) | |||
| return &midiInBuffer; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| if ((portMask & kPortMaskOutputMIDI) == kPortMaskOutputMIDI) | |||
| return &midiOutBuffer; | |||
| #endif | |||
| return nullptr; | |||
| } | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| static void AudioInputCallback(void* const userData, uchar* const stream, const int len) | |||
| { | |||
| SDLBridge* const self = (SDLBridge*)userData; | |||
| NativeBridge* const self = static_cast<NativeBridge*>(userData); | |||
| // safety checks | |||
| DISTRHO_SAFE_ASSERT_RETURN(stream != nullptr,); | |||
| @@ -287,14 +191,17 @@ struct SDLBridge { | |||
| self->audioBuffers[i][j] = fstream[j * DISTRHO_PLUGIN_NUM_INPUTS + i]; | |||
| } | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS == 0 | |||
| // if there are no outputs, run process callback now | |||
| self->jackProcessCallback(numFrames, self->jackProcessArg); | |||
| #endif | |||
| } | |||
| #endif | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| static void AudioOutputCallback(void* const userData, uchar* const stream, const int len) | |||
| { | |||
| SDLBridge* const self = (SDLBridge*)userData; | |||
| NativeBridge* const self = static_cast<NativeBridge*>(userData); | |||
| // safety checks | |||
| DISTRHO_SAFE_ASSERT_RETURN(stream != nullptr,); | |||
| @@ -309,10 +216,7 @@ struct SDLBridge { | |||
| const uint numFrames = static_cast<uint>(len / sizeof(float) / DISTRHO_PLUGIN_NUM_OUTPUTS); | |||
| DISTRHO_SAFE_ASSERT_UINT2_RETURN(numFrames == self->bufferSize, numFrames, self->bufferSize,); | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS == 0 | |||
| // if there are no inputs, run process callback now | |||
| self->jackProcessCallback(numFrames, self->jackProcessArg); | |||
| #endif | |||
| float* const fstream = (float*)stream; | |||
| @@ -322,7 +226,7 @@ struct SDLBridge { | |||
| fstream[j * DISTRHO_PLUGIN_NUM_OUTPUTS + i] = self->audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + i][j]; | |||
| } | |||
| } | |||
| #endif | |||
| #endif | |||
| }; | |||
| #endif | |||
| #endif // SDL_BRIDGE_HPP_INCLUDED | |||
| @@ -0,0 +1,354 @@ | |||
| /* | |||
| * Web Audio + MIDI Bridge for DPF | |||
| * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| * or without fee is hereby granted, provided that the above copyright notice and this | |||
| * permission notice appear in all copies. | |||
| * | |||
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| */ | |||
| #ifndef WEB_BRIDGE_HPP_INCLUDED | |||
| #define WEB_BRIDGE_HPP_INCLUDED | |||
| #include "NativeBridge.hpp" | |||
| #include <emscripten/emscripten.h> | |||
| struct WebBridge : NativeBridge { | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| bool captureAvailable = false; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| bool playbackAvailable = false; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| bool midiAvailable = false; | |||
| #endif | |||
| bool active = false; | |||
| double timestamp = 0; | |||
| WebBridge() | |||
| { | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| captureAvailable = EM_ASM_INT({ | |||
| if (typeof(navigator.mediaDevices) !== 'undefined' && typeof(navigator.mediaDevices.getUserMedia) !== 'undefined') | |||
| return 1; | |||
| if (typeof(navigator.webkitGetUserMedia) !== 'undefined') | |||
| return 1; | |||
| return false; | |||
| }) != 0; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| playbackAvailable = EM_ASM_INT({ | |||
| if (typeof(AudioContext) !== 'undefined') | |||
| return 1; | |||
| if (typeof(webkitAudioContext) !== 'undefined') | |||
| return 1; | |||
| return 0; | |||
| }) != 0; | |||
| #endif | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| midiAvailable = EM_ASM_INT({ | |||
| return typeof(navigator.requestMIDIAccess) === 'function' ? 1 : 0; | |||
| }) != 0; | |||
| #endif | |||
| } | |||
| bool open(const char*) override | |||
| { | |||
| // early bail out if required features are not supported | |||
| #if DISTRHO_PLUGIN_NUM_INPUTS > 0 | |||
| if (!captureAvailable) | |||
| { | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS == 0 | |||
| d_stderr2("Audio capture is not supported"); | |||
| return false; | |||
| #else | |||
| if (!playbackAvailable) | |||
| { | |||
| d_stderr2("Audio capture and playback are not supported"); | |||
| return false; | |||
| } | |||
| d_stderr2("Audio capture is not supported, but can still use playback"); | |||
| #endif | |||
| } | |||
| #endif | |||
| #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 | |||
| if (!playbackAvailable) | |||
| { | |||
| d_stderr2("Audio playback is not supported"); | |||
| return false; | |||
| } | |||
| #endif | |||
| const bool initialized = EM_ASM_INT({ | |||
| if (typeof(Module['WebAudioBridge']) === 'undefined') { | |||
| Module['WebAudioBridge'] = {}; | |||
| } | |||
| var WAB = Module['WebAudioBridge']; | |||
| if (!WAB.audioContext) { | |||
| if (typeof(AudioContext) !== 'undefined') { | |||
| WAB.audioContext = new AudioContext(); | |||
| } else if (typeof(webkitAudioContext) !== 'undefined') { | |||
| WAB.audioContext = new webkitAudioContext(); | |||
| } | |||
| } | |||
| return WAB.audioContext === undefined ? 0 : 1; | |||
| }) != 0; | |||
| if (!initialized) | |||
| { | |||
| d_stderr2("Failed to initialize web audio"); | |||
| return false; | |||
| } | |||
| bufferSize = 512; | |||
| sampleRate = EM_ASM_INT_V({ | |||
| var WAB = Module['WebAudioBridge']; | |||
| return WAB.audioContext.sampleRate; | |||
| }); | |||
| allocBuffers(); | |||
| EM_ASM({ | |||
| var numInputs = $0; | |||
| var numOutputs = $1; | |||
| var bufferSize = $2; | |||
| var WAB = Module['WebAudioBridge']; | |||
| // main processor | |||
| WAB.processor = WAB.audioContext['createScriptProcessor'](bufferSize, numInputs, numOutputs); | |||
| WAB.processor['onaudioprocess'] = function (e) { | |||
| var timestamp = performance.now(); | |||
| for (var i = 0; i < numInputs; ++i) { | |||
| var buffer = e['inputBuffer']['getChannelData'](i); | |||
| for (var j = 0; j < bufferSize; ++j) { | |||
| // setValue($3 + ((bufferSize * i) + j) * 4, buffer[j], 'float'); | |||
| HEAPF32[$3 + (((bufferSize * i) + j) << 2) >> 2] = buffer[j]; | |||
| } | |||
| } | |||
| dynCall('vif', $4, [$5, timestamp]); | |||
| for (var i = 0; i < numOutputs; ++i) { | |||
| var buffer = e['outputBuffer']['getChannelData'](i); | |||
| var offset = bufferSize * (numInputs + i); | |||
| for (var j = 0; j < bufferSize; ++j) { | |||
| buffer[j] = HEAPF32[$3 + ((offset + j) << 2) >> 2]; | |||
| } | |||
| } | |||
| }; | |||
| // connect to output | |||
| WAB.processor['connect'](WAB.audioContext['destination']); | |||
| // resume/start playback on first click | |||
| document.addEventListener('click', function(e) { | |||
| var WAB = Module['WebAudioBridge']; | |||
| console.log(WAB.audioContext.state); | |||
| if (WAB.audioContext.state === 'suspended') | |||
| WAB.audioContext.resume(); | |||
| }); | |||
| }, DISTRHO_PLUGIN_NUM_INPUTS, DISTRHO_PLUGIN_NUM_OUTPUTS, bufferSize, audioBufferStorage, WebAudioCallback, this); | |||
| // enableInput(); | |||
| enableMIDI(); | |||
| return true; | |||
| } | |||
| bool close() override | |||
| { | |||
| freeBuffers(); | |||
| return true; | |||
| } | |||
| bool activate() override | |||
| { | |||
| active = true; | |||
| return true; | |||
| } | |||
| bool deactivate() override | |||
| { | |||
| active = false; | |||
| return true; | |||
| } | |||
| bool enableInput() | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(DISTRHO_PLUGIN_NUM_INPUTS > 0, false); | |||
| EM_ASM({ | |||
| var numInputs = $0; | |||
| var WAB = Module['WebAudioBridge']; | |||
| var constraints = {}; | |||
| // we need to use this weird awkward way for objects, otherwise build fails | |||
| constraints['audio'] = true; | |||
| constraints['video'] = false; | |||
| constraints['latency'] = 0; | |||
| constraints['sampleSize'] = 24; | |||
| constraints['mandatory'] = {}; | |||
| constraints['mandatory']['autoGainControl'] = false; | |||
| constraints['mandatory']['echoCancellation'] = false; | |||
| constraints['mandatory']['noiseSuppression'] = false; | |||
| constraints['mandatory']['channelCount'] = numInputs; | |||
| // old property for chrome | |||
| constraints['mandatory']['googAutoGainControl'] = false; | |||
| var success = function(stream) { | |||
| WAB.captureStreamNode = WAB.audioContext['createMediaStreamSource'](stream); | |||
| WAB.captureStreamNode.connect(WAB.processor); | |||
| }; | |||
| var fail = function() { | |||
| }; | |||
| if (navigator.mediaDevices !== undefined && navigator.mediaDevices.getUserMedia !== undefined) { | |||
| navigator.mediaDevices.getUserMedia(constraints).then(success).catch(fail); | |||
| } else if (navigator.webkitGetUserMedia !== undefined) { | |||
| navigator.webkitGetUserMedia(constraints, success, fail); | |||
| } | |||
| }, DISTRHO_PLUGIN_NUM_INPUTS); | |||
| return true; | |||
| } | |||
| bool enableMIDI() | |||
| { | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| if (midiAvailable) | |||
| { | |||
| EM_ASM({ | |||
| var useInput = !!$0; | |||
| var useOutput = !!$1; | |||
| var maxSize = $2; | |||
| var WAB = Module['WebAudioBridge']; | |||
| var offset = Module._malloc(maxSize); | |||
| var inputCallback = function(event) { | |||
| if (event.data.length > maxSize) | |||
| return; | |||
| var buffer = new Uint8Array(Module.HEAPU8.buffer, offset, maxSize); | |||
| buffer.set(event.data); | |||
| dynCall('viiif', $3, [$4, buffer.byteOffset, event.data.length, event.timeStamp]); | |||
| }; | |||
| var stateCallback = function(event) { | |||
| if (event.port.state === 'connected' && event.port.connection === 'open') { | |||
| if (useInput && event.port.type === 'input') { | |||
| if (event.port.name.indexOf('Midi Through') < 0) | |||
| event.port.onmidimessage = inputCallback; | |||
| } else if (useOutput && event.port.type === 'output') { | |||
| event.port.open(); | |||
| } | |||
| } | |||
| }; | |||
| var success = function(midi) { | |||
| WAB.midi = midi; | |||
| midi.onstatechange = stateCallback; | |||
| if (useInput) { | |||
| midi.inputs.forEach(function(port) { | |||
| if (port.name.indexOf('Midi Through') < 0) | |||
| port.onmidimessage = inputCallback; | |||
| }); | |||
| } | |||
| if (useOutput) { | |||
| midi.outputs.forEach(function(port) { | |||
| port.open(); | |||
| }); | |||
| } | |||
| }; | |||
| var fail = function(why) { | |||
| console.log("midi access failed:", why); | |||
| }; | |||
| navigator.requestMIDIAccess().then(success, fail); | |||
| }, DISTRHO_PLUGIN_WANT_MIDI_INPUT, DISTRHO_PLUGIN_WANT_MIDI_OUTPUT, kMaxMIDIInputMessageSize, WebMIDICallback, this); | |||
| return true; | |||
| } | |||
| else | |||
| #endif | |||
| { | |||
| d_stderr2("MIDI is not supported"); | |||
| return false; | |||
| } | |||
| } | |||
| static void WebAudioCallback(void* const userData, const double timestamp) | |||
| { | |||
| WebBridge* const self = static_cast<WebBridge*>(userData); | |||
| self->timestamp = timestamp; | |||
| const uint numFrames = self->bufferSize; | |||
| if (self->jackProcessCallback != nullptr && self->active) | |||
| { | |||
| self->jackProcessCallback(numFrames, self->jackProcessArg); | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT | |||
| if (self->midiAvailable) | |||
| { | |||
| static_assert(kMaxMIDIInputMessageSize + 1u == 4, "change code if bumping this value"); | |||
| uint32_t offset = 0; | |||
| uint8_t bytes[4] = {}; | |||
| while (self->midiOutBuffer.isDataAvailableForReading() && | |||
| self->midiOutBuffer.readCustomData(bytes, ARRAY_SIZE(bytes))) | |||
| { | |||
| offset = self->midiOutBuffer.readUInt(); | |||
| EM_ASM({ | |||
| var WAB = Module['WebAudioBridge']; | |||
| if (WAB.midi) { | |||
| var timestamp = performance.now() + $0; | |||
| var size = $1; | |||
| WAB.midi.outputs.forEach(function(port) { | |||
| if (port.state !== 'disconnected') { | |||
| port.send(size == 3 ? [ $2, $3, $4 ] : | |||
| size == 2 ? [ $2, $3 ] : | |||
| [ $2 ], timestamp); | |||
| } | |||
| }); | |||
| } | |||
| }, offset, bytes[0], bytes[1], bytes[2], bytes[3]); | |||
| } | |||
| self->midiOutBuffer.clearData(); | |||
| } | |||
| #endif | |||
| } | |||
| else | |||
| { | |||
| for (uint i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) | |||
| std::memset(self->audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + i], 0, sizeof(float)*numFrames); | |||
| } | |||
| } | |||
| #if DISTRHO_PLUGIN_WANT_MIDI_INPUT | |||
| static void WebMIDICallback(void* const userData, uint8_t* const data, const int len, const double timestamp) | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(len > 0 && len <= (int)kMaxMIDIInputMessageSize,); | |||
| WebBridge* const self = static_cast<WebBridge*>(userData); | |||
| // TODO timestamp handling | |||
| self->midiInBufferPending.writeByte(static_cast<uint8_t>(len)); | |||
| self->midiInBufferPending.writeCustomData(data, kMaxMIDIInputMessageSize); | |||
| self->midiInBufferPending.commitWrite(); | |||
| } | |||
| #endif | |||
| }; | |||
| #endif // WEB_BRIDGE_HPP_INCLUDED | |||