Browse Source

Rework native audio standalone fallback code, add full wasm stuff

Signed-off-by: falkTX <falktx@falktx.com>
pull/321/head
falkTX 2 years ago
parent
commit
ec02b88923
Signed by: falkTX <falktx@falktx.com> GPG Key ID: CDBAA37ABC74FBA0
8 changed files with 887 additions and 496 deletions
  1. +12
    -3
      Makefile.base.mk
  2. +13
    -0
      Makefile.plugins.mk
  3. +23
    -2
      distrho/extra/RingBuffer.hpp
  4. +182
    -229
      distrho/src/jackbridge/JackBridge.cpp
  5. +213
    -0
      distrho/src/jackbridge/NativeBridge.hpp
  6. +49
    -125
      distrho/src/jackbridge/RtAudioBridge.hpp
  7. +41
    -137
      distrho/src/jackbridge/SDL2Bridge.hpp
  8. +354
    -0
      distrho/src/jackbridge/WebBridge.hpp

+ 12
- 3
Makefile.base.mk View File

@@ -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)


+ 13
- 0
Makefile.plugins.mk View File

@@ -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

# ---------------------------------------------------------------------------------------------------------------------


+ 23
- 2
distrho/extra/RingBuffer.hpp View File

@@ -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;


+ 182
- 229
distrho/src/jackbridge/JackBridge.cpp
File diff suppressed because it is too large
View File


+ 213
- 0
distrho/src/jackbridge/NativeBridge.hpp View File

@@ -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

+ 49
- 125
distrho/src/jackbridge/RtAudioBridge.hpp View File

@@ -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

distrho/src/jackbridge/SDLBridge.hpp → distrho/src/jackbridge/SDL2Bridge.hpp View File

@@ -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

+ 354
- 0
distrho/src/jackbridge/WebBridge.hpp View File

@@ -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

Loading…
Cancel
Save