diff --git a/examples/DemoRunner/CMakeLists.txt b/examples/DemoRunner/CMakeLists.txt index bf50a90fd7..0261c51c30 100644 --- a/examples/DemoRunner/CMakeLists.txt +++ b/examples/DemoRunner/CMakeLists.txt @@ -46,6 +46,7 @@ target_compile_definitions(DemoRunner PRIVATE PIP_JUCE_EXAMPLES_DIRECTORY_STRING="${JUCE_SOURCE_DIR}/examples" JUCE_ALLOW_STATIC_NULL_VARIABLES=0 JUCE_DEMO_RUNNER=1 + JUCE_JACK=1 JUCE_STRICT_REFCOUNTEDPOINTER=1 JUCE_UNIT_TESTS=1 JUCE_USE_CAMERA=1 diff --git a/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp b/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp index 9db269b70f..33758a4063 100644 --- a/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp +++ b/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp @@ -36,9 +36,11 @@ static void* juce_loadJackFunction (const char* const 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); \ - return (fn != nullptr) ? ((*fn) arguments) : (return_type) 0; \ + jassert (fn != nullptr); \ + return (fn != nullptr) ? ((*fn) arguments) : ReturnType(); \ } #define JUCE_DECL_VOID_JACK_FUNCTION(fn_name, argument_types, arguments) \ @@ -46,6 +48,7 @@ static void* juce_loadJackFunction (const char* const name) { \ typedef void (*fn_type) argument_types; \ static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ + jassert (fn != nullptr); \ if (fn != nullptr) (*fn) arguments; \ } @@ -57,6 +60,7 @@ 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)) @@ -70,6 +74,9 @@ JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, 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 @@ -115,56 +122,56 @@ namespace struct JackPortIterator { JackPortIterator (jack_client_t* const client, const bool forInput) - : ports (nullptr), index (-1) { if (client != nullptr) - ports = juce::jack_get_ports (client, nullptr, nullptr, - forInput ? JackPortIsOutput : JackPortIsInput); - // (NB: This looks like it's the wrong way round, but it is correct!) - } - - ~JackPortIterator() - { - ::free (ports); + ports.reset (juce::jack_get_ports (client, nullptr, nullptr, + forInput ? JackPortIsInput : JackPortIsOutput)); } bool next() { - if (ports == nullptr || ports [index + 1] == nullptr) + if (ports == nullptr || ports.get()[index + 1] == nullptr) return false; - name = CharPointer_UTF8 (ports[++index]); - clientName = name.upToFirstOccurrenceOf (":", false, false); + name = CharPointer_UTF8 (ports.get()[++index]); return true; } - const char** ports; - int index; + 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 ports; + int index = -1; String name; - String clientName; }; -class JackAudioIODeviceType; -static Array activeDeviceTypes; - //============================================================================== class JackAudioIODevice : public AudioIODevice { public: - JackAudioIODevice (const String& deviceName, - const String& inId, - const String& outId) - : AudioIODevice (deviceName, "JACK"), - inputId (inId), - outputId (outId), - deviceIsOpen (false), - callback (nullptr), - totalNumberOfInputChannels (0), - totalNumberOfOutputChannels (0) - { - jassert (deviceName.isNotEmpty()); - - jack_status_t status; + JackAudioIODevice (const String& inName, + const String& outName, + std::function 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) @@ -202,7 +209,7 @@ public: } } - ~JackAudioIODevice() + ~JackAudioIODevice() override { close(); if (client != nullptr) @@ -212,19 +219,19 @@ public: } } - StringArray getChannelNames (bool forInput) const + StringArray getChannelNames (const String& clientName, bool forInput) const { StringArray names; for (JackPortIterator i (client, forInput); i.next();) - if (i.clientName == getName()) - names.add (i.name.fromFirstOccurrenceOf (":", false, false)); + if (i.getClientName() == clientName) + names.add (i.getChannelName()); return names; } - StringArray getOutputChannelNames() override { return getChannelNames (false); } - StringArray getInputChannelNames() override { return getChannelNames (true); } + StringArray getOutputChannelNames() override { return getChannelNames (outputName, true); } + StringArray getInputChannelNames() override { return getChannelNames (inputName, false); } Array getAvailableSampleRates() override { @@ -250,6 +257,20 @@ public: int getCurrentBufferSizeSamples() override { return client != nullptr ? static_cast (juce::jack_get_buffer_size (client)) : 0; } double getCurrentSampleRate() override { return client != nullptr ? static_cast (juce::jack_get_sample_rate (client)) : 0; } + template + 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 @@ -263,38 +284,55 @@ public: lastError.clear(); close(); - xruns = 0; + 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()) { - for (JackPortIterator i (client, true); i.next();) + forEachClientChannel (inputName, false, [&] (const char* portName, int index) { - if (inputChannels [i.index] && i.clientName == getName()) - { - int error = juce::jack_connect (client, i.ports[i.index], juce::jack_port_name ((jack_port_t*) inputPorts[i.index])); - if (error != 0) - JUCE_JACK_LOG ("Cannot connect input port " + String (i.index) + " (" + i.name + "), error " + String (error)); - } - } + 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()) { - for (JackPortIterator i (client, false); i.next();) + forEachClientChannel (outputName, true, [&] (const char* portName, int index) { - if (outputChannels [i.index] && i.clientName == getName()) - { - int error = juce::jack_connect (client, juce::jack_port_name ((jack_port_t*) outputPorts[i.index]), i.ports[i.index]); - if (error != 0) - JUCE_JACK_LOG ("Cannot connect output port " + String (i.index) + " (" + i.name + "), error " + String (error)); - } - } + 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(); @@ -308,12 +346,15 @@ public: if (client != nullptr) { - juce::jack_deactivate (client); + const auto result = juce::jack_deactivate (client); + jassert (result == 0); + ignoreUnused (result); 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; @@ -347,7 +388,7 @@ public: bool isPlaying() override { return callback != nullptr; } int getCurrentBitDepth() override { return 32; } String getLastError() override { return lastError; } - int getXRunCount() const noexcept override { return xruns; } + int getXRunCount() const noexcept override { return xruns.load (std::memory_order_relaxed); } BigInteger getActiveOutputChannels() const override { return activeOutputChannels; } BigInteger getActiveInputChannels() const override { return activeInputChannels; } @@ -357,7 +398,7 @@ public: int latency = 0; for (int i = 0; i < outputPorts.size(); i++) - latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) outputPorts [i])); + latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, outputPorts[i])); return latency; } @@ -367,14 +408,36 @@ public: int latency = 0; for (int i = 0; i < inputPorts.size(); i++) - latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) inputPorts [i])); + latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, inputPorts[i])); return latency; } - String inputId, outputId; + 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; @@ -382,17 +445,17 @@ private: for (int i = 0; i < totalNumberOfInputChannels; ++i) { if (activeInputChannels[i]) - if (jack_default_audio_sample_t* in - = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) inputPorts.getUnchecked(i), static_cast (numSamples))) - inChans [numActiveInChans++] = (float*) in; + if (auto* in = (jack_default_audio_sample_t*) juce::jack_port_get_buffer (inputPorts.getUnchecked (i), + static_cast (numSamples))) + inChans[numActiveInChans++] = (float*) in; } for (int i = 0; i < totalNumberOfOutputChannels; ++i) { if (activeOutputChannels[i]) - if (jack_default_audio_sample_t* out - = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) outputPorts.getUnchecked(i), static_cast (numSamples))) - outChans [numActiveOutChans++] = (float*) out; + if (auto* out = (jack_default_audio_sample_t*) juce::jack_port_get_buffer (outputPorts.getUnchecked (i), + static_cast (numSamples))) + outChans[numActiveOutChans++] = (float*) out; } const ScopedLock sl (callbackLock); @@ -406,7 +469,7 @@ private: else { for (int i = 0; i < numActiveOutChans; ++i) - zeromem (outChans[i], sizeof (float) * static_cast (numSamples)); + zeromem (outChans[i], static_cast (numSamples) * sizeof (float)); } } @@ -431,11 +494,11 @@ private: BigInteger newOutputChannels, newInputChannels; for (int i = 0; i < outputPorts.size(); ++i) - if (juce::jack_port_connected ((jack_port_t*) outputPorts.getUnchecked(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 ((jack_port_t*) inputPorts.getUnchecked(i))) + if (juce::jack_port_connected (inputPorts.getUnchecked (i))) newInputChannels.setBit (i); if (newOutputChannels != activeOutputChannels @@ -451,14 +514,15 @@ private: if (oldCallback != nullptr) start (oldCallback); - sendDeviceChangedCallback(); + if (notifyChannelsChanged != nullptr) + notifyChannelsChanged(); } } static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg) { if (JackAudioIODevice* device = static_cast (arg)) - device->updateActivePorts(); + device->mainThreadDispatcher.updateActivePorts(); } static void threadInitCallback (void* /* callbackArgument */) @@ -477,82 +541,76 @@ private: } } + static void infoShutdownCallback (jack_status_t code, const char* reason, void* arg) + { + jassert (code == 0); + ignoreUnused (code); + + 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); } - static void sendDeviceChangedCallback(); - - bool deviceIsOpen; - jack_client_t* client; + bool deviceIsOpen = false; + jack_client_t* client = nullptr; String lastError; - AudioIODeviceCallback* callback; + AudioIODeviceCallback* callback = nullptr; CriticalSection callbackLock; HeapBlock inChans, outChans; - int totalNumberOfInputChannels; - int totalNumberOfOutputChannels; - Array inputPorts, outputPorts; + int totalNumberOfInputChannels = 0; + int totalNumberOfOutputChannels = 0; + Array inputPorts, outputPorts; BigInteger activeInputChannels, activeOutputChannels; - int xruns; -}; + std::atomic xruns { 0 }; + std::function notifyChannelsChanged; + MainThreadDispatcher mainThreadDispatcher { *this }; +}; //============================================================================== +class JackAudioIODeviceType; + class JackAudioIODeviceType : public AudioIODeviceType { public: JackAudioIODeviceType() - : AudioIODeviceType ("JACK"), - hasScanned (false) - { - activeDeviceTypes.add (this); - } - - ~JackAudioIODeviceType() - { - activeDeviceTypes.removeFirstMatchingValue (this); - } + : AudioIODeviceType ("JACK") + {} void scanForDevices() { hasScanned = true; inputNames.clear(); - inputIds.clear(); outputNames.clear(); - outputIds.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; + jack_status_t status = {}; // open a dummy client - if (jack_client_t* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status)) + if (auto* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status)) { // scan for output devices for (JackPortIterator i (client, false); i.next();) - { - if (i.clientName != (JUCE_JACK_CLIENT_NAME) && ! inputNames.contains (i.clientName)) - { - inputNames.add (i.clientName); - inputIds.add (i.ports [i.index]); - } - } + 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.clientName != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (i.clientName)) - { - outputNames.add (i.clientName); - outputIds.add (i.ports [i.index]); - } - } + if (i.getClientName() != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (i.getClientName())) + outputNames.add (i.getClientName()); juce::jack_client_close (client); } @@ -581,8 +639,8 @@ public: jassert (hasScanned); // need to call scanForDevices() before doing this if (JackAudioIODevice* d = dynamic_cast (device)) - return asInput ? inputIds.indexOf (d->inputId) - : outputIds.indexOf (d->outputId); + return asInput ? inputNames.indexOf (d->inputName) + : outputNames.indexOf (d->outputName); return -1; } @@ -596,30 +654,19 @@ public: const int outputIndex = outputNames.indexOf (outputDeviceName); if (inputIndex >= 0 || outputIndex >= 0) - return new JackAudioIODevice (outputIndex >= 0 ? outputDeviceName - : inputDeviceName, - inputIds [inputIndex], - outputIds [outputIndex]); + return new JackAudioIODevice (inputDeviceName, outputDeviceName, + [this] { callDeviceChangeListeners(); }); return nullptr; } - void portConnectionChange() { callDeviceChangeListeners(); } - private: - StringArray inputNames, outputNames, inputIds, outputIds; - bool hasScanned; + StringArray inputNames, outputNames; + bool hasScanned = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType) }; -void JackAudioIODevice::sendDeviceChangedCallback() -{ - for (int i = activeDeviceTypes.size(); --i >= 0;) - if (JackAudioIODeviceType* d = activeDeviceTypes[i]) - d->portConnectionChange(); -} - //============================================================================== AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { diff --git a/modules/juce_dsp/containers/juce_SIMDRegister_test.cpp b/modules/juce_dsp/containers/juce_SIMDRegister_test.cpp index 5435330ea6..1e26f7fe4f 100644 --- a/modules/juce_dsp/containers/juce_SIMDRegister_test.cpp +++ b/modules/juce_dsp/containers/juce_SIMDRegister_test.cpp @@ -750,7 +750,7 @@ public: copy (a, inArray); a = SIMDRegister::abs (a); - auto calcAbs = [] (type x) -> type { return x >= type (0) ? x : -x; }; + auto calcAbs = [] (type x) -> type { return x >= type (0) ? x : type (-x); }; for (size_t j = 0; j < SIMDRegister::SIMDNumElements; ++j) outArray[j] = calcAbs (inArray[j]);