mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
694 lines
26 KiB
C++
694 lines
26 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE framework.
|
|
Copyright (c) Raw Material Software Limited
|
|
|
|
JUCE is an open source framework subject to commercial or open source
|
|
licensing.
|
|
|
|
By downloading, installing, or using the JUCE framework, or combining the
|
|
JUCE framework with any other source code, object code, content or any other
|
|
copyrightable work, you agree to the terms of the JUCE End User Licence
|
|
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
|
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
|
do not agree to the terms of these agreements, we will not license the JUCE
|
|
framework to you, and you must discontinue the installation or download
|
|
process and cease use of the JUCE framework.
|
|
|
|
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
|
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
|
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
|
|
|
Or:
|
|
|
|
You may also use this code under the terms of the AGPLv3:
|
|
https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
|
|
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
|
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
|
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
namespace juce
|
|
{
|
|
|
|
static void* juce_libjackHandle = nullptr;
|
|
|
|
static void* juce_loadJackFunction (const char* const name)
|
|
{
|
|
if (juce_libjackHandle == nullptr)
|
|
return nullptr;
|
|
|
|
#if JUCE_WINDOWS
|
|
return GetProcAddress ((HMODULE) juce_libjackHandle, name);
|
|
#else
|
|
return dlsym (juce_libjackHandle, name);
|
|
#endif
|
|
}
|
|
|
|
#define JUCE_DECL_JACK_FUNCTION(return_type, fn_name, argument_types, arguments) \
|
|
static inline return_type fn_name argument_types \
|
|
{ \
|
|
using ReturnType = return_type; \
|
|
typedef return_type (*fn_type) argument_types; \
|
|
static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \
|
|
jassert (fn != nullptr); \
|
|
return (fn != nullptr) ? ((*fn) arguments) : ReturnType(); \
|
|
}
|
|
|
|
#define JUCE_DECL_VOID_JACK_FUNCTION(fn_name, argument_types, arguments) \
|
|
static inline void fn_name argument_types \
|
|
{ \
|
|
typedef void (*fn_type) argument_types; \
|
|
static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \
|
|
jassert (fn != nullptr); \
|
|
if (fn != nullptr) (*fn) arguments; \
|
|
}
|
|
|
|
//==============================================================================
|
|
JUCE_DECL_JACK_FUNCTION (jack_client_t*, jack_client_open, (const char* client_name, jack_options_t options, jack_status_t* status, ...), (client_name, options, status))
|
|
JUCE_DECL_JACK_FUNCTION (int, jack_client_close, (jack_client_t *client), (client))
|
|
JUCE_DECL_JACK_FUNCTION (int, jack_activate, (jack_client_t* client), (client))
|
|
JUCE_DECL_JACK_FUNCTION (int, jack_deactivate, (jack_client_t* client), (client))
|
|
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_buffer_size, (jack_client_t* client), (client))
|
|
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_sample_rate, (jack_client_t* client), (client))
|
|
JUCE_DECL_VOID_JACK_FUNCTION (jack_on_shutdown, (jack_client_t* client, void (*function) (void* arg), void* arg), (client, function, arg))
|
|
JUCE_DECL_VOID_JACK_FUNCTION (jack_on_info_shutdown, (jack_client_t* client, JackInfoShutdownCallback function, void* arg), (client, function, arg))
|
|
JUCE_DECL_JACK_FUNCTION (void* , jack_port_get_buffer, (jack_port_t* port, jack_nframes_t nframes), (port, nframes))
|
|
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_port_get_total_latency, (jack_client_t* client, jack_port_t* port), (client, port))
|
|
JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_register, (jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size), (client, port_name, port_type, flags, buffer_size))
|
|
JUCE_DECL_VOID_JACK_FUNCTION (jack_set_error_function, (void (*func) (const char*)), (func))
|
|
JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client, JackProcessCallback process_callback, void* arg), (client, process_callback, arg))
|
|
JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags))
|
|
JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port))
|
|
JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port))
|
|
JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg))
|
|
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port))
|
|
JUCE_DECL_JACK_FUNCTION (int, jack_set_xrun_callback, (jack_client_t* client, JackXRunCallback xrun_callback, void* arg), (client, xrun_callback, arg))
|
|
JUCE_DECL_JACK_FUNCTION (int, jack_port_flags, (const jack_port_t* port), (port))
|
|
JUCE_DECL_JACK_FUNCTION (jack_port_t*, jack_port_by_name, (jack_client_t* client, const char* name), (client, name))
|
|
JUCE_DECL_VOID_JACK_FUNCTION (jack_free, (void* ptr), (ptr))
|
|
|
|
#if JUCE_DEBUG
|
|
#define JACK_LOGGING_ENABLED 1
|
|
#endif
|
|
|
|
#if JACK_LOGGING_ENABLED
|
|
namespace
|
|
{
|
|
void jack_Log (const String& s)
|
|
{
|
|
std::cerr << s << std::endl;
|
|
}
|
|
|
|
const char* getJackErrorMessage (const jack_status_t status)
|
|
{
|
|
if (status & JackServerFailed
|
|
|| status & JackServerError) return "Unable to connect to JACK server";
|
|
if (status & JackVersionError) return "Client's protocol version does not match";
|
|
if (status & JackInvalidOption) return "The operation contained an invalid or unsupported option";
|
|
if (status & JackNameNotUnique) return "The desired client name was not unique";
|
|
if (status & JackNoSuchClient) return "Requested client does not exist";
|
|
if (status & JackInitFailure) return "Unable to initialize client";
|
|
return nullptr;
|
|
}
|
|
}
|
|
#define JUCE_JACK_LOG_STATUS(x) { if (const char* m = getJackErrorMessage (x)) jack_Log (m); }
|
|
#define JUCE_JACK_LOG(x) jack_Log(x)
|
|
#else
|
|
#define JUCE_JACK_LOG_STATUS(x) {}
|
|
#define JUCE_JACK_LOG(x) {}
|
|
#endif
|
|
|
|
|
|
//==============================================================================
|
|
#ifndef JUCE_JACK_CLIENT_NAME
|
|
#ifdef JucePlugin_Name
|
|
#define JUCE_JACK_CLIENT_NAME JucePlugin_Name
|
|
#else
|
|
#define JUCE_JACK_CLIENT_NAME "JUCEJack"
|
|
#endif
|
|
#endif
|
|
|
|
struct JackPortIterator
|
|
{
|
|
JackPortIterator (jack_client_t* const client, const bool forInput)
|
|
{
|
|
if (client != nullptr)
|
|
ports.reset (juce::jack_get_ports (client, nullptr, nullptr,
|
|
forInput ? JackPortIsInput : JackPortIsOutput));
|
|
}
|
|
|
|
bool next()
|
|
{
|
|
if (ports == nullptr || ports.get()[index + 1] == nullptr)
|
|
return false;
|
|
|
|
name = CharPointer_UTF8 (ports.get()[++index]);
|
|
return true;
|
|
}
|
|
|
|
String getClientName() const
|
|
{
|
|
return name.upToFirstOccurrenceOf (":", false, false);
|
|
}
|
|
|
|
String getChannelName() const
|
|
{
|
|
return name.fromFirstOccurrenceOf (":", false, false);
|
|
}
|
|
|
|
struct Free
|
|
{
|
|
void operator() (const char** ptr) const noexcept { juce::jack_free (ptr); }
|
|
};
|
|
|
|
std::unique_ptr<const char*, Free> ports;
|
|
int index = -1;
|
|
String name;
|
|
};
|
|
|
|
//==============================================================================
|
|
class JackAudioIODevice final : public AudioIODevice
|
|
{
|
|
public:
|
|
JackAudioIODevice (const String& inName,
|
|
const String& outName,
|
|
std::function<void()> notifyIn)
|
|
: AudioIODevice (outName.isEmpty() ? inName : outName, "JACK"),
|
|
inputName (inName),
|
|
outputName (outName),
|
|
notifyChannelsChanged (std::move (notifyIn))
|
|
{
|
|
jassert (outName.isNotEmpty() || inName.isNotEmpty());
|
|
|
|
jack_status_t status = {};
|
|
client = juce::jack_client_open (JUCE_JACK_CLIENT_NAME, JackNoStartServer, &status);
|
|
|
|
if (client == nullptr)
|
|
{
|
|
JUCE_JACK_LOG_STATUS (status);
|
|
}
|
|
else
|
|
{
|
|
juce::jack_set_error_function (errorCallback);
|
|
|
|
// open input ports
|
|
const StringArray inputChannels (getInputChannelNames());
|
|
for (int i = 0; i < inputChannels.size(); ++i)
|
|
{
|
|
String inputChannelName;
|
|
inputChannelName << "in_" << ++totalNumberOfInputChannels;
|
|
|
|
inputPorts.add (juce::jack_port_register (client, inputChannelName.toUTF8(),
|
|
JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0));
|
|
}
|
|
|
|
// open output ports
|
|
const StringArray outputChannels (getOutputChannelNames());
|
|
for (int i = 0; i < outputChannels.size(); ++i)
|
|
{
|
|
String outputChannelName;
|
|
outputChannelName << "out_" << ++totalNumberOfOutputChannels;
|
|
|
|
outputPorts.add (juce::jack_port_register (client, outputChannelName.toUTF8(),
|
|
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0));
|
|
}
|
|
|
|
inChans.calloc (totalNumberOfInputChannels + 2);
|
|
outChans.calloc (totalNumberOfOutputChannels + 2);
|
|
}
|
|
}
|
|
|
|
~JackAudioIODevice() override
|
|
{
|
|
close();
|
|
if (client != nullptr)
|
|
{
|
|
juce::jack_client_close (client);
|
|
client = nullptr;
|
|
}
|
|
}
|
|
|
|
StringArray getChannelNames (const String& clientName, bool forInput) const
|
|
{
|
|
StringArray names;
|
|
|
|
for (JackPortIterator i (client, forInput); i.next();)
|
|
if (i.getClientName() == clientName)
|
|
names.add (i.getChannelName());
|
|
|
|
return names;
|
|
}
|
|
|
|
StringArray getOutputChannelNames() override { return getChannelNames (outputName, true); }
|
|
StringArray getInputChannelNames() override { return getChannelNames (inputName, false); }
|
|
|
|
Array<double> getAvailableSampleRates() override
|
|
{
|
|
Array<double> rates;
|
|
|
|
if (client != nullptr)
|
|
rates.add (juce::jack_get_sample_rate (client));
|
|
|
|
return rates;
|
|
}
|
|
|
|
Array<int> getAvailableBufferSizes() override
|
|
{
|
|
Array<int> sizes;
|
|
|
|
if (client != nullptr)
|
|
sizes.add (static_cast<int> (juce::jack_get_buffer_size (client)));
|
|
|
|
return sizes;
|
|
}
|
|
|
|
int getDefaultBufferSize() override { return getCurrentBufferSizeSamples(); }
|
|
int getCurrentBufferSizeSamples() override { return client != nullptr ? static_cast<int> (juce::jack_get_buffer_size (client)) : 0; }
|
|
double getCurrentSampleRate() override { return client != nullptr ? static_cast<int> (juce::jack_get_sample_rate (client)) : 0; }
|
|
|
|
template <typename Fn>
|
|
void forEachClientChannel (const String& clientName, bool isInput, Fn&& fn)
|
|
{
|
|
auto index = 0;
|
|
|
|
for (JackPortIterator i (client, isInput); i.next();)
|
|
{
|
|
if (i.getClientName() != clientName)
|
|
continue;
|
|
|
|
fn (i.ports.get()[i.index], index);
|
|
index += 1;
|
|
}
|
|
}
|
|
|
|
String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
|
|
double /* sampleRate */, int /* bufferSizeSamples */) override
|
|
{
|
|
if (client == nullptr)
|
|
{
|
|
lastError = "No JACK client running";
|
|
return lastError;
|
|
}
|
|
|
|
lastError.clear();
|
|
close();
|
|
|
|
xruns.store (0, std::memory_order_relaxed);
|
|
juce::jack_set_process_callback (client, processCallback, this);
|
|
juce::jack_set_port_connect_callback (client, portConnectCallback, this);
|
|
juce::jack_on_shutdown (client, shutdownCallback, this);
|
|
juce::jack_on_info_shutdown (client, infoShutdownCallback, this);
|
|
juce::jack_set_xrun_callback (client, xrunCallback, this);
|
|
juce::jack_activate (client);
|
|
deviceIsOpen = true;
|
|
|
|
if (! inputChannels.isZero())
|
|
{
|
|
forEachClientChannel (inputName, false, [&] (const char* portName, int index)
|
|
{
|
|
if (! inputChannels[index])
|
|
return;
|
|
|
|
jassert (index < inputPorts.size());
|
|
|
|
const auto* source = portName;
|
|
const auto* inputPort = inputPorts[index];
|
|
|
|
jassert (juce::jack_port_flags (juce::jack_port_by_name (client, source)) & JackPortIsOutput);
|
|
jassert (juce::jack_port_flags (inputPort) & JackPortIsInput);
|
|
|
|
auto error = juce::jack_connect (client, source, juce::jack_port_name (inputPort));
|
|
if (error != 0)
|
|
JUCE_JACK_LOG ("Cannot connect input port " + String (index) + " (" + portName + "), error " + String (error));
|
|
});
|
|
}
|
|
|
|
if (! outputChannels.isZero())
|
|
{
|
|
forEachClientChannel (outputName, true, [&] (const char* portName, int index)
|
|
{
|
|
if (! outputChannels[index])
|
|
return;
|
|
|
|
jassert (index < outputPorts.size());
|
|
|
|
const auto* outputPort = outputPorts[index];
|
|
const auto* destination = portName;
|
|
|
|
jassert (juce::jack_port_flags (outputPort) & JackPortIsOutput);
|
|
jassert (juce::jack_port_flags (juce::jack_port_by_name (client, destination)) & JackPortIsInput);
|
|
|
|
auto error = juce::jack_connect (client, juce::jack_port_name (outputPort), destination);
|
|
if (error != 0)
|
|
JUCE_JACK_LOG ("Cannot connect output port " + String (index) + " (" + portName + "), error " + String (error));
|
|
});
|
|
}
|
|
|
|
updateActivePorts();
|
|
|
|
return lastError;
|
|
}
|
|
|
|
void close() override
|
|
{
|
|
stop();
|
|
|
|
if (client != nullptr)
|
|
{
|
|
[[maybe_unused]] const auto result = juce::jack_deactivate (client);
|
|
jassert (result == 0);
|
|
|
|
juce::jack_set_xrun_callback (client, xrunCallback, nullptr);
|
|
juce::jack_set_process_callback (client, processCallback, nullptr);
|
|
juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr);
|
|
juce::jack_on_shutdown (client, shutdownCallback, nullptr);
|
|
juce::jack_on_info_shutdown (client, infoShutdownCallback, nullptr);
|
|
}
|
|
|
|
deviceIsOpen = false;
|
|
}
|
|
|
|
void start (AudioIODeviceCallback* newCallback) override
|
|
{
|
|
if (deviceIsOpen && newCallback != callback)
|
|
{
|
|
if (newCallback != nullptr)
|
|
newCallback->audioDeviceAboutToStart (this);
|
|
|
|
AudioIODeviceCallback* const oldCallback = callback;
|
|
|
|
{
|
|
const ScopedLock sl (callbackLock);
|
|
callback = newCallback;
|
|
}
|
|
|
|
if (oldCallback != nullptr)
|
|
oldCallback->audioDeviceStopped();
|
|
}
|
|
}
|
|
|
|
void stop() override
|
|
{
|
|
start (nullptr);
|
|
}
|
|
|
|
bool isOpen() override { return deviceIsOpen; }
|
|
bool isPlaying() override { return callback != nullptr; }
|
|
int getCurrentBitDepth() override { return 32; }
|
|
String getLastError() override { return lastError; }
|
|
int getXRunCount() const noexcept override { return xruns.load (std::memory_order_relaxed); }
|
|
|
|
BigInteger getActiveOutputChannels() const override { return activeOutputChannels; }
|
|
BigInteger getActiveInputChannels() const override { return activeInputChannels; }
|
|
|
|
int getOutputLatencyInSamples() override
|
|
{
|
|
int latency = 0;
|
|
|
|
for (int i = 0; i < outputPorts.size(); i++)
|
|
latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, outputPorts[i]));
|
|
|
|
return latency;
|
|
}
|
|
|
|
int getInputLatencyInSamples() override
|
|
{
|
|
int latency = 0;
|
|
|
|
for (int i = 0; i < inputPorts.size(); i++)
|
|
latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, inputPorts[i]));
|
|
|
|
return latency;
|
|
}
|
|
|
|
String inputName, outputName;
|
|
|
|
private:
|
|
//==============================================================================
|
|
class MainThreadDispatcher final : private AsyncUpdater
|
|
{
|
|
public:
|
|
explicit MainThreadDispatcher (JackAudioIODevice& device) : ref (device) {}
|
|
~MainThreadDispatcher() override { cancelPendingUpdate(); }
|
|
|
|
void updateActivePorts()
|
|
{
|
|
if (MessageManager::getInstance()->isThisTheMessageThread())
|
|
handleAsyncUpdate();
|
|
else
|
|
triggerAsyncUpdate();
|
|
}
|
|
|
|
private:
|
|
void handleAsyncUpdate() override { ref.updateActivePorts(); }
|
|
|
|
JackAudioIODevice& ref;
|
|
};
|
|
|
|
//==============================================================================
|
|
void process (const int numSamples)
|
|
{
|
|
int numActiveInChans = 0, numActiveOutChans = 0;
|
|
|
|
for (int i = 0; i < totalNumberOfInputChannels; ++i)
|
|
{
|
|
if (activeInputChannels[i])
|
|
if (auto* in = (jack_default_audio_sample_t*) juce::jack_port_get_buffer (inputPorts.getUnchecked (i),
|
|
static_cast<jack_nframes_t> (numSamples)))
|
|
inChans[numActiveInChans++] = (float*) in;
|
|
}
|
|
|
|
for (int i = 0; i < totalNumberOfOutputChannels; ++i)
|
|
{
|
|
if (activeOutputChannels[i])
|
|
if (auto* out = (jack_default_audio_sample_t*) juce::jack_port_get_buffer (outputPorts.getUnchecked (i),
|
|
static_cast<jack_nframes_t> (numSamples)))
|
|
outChans[numActiveOutChans++] = (float*) out;
|
|
}
|
|
|
|
const ScopedLock sl (callbackLock);
|
|
|
|
if (callback != nullptr)
|
|
{
|
|
if ((numActiveInChans + numActiveOutChans) > 0)
|
|
callback->audioDeviceIOCallbackWithContext (inChans.getData(),
|
|
numActiveInChans,
|
|
outChans,
|
|
numActiveOutChans,
|
|
numSamples,
|
|
{});
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < numActiveOutChans; ++i)
|
|
zeromem (outChans[i], static_cast<size_t> (numSamples) * sizeof (float));
|
|
}
|
|
}
|
|
|
|
static int processCallback (jack_nframes_t nframes, void* callbackArgument)
|
|
{
|
|
if (callbackArgument != nullptr)
|
|
((JackAudioIODevice*) callbackArgument)->process (static_cast<int> (nframes));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xrunCallback (void* callbackArgument)
|
|
{
|
|
if (callbackArgument != nullptr)
|
|
((JackAudioIODevice*) callbackArgument)->xruns++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void updateActivePorts()
|
|
{
|
|
BigInteger newOutputChannels, newInputChannels;
|
|
|
|
for (int i = 0; i < outputPorts.size(); ++i)
|
|
if (juce::jack_port_connected (outputPorts.getUnchecked (i)))
|
|
newOutputChannels.setBit (i);
|
|
|
|
for (int i = 0; i < inputPorts.size(); ++i)
|
|
if (juce::jack_port_connected (inputPorts.getUnchecked (i)))
|
|
newInputChannels.setBit (i);
|
|
|
|
if (newOutputChannels != activeOutputChannels
|
|
|| newInputChannels != activeInputChannels)
|
|
{
|
|
AudioIODeviceCallback* const oldCallback = callback;
|
|
|
|
stop();
|
|
|
|
activeOutputChannels = newOutputChannels;
|
|
activeInputChannels = newInputChannels;
|
|
|
|
if (oldCallback != nullptr)
|
|
start (oldCallback);
|
|
|
|
NullCheckedInvocation::invoke (notifyChannelsChanged);
|
|
}
|
|
}
|
|
|
|
static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg)
|
|
{
|
|
if (JackAudioIODevice* device = static_cast<JackAudioIODevice*> (arg))
|
|
device->mainThreadDispatcher.updateActivePorts();
|
|
}
|
|
|
|
static void threadInitCallback (void* /* callbackArgument */)
|
|
{
|
|
JUCE_JACK_LOG ("JackAudioIODevice::initialise");
|
|
}
|
|
|
|
static void shutdownCallback (void* callbackArgument)
|
|
{
|
|
JUCE_JACK_LOG ("JackAudioIODevice::shutdown");
|
|
|
|
if (JackAudioIODevice* device = (JackAudioIODevice*) callbackArgument)
|
|
{
|
|
device->client = nullptr;
|
|
device->close();
|
|
}
|
|
}
|
|
|
|
static void infoShutdownCallback ([[maybe_unused]] jack_status_t code, [[maybe_unused]] const char* reason, void* arg)
|
|
{
|
|
jassert (code == 0);
|
|
|
|
JUCE_JACK_LOG ("Shutting down with message:");
|
|
JUCE_JACK_LOG (reason);
|
|
|
|
shutdownCallback (arg);
|
|
}
|
|
|
|
static void errorCallback ([[maybe_unused]] const char* msg)
|
|
{
|
|
JUCE_JACK_LOG ("JackAudioIODevice::errorCallback " + String (msg));
|
|
}
|
|
|
|
bool deviceIsOpen = false;
|
|
jack_client_t* client = nullptr;
|
|
String lastError;
|
|
AudioIODeviceCallback* callback = nullptr;
|
|
CriticalSection callbackLock;
|
|
|
|
HeapBlock<float*> inChans, outChans;
|
|
int totalNumberOfInputChannels = 0;
|
|
int totalNumberOfOutputChannels = 0;
|
|
Array<jack_port_t*> inputPorts, outputPorts;
|
|
BigInteger activeInputChannels, activeOutputChannels;
|
|
|
|
std::atomic<int> xruns { 0 };
|
|
|
|
std::function<void()> notifyChannelsChanged;
|
|
MainThreadDispatcher mainThreadDispatcher { *this };
|
|
};
|
|
|
|
//==============================================================================
|
|
class JackAudioIODeviceType;
|
|
|
|
class JackAudioIODeviceType final : public AudioIODeviceType
|
|
{
|
|
public:
|
|
JackAudioIODeviceType()
|
|
: AudioIODeviceType ("JACK")
|
|
{}
|
|
|
|
void scanForDevices()
|
|
{
|
|
hasScanned = true;
|
|
inputNames.clear();
|
|
outputNames.clear();
|
|
|
|
#if (JUCE_LINUX || JUCE_BSD)
|
|
if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so.0", RTLD_LAZY);
|
|
if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY);
|
|
#elif JUCE_MAC
|
|
if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.dylib", RTLD_LAZY);
|
|
#elif JUCE_WINDOWS
|
|
#if JUCE_64BIT
|
|
if (juce_libjackHandle == nullptr) juce_libjackHandle = LoadLibraryA ("libjack64.dll");
|
|
#else
|
|
if (juce_libjackHandle == nullptr) juce_libjackHandle = LoadLibraryA ("libjack.dll");
|
|
#endif
|
|
#endif
|
|
|
|
if (juce_libjackHandle == nullptr) return;
|
|
|
|
jack_status_t status = {};
|
|
|
|
// open a dummy client
|
|
if (auto* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status))
|
|
{
|
|
// scan for output devices
|
|
for (JackPortIterator i (client, false); i.next();)
|
|
if (i.getClientName() != (JUCE_JACK_CLIENT_NAME) && ! inputNames.contains (i.getClientName()))
|
|
inputNames.add (i.getClientName());
|
|
|
|
// scan for input devices
|
|
for (JackPortIterator i (client, true); i.next();)
|
|
if (i.getClientName() != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (i.getClientName()))
|
|
outputNames.add (i.getClientName());
|
|
|
|
juce::jack_client_close (client);
|
|
}
|
|
else
|
|
{
|
|
JUCE_JACK_LOG_STATUS (status);
|
|
}
|
|
}
|
|
|
|
StringArray getDeviceNames (bool wantInputNames) const
|
|
{
|
|
jassert (hasScanned); // need to call scanForDevices() before doing this
|
|
return wantInputNames ? inputNames : outputNames;
|
|
}
|
|
|
|
int getDefaultDeviceIndex (bool /* forInput */) const
|
|
{
|
|
jassert (hasScanned); // need to call scanForDevices() before doing this
|
|
return 0;
|
|
}
|
|
|
|
bool hasSeparateInputsAndOutputs() const { return true; }
|
|
|
|
int getIndexOfDevice (AudioIODevice* device, bool asInput) const
|
|
{
|
|
jassert (hasScanned); // need to call scanForDevices() before doing this
|
|
|
|
if (JackAudioIODevice* d = dynamic_cast<JackAudioIODevice*> (device))
|
|
return asInput ? inputNames.indexOf (d->inputName)
|
|
: outputNames.indexOf (d->outputName);
|
|
|
|
return -1;
|
|
}
|
|
|
|
AudioIODevice* createDevice (const String& outputDeviceName,
|
|
const String& inputDeviceName)
|
|
{
|
|
jassert (hasScanned); // need to call scanForDevices() before doing this
|
|
|
|
const int inputIndex = inputNames.indexOf (inputDeviceName);
|
|
const int outputIndex = outputNames.indexOf (outputDeviceName);
|
|
|
|
if (inputIndex >= 0 || outputIndex >= 0)
|
|
return new JackAudioIODevice (inputDeviceName, outputDeviceName,
|
|
[this] { callDeviceChangeListeners(); });
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
private:
|
|
StringArray inputNames, outputNames;
|
|
bool hasScanned = false;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType)
|
|
};
|
|
|
|
} // namespace juce
|