|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2022 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- The code included in this file is provided under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license. 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.
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- static void* juce_libjackHandle = nullptr;
-
- static void* juce_loadJackFunction (const char* const name)
- {
- if (juce_libjackHandle == nullptr)
- return nullptr;
-
- return dlsym (juce_libjackHandle, name);
- }
-
- #define JUCE_DECL_JACK_FUNCTION(return_type, fn_name, argument_types, arguments) \
- 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) \
- 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 (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id))
- JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port))
- JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name))
- 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 : 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)
- {
- const auto result = juce::jack_deactivate (client);
- jassertquiet (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 : 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 (const_cast<const float**> (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);
-
- if (notifyChannelsChanged != nullptr)
- 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 (jack_status_t code, const char* reason, void* arg)
- {
- jassertquiet (code == 0);
-
- JUCE_JACK_LOG ("Shutting down with message:");
- JUCE_JACK_LOG (reason);
- ignoreUnused (reason);
-
- shutdownCallback (arg);
- }
-
- static void errorCallback (const char* msg)
- {
- JUCE_JACK_LOG ("JackAudioIODevice::errorCallback " + String (msg));
- ignoreUnused (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 : public AudioIODeviceType
- {
- public:
- JackAudioIODeviceType()
- : AudioIODeviceType ("JACK")
- {}
-
- void scanForDevices()
- {
- hasScanned = true;
- inputNames.clear();
- outputNames.clear();
-
- if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so.0", RTLD_LAZY);
- if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY);
- 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
|