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 |