From 20731ed246fb664abe7e16c0bcb38ae79f6e3912 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Mon, 12 May 2025 17:46:53 +0200 Subject: [PATCH 01/21] Incomplete input selection via combo box Add two combo boxes and a Jack client to the Jack setup dialog. The combo boxes display the available Jack inputs. Please note that in Jack terminology these are called "outputs". The own Jack client is necessary because the setup dialog does not have any access to the actual driver. So a new client is created when the dialog is opened and deleted when it is closed, i.e. when the dialog is deleted itself. The available inputs are collected via `AudioJack::setupWidget::getAudioInputNames` and are then put into the combo boxes via `AudioJack::setupWidget::populateComboBox`. `AudioJack::setupWidget::saveSettings` saves the selections that have been made in the combo boxes into the configuration. Add the method `handleRegistrationEvent` to `AudioJack` and register it in `AudioJack::initJackClient` via `jack_set_port_registration_callback`. Currently it will only output information about the port via `printf` but not do anything else. It can likely be removed again because the `AudioJack::setupWidget` uses it's own client and must register its own callbacks in case it wants to react to changed inputs and outputs while the setup dialog is open. * Do the same for the inputs * Read the information stored in the configuration when the driver is initialized. Gracefully handle when selections are not available, e.g. because the device is not plugged in. * Decide if the setup dialog should react to changed ports while it is open. This might complicate selections that are currently made, i.e. it has to be ensured that the user experience is good. * Remove printf statements --- include/AudioJack.h | 12 ++++++ src/core/audio/AudioJack.cpp | 84 ++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/include/AudioJack.h b/include/AudioJack.h index ab87e97f6e9..9d85151a253 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -47,6 +47,7 @@ #endif class QLineEdit; +class QComboBox; namespace lmms { @@ -68,6 +69,8 @@ class AudioJack : public QObject, public AudioDevice void removeMidiClient() { m_midiClient = nullptr; } jack_client_t* jackClient() { return m_client; }; + void handleRegistrationEvent(jack_port_id_t port, int reg); + inline static QString name() { return QT_TRANSLATE_NOOP("AudioDeviceSetupWidget", "JACK (JACK Audio Connection Kit)"); @@ -79,8 +82,17 @@ class AudioJack : public QObject, public AudioDevice setupWidget(QWidget* parent); void saveSettings() override; + private: + std::vector getAudioInputNames() const; + void populateComboBox(QComboBox* comboBox, const std::vector& inputNames); + private: QLineEdit* m_clientName; + // Because we do not have access to a JackAudio driver instance we have to be our own client to display inputs and outputs... + jack_client_t* m_client; + + QComboBox* m_inputDevice1 = nullptr; + QComboBox* m_inputDevice2 = nullptr; }; private slots: diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 429c4ddcb85..c7d8606c95a 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -26,9 +26,11 @@ #ifdef LMMS_HAVE_JACK +#include #include #include #include +#include #include "AudioEngine.h" #include "ConfigManager.h" @@ -128,6 +130,19 @@ AudioJack* AudioJack::addMidiClient(MidiJack* midiClient) } +void AudioJack::handleRegistrationEvent(jack_port_id_t port, int reg) +{ + auto jackPort = jack_port_by_id(m_client, port); + + auto name = jack_port_name(jackPort); + auto type = jack_port_type(jackPort); + auto flags = jack_port_flags(jackPort); + + const bool isAudioPort = strcmp(type, JACK_DEFAULT_AUDIO_TYPE) == 0; + const bool isInputDevice = flags & JackPortIsOutput; + + printf("Reg code %d for name %s and type %s, audio: %d, input: %d\n", reg, name, type, isAudioPort, isInputDevice); +} bool AudioJack::initJackClient() @@ -167,6 +182,13 @@ bool AudioJack::initJackClient() // set shutdown-callback jack_on_shutdown(m_client, shutdownCallback, this); + jack_set_port_registration_callback(m_client, + [](jack_port_id_t port, int reg, void *arg) { + auto audioJack = static_cast(arg); + audioJack->handleRegistrationEvent(port, reg); + }, + this); + if (jack_get_sample_rate(m_client) != sampleRate()) { setSampleRate(jack_get_sample_rate(m_client)); } for (ch_cnt_t ch = 0; ch < channels(); ++ch) @@ -411,6 +433,10 @@ void AudioJack::shutdownCallback(void* udata) AudioJack::setupWidget::setupWidget(QWidget* parent) : AudioDeviceSetupWidget(AudioJack::name(), parent) { + const char* serverName = nullptr; + jack_status_t status; + m_client = jack_client_open("LMMS-Setup Dialog", JackNullOption, &status, serverName); + QFormLayout * form = new QFormLayout(this); QString cn = ConfigManager::inst()->value("audiojack", "clientname"); @@ -418,6 +444,25 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) m_clientName = new QLineEdit(cn, this); form->addRow(tr("Client name"), m_clientName); + + const auto audioInputNames = getAudioInputNames(); + + m_inputDevice1 = new QComboBox(this); + + populateComboBox(m_inputDevice1, audioInputNames); + + form->addRow(tr("Input 1"), m_inputDevice1); + + m_inputDevice2 = new QComboBox(this); + + populateComboBox(m_inputDevice2, audioInputNames); + + form->addRow(tr("Input 2"), m_inputDevice2); + if (m_client != nullptr) + { + jack_deactivate(m_client); + jack_client_close(m_client); + } } @@ -426,6 +471,45 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) void AudioJack::setupWidget::saveSettings() { ConfigManager::inst()->setValue("audiojack", "clientname", m_clientName->text()); + ConfigManager::inst()->setValue("audiojack", "input1", m_inputDevice1->currentText()); + ConfigManager::inst()->setValue("audiojack", "input2", m_inputDevice2->currentText()); +} + +std::vector AudioJack::setupWidget::getAudioInputNames() const +{ + std::vector audioInputs; + + const char **inputAudioPorts = jack_get_ports(m_client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput); + if (inputAudioPorts) + { + for (int i = 0; inputAudioPorts[i] != nullptr; ++i) + { + auto currentPortName = inputAudioPorts[i]; + printf("Port %d: %s\n", i, currentPortName); + + audioInputs.push_back(currentPortName); + } + jack_free(inputAudioPorts); // Remember to free after use + } + + return audioInputs; +} + +void AudioJack::setupWidget::populateComboBox(QComboBox* comboBox, const std::vector& inputNames) +{ + QStringList playbackDevices; + for (const auto & inputName : inputNames) + { + playbackDevices.append(QString::fromStdString(inputName)); + } + + //playbackDevices.sort(); + + comboBox->addItems(playbackDevices); + + // TODO Select device from configuration + // const auto playbackDevice = ConfigManager::inst()->value(SectionSDL, PlaybackDeviceSDL); + // m_playbackDeviceComboBox->setCurrentText(playbackDevice.isEmpty() ? s_systemDefaultDevice : playbackDevice); } From 490f6316cee02b0dfa84b35ec117809676816587 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 31 May 2025 10:04:53 +0200 Subject: [PATCH 02/21] Add combo boxes for Jack outputs Extend the Jack setup dialog with combo boxes that present the available outputs. Save the selected outputs in the configuration. Add `AudioJack::setupWidget::getAudioPortNames` which takes the type of port and then collects all port names which match. Make the new `getAudioOutputNames` and `getAudioInputNames` delegate to that method with the appropriate type. This also hides the different terminologies a bit. --- include/AudioJack.h | 5 +++++ src/core/audio/AudioJack.cpp | 38 +++++++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index 9d85151a253..5afc353edb0 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -83,7 +83,9 @@ class AudioJack : public QObject, public AudioDevice void saveSettings() override; private: + std::vector getAudioPortNames(JackPortFlags portFlags) const; std::vector getAudioInputNames() const; + std::vector getAudioOutputNames() const; void populateComboBox(QComboBox* comboBox, const std::vector& inputNames); private: @@ -91,6 +93,9 @@ class AudioJack : public QObject, public AudioDevice // Because we do not have access to a JackAudio driver instance we have to be our own client to display inputs and outputs... jack_client_t* m_client; + QComboBox* m_outputDevice1 = nullptr; + QComboBox* m_outputDevice2 = nullptr; + QComboBox* m_inputDevice1 = nullptr; QComboBox* m_inputDevice2 = nullptr; }; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index c7d8606c95a..cb7f49d3b71 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -445,6 +445,22 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) form->addRow(tr("Client name"), m_clientName); + // Outputs + const auto audioOutputNames = getAudioOutputNames(); + + m_outputDevice1 = new QComboBox(this); + + populateComboBox(m_outputDevice1, audioOutputNames); + + form->addRow(tr("Output 1"), m_outputDevice1); + + m_outputDevice2 = new QComboBox(this); + + populateComboBox(m_outputDevice2, audioOutputNames); + + form->addRow(tr("Output 2"), m_outputDevice2); + + // Inputs const auto audioInputNames = getAudioInputNames(); m_inputDevice1 = new QComboBox(this); @@ -471,15 +487,17 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) void AudioJack::setupWidget::saveSettings() { ConfigManager::inst()->setValue("audiojack", "clientname", m_clientName->text()); + ConfigManager::inst()->setValue("audiojack", "output1", m_outputDevice1->currentText()); + ConfigManager::inst()->setValue("audiojack", "output2", m_outputDevice2->currentText()); ConfigManager::inst()->setValue("audiojack", "input1", m_inputDevice1->currentText()); ConfigManager::inst()->setValue("audiojack", "input2", m_inputDevice2->currentText()); } -std::vector AudioJack::setupWidget::getAudioInputNames() const +std::vector AudioJack::setupWidget::getAudioPortNames(JackPortFlags portFlags) const { - std::vector audioInputs; + std::vector audioPorts; - const char **inputAudioPorts = jack_get_ports(m_client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput); + const char **inputAudioPorts = jack_get_ports(m_client, nullptr, JACK_DEFAULT_AUDIO_TYPE, portFlags); if (inputAudioPorts) { for (int i = 0; inputAudioPorts[i] != nullptr; ++i) @@ -487,12 +505,22 @@ std::vector AudioJack::setupWidget::getAudioInputNames() const auto currentPortName = inputAudioPorts[i]; printf("Port %d: %s\n", i, currentPortName); - audioInputs.push_back(currentPortName); + audioPorts.push_back(currentPortName); } jack_free(inputAudioPorts); // Remember to free after use } - return audioInputs; + return audioPorts; +} + +std::vector AudioJack::setupWidget::getAudioOutputNames() const +{ + return getAudioPortNames(JackPortIsInput); +} + +std::vector AudioJack::setupWidget::getAudioInputNames() const +{ + return getAudioPortNames(JackPortIsOutput); } void AudioJack::setupWidget::populateComboBox(QComboBox* comboBox, const std::vector& inputNames) From de5c328e0a12bb65cc332d14058bb94ef8dbe7fc Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 31 May 2025 13:05:22 +0200 Subject: [PATCH 03/21] Reconnect inputs and outputs Attempt to reconnect the inputs and outputs from the configuration during startup of the Jack driver. Nothing will be done for inputs and outputs that are not available at startup. Example: the users might have saved some inputs when a device was available. The device is then disconnected and LMMS restarted. The stored inputs cannot be used anymore. To give the users the least surprise nothing is done. `AudioJack::attemptToConnect` does the actual reconnection and also prints some information for now. `attemptToReconnectOutput` and `attemptToReconnectInput` delegate to `attemptToConnect` with the right parameters. --- include/AudioJack.h | 4 +++ src/core/audio/AudioJack.cpp | 56 ++++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index 5afc353edb0..981d8609b84 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -107,6 +107,10 @@ private slots: bool initJackClient(); void resizeInputBuffer(jack_nframes_t nframes); + void attemptToConnect(size_t index, const char *lmms_port_type, const char *source_port, const char *destination_port); + void attemptToReconnectOutput(size_t outputIndex, const QString& targetPort); + void attemptToReconnectInput(size_t inputIndex, const QString& sourcePort); + void startProcessing() override; void stopProcessing() override; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index cb7f49d3b71..85e5433aba2 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -218,7 +218,38 @@ void AudioJack::resizeInputBuffer(jack_nframes_t nframes) m_inputFrameBuffer.resize(nframes); } +void AudioJack::attemptToConnect(size_t index, const char *lmms_port_type, const char *source_port, const char *destination_port) +{ + printf("Attempting to reconnect %s port %u: %s -> %s", lmms_port_type, static_cast(index), source_port, destination_port); + if (!jack_connect(m_client, source_port, destination_port)) + { + printf(" - Success!\n"); + } + else + { + printf(" - Failure\n"); + } +} + +void AudioJack::attemptToReconnectOutput(size_t outputIndex, const QString& targetPort) +{ + if (outputIndex > m_outputPorts.size()) return; + + auto outputName = jack_port_name(m_outputPorts[outputIndex]); + auto targetName = targetPort.toLatin1().constData(); + + attemptToConnect(outputIndex, "output", outputName, targetName); +} + +void AudioJack::attemptToReconnectInput(size_t inputIndex, const QString& sourcePort) +{ + if (inputIndex > m_inputPorts.size()) return; + auto inputName = jack_port_name(m_inputPorts[inputIndex]); + auto sourceName = sourcePort.toLatin1().constData(); + + attemptToConnect(inputIndex, "input", sourceName, inputName); +} void AudioJack::startProcessing() @@ -240,26 +271,15 @@ void AudioJack::startProcessing() // try to sync JACK's and LMMS's buffer-size // jack_set_buffer_size( m_client, audioEngine()->framesPerPeriod() ); - const char** ports = jack_get_ports(m_client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput); - if (ports == nullptr) - { - printf("no physical playback ports. you'll have to do " - "connections at your own!\n"); - } - else - { - for (ch_cnt_t ch = 0; ch < channels(); ++ch) - { - if (jack_connect(m_client, jack_port_name(m_outputPorts[ch]), ports[ch])) - { - printf("cannot connect output ports. you'll " - "have to do connections at your own!\n"); - } - } - } + const auto cm = ConfigManager::inst(); + + attemptToReconnectOutput(0, cm->value("audiojack", "output1")); + attemptToReconnectOutput(1, cm->value("audiojack", "output2")); + + attemptToReconnectInput(0, cm->value("audiojack", "input1")); + attemptToReconnectInput(1, cm->value("audiojack", "input2")); m_stopped = false; - jack_free(ports); } From 67a17c71fbc14bb06d23a8a8c48cf2dfdb3e47a6 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 31 May 2025 14:27:40 +0200 Subject: [PATCH 04/21] Init combo boxes with current input/output When populating the combo boxes select the current input/output as saved in the configuration. --- include/AudioJack.h | 2 +- src/core/audio/AudioJack.cpp | 31 ++++++++++++------------------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index 981d8609b84..c4aaff85062 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -86,7 +86,7 @@ class AudioJack : public QObject, public AudioDevice std::vector getAudioPortNames(JackPortFlags portFlags) const; std::vector getAudioInputNames() const; std::vector getAudioOutputNames() const; - void populateComboBox(QComboBox* comboBox, const std::vector& inputNames); + void populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry); private: QLineEdit* m_clientName; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 85e5433aba2..527fab78e52 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -459,7 +459,8 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) QFormLayout * form = new QFormLayout(this); - QString cn = ConfigManager::inst()->value("audiojack", "clientname"); + const auto cm = ConfigManager::inst(); + QString cn = cm->value("audiojack", "clientname"); if (cn.isEmpty()) { cn = "lmms"; } m_clientName = new QLineEdit(cn, this); @@ -469,30 +470,26 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) const auto audioOutputNames = getAudioOutputNames(); m_outputDevice1 = new QComboBox(this); - - populateComboBox(m_outputDevice1, audioOutputNames); - + const auto output1 = cm->value("audiojack", "output1"); + populateComboBox(m_outputDevice1, audioOutputNames, output1); form->addRow(tr("Output 1"), m_outputDevice1); m_outputDevice2 = new QComboBox(this); - - populateComboBox(m_outputDevice2, audioOutputNames); - + const auto output2 = cm->value("audiojack", "output2"); + populateComboBox(m_outputDevice2, audioOutputNames, output2); form->addRow(tr("Output 2"), m_outputDevice2); // Inputs const auto audioInputNames = getAudioInputNames(); m_inputDevice1 = new QComboBox(this); - - populateComboBox(m_inputDevice1, audioInputNames); - + const auto input1 = cm->value("audiojack", "input1"); + populateComboBox(m_inputDevice1, audioInputNames, input1); form->addRow(tr("Input 1"), m_inputDevice1); m_inputDevice2 = new QComboBox(this); - - populateComboBox(m_inputDevice2, audioInputNames); - + const auto input2 = cm->value("audiojack", "input2"); + populateComboBox(m_inputDevice2, audioInputNames, input2); form->addRow(tr("Input 2"), m_inputDevice2); if (m_client != nullptr) { @@ -543,7 +540,7 @@ std::vector AudioJack::setupWidget::getAudioInputNames() const return getAudioPortNames(JackPortIsOutput); } -void AudioJack::setupWidget::populateComboBox(QComboBox* comboBox, const std::vector& inputNames) +void AudioJack::setupWidget::populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry) { QStringList playbackDevices; for (const auto & inputName : inputNames) @@ -551,13 +548,9 @@ void AudioJack::setupWidget::populateComboBox(QComboBox* comboBox, const std::ve playbackDevices.append(QString::fromStdString(inputName)); } - //playbackDevices.sort(); - comboBox->addItems(playbackDevices); - // TODO Select device from configuration - // const auto playbackDevice = ConfigManager::inst()->value(SectionSDL, PlaybackDeviceSDL); - // m_playbackDeviceComboBox->setCurrentText(playbackDevice.isEmpty() ? s_systemDefaultDevice : playbackDevice); + comboBox->setCurrentText(selectedEntry); } From 2ea1eb6a87286a8f38d77a57ae02b39f9d7adffa Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 31 May 2025 14:53:25 +0200 Subject: [PATCH 05/21] Show warning message for missing ports Show a warning message in the Jack setup dialog if some inputs or outputs that are set in the configuration are missing in the system. This might for example be caused by disconnected devices. Let `populateComboBox` return a boolean which indicates if the selected port can be found in the list of displayed ports or not. --- include/AudioJack.h | 2 +- src/core/audio/AudioJack.cpp | 25 ++++++++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index c4aaff85062..db73a89301f 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -86,7 +86,7 @@ class AudioJack : public QObject, public AudioDevice std::vector getAudioPortNames(JackPortFlags portFlags) const; std::vector getAudioInputNames() const; std::vector getAudioOutputNames() const; - void populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry); + bool populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry); private: QLineEdit* m_clientName; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 527fab78e52..6efd7321537 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -457,7 +458,9 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) jack_status_t status; m_client = jack_client_open("LMMS-Setup Dialog", JackNullOption, &status, serverName); + QVBoxLayout* mainLayout = new QVBoxLayout(this); QFormLayout * form = new QFormLayout(this); + mainLayout->addLayout(form); const auto cm = ConfigManager::inst(); QString cn = cm->value("audiojack", "clientname"); @@ -466,17 +469,22 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) form->addRow(tr("Client name"), m_clientName); + // This variable stores if all ports from the configuration + // could be found in the current system, i.e. that no device + // has been disconnected during invocations of LMMS. + bool allPortsFound = true; + // Outputs const auto audioOutputNames = getAudioOutputNames(); m_outputDevice1 = new QComboBox(this); const auto output1 = cm->value("audiojack", "output1"); - populateComboBox(m_outputDevice1, audioOutputNames, output1); + allPortsFound &= populateComboBox(m_outputDevice1, audioOutputNames, output1); form->addRow(tr("Output 1"), m_outputDevice1); m_outputDevice2 = new QComboBox(this); const auto output2 = cm->value("audiojack", "output2"); - populateComboBox(m_outputDevice2, audioOutputNames, output2); + allPortsFound &= populateComboBox(m_outputDevice2, audioOutputNames, output2); form->addRow(tr("Output 2"), m_outputDevice2); // Inputs @@ -484,13 +492,18 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) m_inputDevice1 = new QComboBox(this); const auto input1 = cm->value("audiojack", "input1"); - populateComboBox(m_inputDevice1, audioInputNames, input1); + allPortsFound &= populateComboBox(m_inputDevice1, audioInputNames, input1); form->addRow(tr("Input 1"), m_inputDevice1); m_inputDevice2 = new QComboBox(this); const auto input2 = cm->value("audiojack", "input2"); - populateComboBox(m_inputDevice2, audioInputNames, input2); + allPortsFound &= populateComboBox(m_inputDevice2, audioInputNames, input2); form->addRow(tr("Input 2"), m_inputDevice2); + + if (!allPortsFound) + { + mainLayout->addWidget(new QLabel(tr("Some inputs/outputs could not be found and have been reset!"), this)); + } if (m_client != nullptr) { jack_deactivate(m_client); @@ -540,7 +553,7 @@ std::vector AudioJack::setupWidget::getAudioInputNames() const return getAudioPortNames(JackPortIsOutput); } -void AudioJack::setupWidget::populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry) +bool AudioJack::setupWidget::populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry) { QStringList playbackDevices; for (const auto & inputName : inputNames) @@ -551,6 +564,8 @@ void AudioJack::setupWidget::populateComboBox(QComboBox* comboBox, const std::ve comboBox->addItems(playbackDevices); comboBox->setCurrentText(selectedEntry); + + return comboBox->findText(selectedEntry) >= 0; } From 0aa3d311fab29e5e5b7f0247ceac0036aacffce8 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 31 May 2025 14:56:04 +0200 Subject: [PATCH 06/21] Remove `handleRegistrationEvent` Remove `AudioJack::handleRegistrationEvent` as it was only used for debugging so far. --- include/AudioJack.h | 2 -- src/core/audio/AudioJack.cpp | 22 ---------------------- 2 files changed, 24 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index db73a89301f..82971e06fa6 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -69,8 +69,6 @@ class AudioJack : public QObject, public AudioDevice void removeMidiClient() { m_midiClient = nullptr; } jack_client_t* jackClient() { return m_client; }; - void handleRegistrationEvent(jack_port_id_t port, int reg); - inline static QString name() { return QT_TRANSLATE_NOOP("AudioDeviceSetupWidget", "JACK (JACK Audio Connection Kit)"); diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 6efd7321537..0c879e11ee4 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -131,21 +131,6 @@ AudioJack* AudioJack::addMidiClient(MidiJack* midiClient) } -void AudioJack::handleRegistrationEvent(jack_port_id_t port, int reg) -{ - auto jackPort = jack_port_by_id(m_client, port); - - auto name = jack_port_name(jackPort); - auto type = jack_port_type(jackPort); - auto flags = jack_port_flags(jackPort); - - const bool isAudioPort = strcmp(type, JACK_DEFAULT_AUDIO_TYPE) == 0; - const bool isInputDevice = flags & JackPortIsOutput; - - printf("Reg code %d for name %s and type %s, audio: %d, input: %d\n", reg, name, type, isAudioPort, isInputDevice); -} - - bool AudioJack::initJackClient() { QString clientName = ConfigManager::inst()->value("audiojack", "clientname"); @@ -183,13 +168,6 @@ bool AudioJack::initJackClient() // set shutdown-callback jack_on_shutdown(m_client, shutdownCallback, this); - jack_set_port_registration_callback(m_client, - [](jack_port_id_t port, int reg, void *arg) { - auto audioJack = static_cast(arg); - audioJack->handleRegistrationEvent(port, reg); - }, - this); - if (jack_get_sample_rate(m_client) != sampleRate()) { setSampleRate(jack_get_sample_rate(m_client)); } for (ch_cnt_t ch = 0; ch < channels(); ++ch) From a886aa3c7907f0923a532c7fac4355ace7ab579e Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 31 May 2025 15:04:04 +0200 Subject: [PATCH 07/21] Replace repeated hard-coded strings Replace repeated hard-coded strings with references to static constant variables in the anonymous namespace. This should prevent subtle mistakes when working with the configuration values of the Jack driver. --- src/core/audio/AudioJack.cpp | 41 +++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 0c879e11ee4..ab4657bb204 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -40,6 +40,17 @@ #include "MainWindow.h" #include "MidiJack.h" + +namespace +{ +static const QString audioJackClass("audiojack"); +static const QString clientNameKey("clientname"); +static const QString output1Key("output1"); +static const QString output2Key("output2"); +static const QString input1Key("input1"); +static const QString input2Key("input2"); +} + namespace lmms { @@ -133,7 +144,7 @@ AudioJack* AudioJack::addMidiClient(MidiJack* midiClient) bool AudioJack::initJackClient() { - QString clientName = ConfigManager::inst()->value("audiojack", "clientname"); + QString clientName = ConfigManager::inst()->value(audioJackClass, clientNameKey); if (clientName.isEmpty()) { clientName = "lmms"; } const char* serverName = nullptr; @@ -252,11 +263,11 @@ void AudioJack::startProcessing() const auto cm = ConfigManager::inst(); - attemptToReconnectOutput(0, cm->value("audiojack", "output1")); - attemptToReconnectOutput(1, cm->value("audiojack", "output2")); + attemptToReconnectOutput(0, cm->value(audioJackClass, output1Key)); + attemptToReconnectOutput(1, cm->value(audioJackClass, output2Key)); - attemptToReconnectInput(0, cm->value("audiojack", "input1")); - attemptToReconnectInput(1, cm->value("audiojack", "input2")); + attemptToReconnectInput(0, cm->value(audioJackClass, input1Key)); + attemptToReconnectInput(1, cm->value(audioJackClass, input2Key)); m_stopped = false; } @@ -441,7 +452,7 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) mainLayout->addLayout(form); const auto cm = ConfigManager::inst(); - QString cn = cm->value("audiojack", "clientname"); + QString cn = cm->value(audioJackClass, clientNameKey); if (cn.isEmpty()) { cn = "lmms"; } m_clientName = new QLineEdit(cn, this); @@ -456,12 +467,12 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) const auto audioOutputNames = getAudioOutputNames(); m_outputDevice1 = new QComboBox(this); - const auto output1 = cm->value("audiojack", "output1"); + const auto output1 = cm->value(audioJackClass, output1Key); allPortsFound &= populateComboBox(m_outputDevice1, audioOutputNames, output1); form->addRow(tr("Output 1"), m_outputDevice1); m_outputDevice2 = new QComboBox(this); - const auto output2 = cm->value("audiojack", "output2"); + const auto output2 = cm->value(audioJackClass, output2Key); allPortsFound &= populateComboBox(m_outputDevice2, audioOutputNames, output2); form->addRow(tr("Output 2"), m_outputDevice2); @@ -469,12 +480,12 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) const auto audioInputNames = getAudioInputNames(); m_inputDevice1 = new QComboBox(this); - const auto input1 = cm->value("audiojack", "input1"); + const auto input1 = cm->value(audioJackClass, input1Key); allPortsFound &= populateComboBox(m_inputDevice1, audioInputNames, input1); form->addRow(tr("Input 1"), m_inputDevice1); m_inputDevice2 = new QComboBox(this); - const auto input2 = cm->value("audiojack", "input2"); + const auto input2 = cm->value(audioJackClass, input2Key); allPortsFound &= populateComboBox(m_inputDevice2, audioInputNames, input2); form->addRow(tr("Input 2"), m_inputDevice2); @@ -494,11 +505,11 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) void AudioJack::setupWidget::saveSettings() { - ConfigManager::inst()->setValue("audiojack", "clientname", m_clientName->text()); - ConfigManager::inst()->setValue("audiojack", "output1", m_outputDevice1->currentText()); - ConfigManager::inst()->setValue("audiojack", "output2", m_outputDevice2->currentText()); - ConfigManager::inst()->setValue("audiojack", "input1", m_inputDevice1->currentText()); - ConfigManager::inst()->setValue("audiojack", "input2", m_inputDevice2->currentText()); + ConfigManager::inst()->setValue(audioJackClass, clientNameKey, m_clientName->text()); + ConfigManager::inst()->setValue(audioJackClass, output1Key, m_outputDevice1->currentText()); + ConfigManager::inst()->setValue(audioJackClass, output2Key, m_outputDevice2->currentText()); + ConfigManager::inst()->setValue(audioJackClass, input1Key, m_inputDevice1->currentText()); + ConfigManager::inst()->setValue(audioJackClass, input2Key, m_inputDevice2->currentText()); } std::vector AudioJack::setupWidget::getAudioPortNames(JackPortFlags portFlags) const From e8f9bdc0b567154ad3614642920ff74f0f42bc51 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 31 May 2025 18:40:40 +0200 Subject: [PATCH 08/21] Fix whitespace --- src/core/audio/AudioJack.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index ab4657bb204..6ee6c152996 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -493,6 +493,7 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) { mainLayout->addWidget(new QLabel(tr("Some inputs/outputs could not be found and have been reset!"), this)); } + if (m_client != nullptr) { jack_deactivate(m_client); From 09004e53ce7a7a59d41083370afc948f4c27e983 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 31 May 2025 18:45:02 +0200 Subject: [PATCH 09/21] Remove input selection for now Input selection does not make sense for master now because recording is not implemented/merged yet. However, being able to select the inputs from a combo box instead of having to use an external connection manager already brings benefit to users. Hence we remove input selection for now. There will be a separate branch/commit which will revert this commit, i.e. which will add input selection again once it makes sense for master. --- include/AudioJack.h | 5 ----- src/core/audio/AudioJack.cpp | 36 ------------------------------------ 2 files changed, 41 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index 82971e06fa6..6355b0bdbf6 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -82,7 +82,6 @@ class AudioJack : public QObject, public AudioDevice private: std::vector getAudioPortNames(JackPortFlags portFlags) const; - std::vector getAudioInputNames() const; std::vector getAudioOutputNames() const; bool populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry); @@ -93,9 +92,6 @@ class AudioJack : public QObject, public AudioDevice QComboBox* m_outputDevice1 = nullptr; QComboBox* m_outputDevice2 = nullptr; - - QComboBox* m_inputDevice1 = nullptr; - QComboBox* m_inputDevice2 = nullptr; }; private slots: @@ -107,7 +103,6 @@ private slots: void attemptToConnect(size_t index, const char *lmms_port_type, const char *source_port, const char *destination_port); void attemptToReconnectOutput(size_t outputIndex, const QString& targetPort); - void attemptToReconnectInput(size_t inputIndex, const QString& sourcePort); void startProcessing() override; void stopProcessing() override; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 6ee6c152996..3af5cb15229 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -47,8 +47,6 @@ static const QString audioJackClass("audiojack"); static const QString clientNameKey("clientname"); static const QString output1Key("output1"); static const QString output2Key("output2"); -static const QString input1Key("input1"); -static const QString input2Key("input2"); } namespace lmms @@ -231,17 +229,6 @@ void AudioJack::attemptToReconnectOutput(size_t outputIndex, const QString& targ attemptToConnect(outputIndex, "output", outputName, targetName); } -void AudioJack::attemptToReconnectInput(size_t inputIndex, const QString& sourcePort) -{ - if (inputIndex > m_inputPorts.size()) return; - - auto inputName = jack_port_name(m_inputPorts[inputIndex]); - auto sourceName = sourcePort.toLatin1().constData(); - - attemptToConnect(inputIndex, "input", sourceName, inputName); -} - - void AudioJack::startProcessing() { if (m_active || m_client == nullptr) @@ -266,9 +253,6 @@ void AudioJack::startProcessing() attemptToReconnectOutput(0, cm->value(audioJackClass, output1Key)); attemptToReconnectOutput(1, cm->value(audioJackClass, output2Key)); - attemptToReconnectInput(0, cm->value(audioJackClass, input1Key)); - attemptToReconnectInput(1, cm->value(audioJackClass, input2Key)); - m_stopped = false; } @@ -476,19 +460,6 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) allPortsFound &= populateComboBox(m_outputDevice2, audioOutputNames, output2); form->addRow(tr("Output 2"), m_outputDevice2); - // Inputs - const auto audioInputNames = getAudioInputNames(); - - m_inputDevice1 = new QComboBox(this); - const auto input1 = cm->value(audioJackClass, input1Key); - allPortsFound &= populateComboBox(m_inputDevice1, audioInputNames, input1); - form->addRow(tr("Input 1"), m_inputDevice1); - - m_inputDevice2 = new QComboBox(this); - const auto input2 = cm->value(audioJackClass, input2Key); - allPortsFound &= populateComboBox(m_inputDevice2, audioInputNames, input2); - form->addRow(tr("Input 2"), m_inputDevice2); - if (!allPortsFound) { mainLayout->addWidget(new QLabel(tr("Some inputs/outputs could not be found and have been reset!"), this)); @@ -509,8 +480,6 @@ void AudioJack::setupWidget::saveSettings() ConfigManager::inst()->setValue(audioJackClass, clientNameKey, m_clientName->text()); ConfigManager::inst()->setValue(audioJackClass, output1Key, m_outputDevice1->currentText()); ConfigManager::inst()->setValue(audioJackClass, output2Key, m_outputDevice2->currentText()); - ConfigManager::inst()->setValue(audioJackClass, input1Key, m_inputDevice1->currentText()); - ConfigManager::inst()->setValue(audioJackClass, input2Key, m_inputDevice2->currentText()); } std::vector AudioJack::setupWidget::getAudioPortNames(JackPortFlags portFlags) const @@ -538,11 +507,6 @@ std::vector AudioJack::setupWidget::getAudioOutputNames() const return getAudioPortNames(JackPortIsInput); } -std::vector AudioJack::setupWidget::getAudioInputNames() const -{ - return getAudioPortNames(JackPortIsOutput); -} - bool AudioJack::setupWidget::populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry) { QStringList playbackDevices; From aa5311c21e5bb48ce78d298c0ea7a1d102de1774 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 31 May 2025 19:00:08 +0200 Subject: [PATCH 10/21] Introduce input selection Introduce input selection for the Jack driver similar to how it is done for the outputs, i.e. saving to configuration, reconnection at driver start, etc. --- include/AudioJack.h | 5 +++++ src/core/audio/AudioJack.cpp | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/include/AudioJack.h b/include/AudioJack.h index 6355b0bdbf6..82971e06fa6 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -82,6 +82,7 @@ class AudioJack : public QObject, public AudioDevice private: std::vector getAudioPortNames(JackPortFlags portFlags) const; + std::vector getAudioInputNames() const; std::vector getAudioOutputNames() const; bool populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry); @@ -92,6 +93,9 @@ class AudioJack : public QObject, public AudioDevice QComboBox* m_outputDevice1 = nullptr; QComboBox* m_outputDevice2 = nullptr; + + QComboBox* m_inputDevice1 = nullptr; + QComboBox* m_inputDevice2 = nullptr; }; private slots: @@ -103,6 +107,7 @@ private slots: void attemptToConnect(size_t index, const char *lmms_port_type, const char *source_port, const char *destination_port); void attemptToReconnectOutput(size_t outputIndex, const QString& targetPort); + void attemptToReconnectInput(size_t inputIndex, const QString& sourcePort); void startProcessing() override; void stopProcessing() override; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 3af5cb15229..6ee6c152996 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -47,6 +47,8 @@ static const QString audioJackClass("audiojack"); static const QString clientNameKey("clientname"); static const QString output1Key("output1"); static const QString output2Key("output2"); +static const QString input1Key("input1"); +static const QString input2Key("input2"); } namespace lmms @@ -229,6 +231,17 @@ void AudioJack::attemptToReconnectOutput(size_t outputIndex, const QString& targ attemptToConnect(outputIndex, "output", outputName, targetName); } +void AudioJack::attemptToReconnectInput(size_t inputIndex, const QString& sourcePort) +{ + if (inputIndex > m_inputPorts.size()) return; + + auto inputName = jack_port_name(m_inputPorts[inputIndex]); + auto sourceName = sourcePort.toLatin1().constData(); + + attemptToConnect(inputIndex, "input", sourceName, inputName); +} + + void AudioJack::startProcessing() { if (m_active || m_client == nullptr) @@ -253,6 +266,9 @@ void AudioJack::startProcessing() attemptToReconnectOutput(0, cm->value(audioJackClass, output1Key)); attemptToReconnectOutput(1, cm->value(audioJackClass, output2Key)); + attemptToReconnectInput(0, cm->value(audioJackClass, input1Key)); + attemptToReconnectInput(1, cm->value(audioJackClass, input2Key)); + m_stopped = false; } @@ -460,6 +476,19 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) allPortsFound &= populateComboBox(m_outputDevice2, audioOutputNames, output2); form->addRow(tr("Output 2"), m_outputDevice2); + // Inputs + const auto audioInputNames = getAudioInputNames(); + + m_inputDevice1 = new QComboBox(this); + const auto input1 = cm->value(audioJackClass, input1Key); + allPortsFound &= populateComboBox(m_inputDevice1, audioInputNames, input1); + form->addRow(tr("Input 1"), m_inputDevice1); + + m_inputDevice2 = new QComboBox(this); + const auto input2 = cm->value(audioJackClass, input2Key); + allPortsFound &= populateComboBox(m_inputDevice2, audioInputNames, input2); + form->addRow(tr("Input 2"), m_inputDevice2); + if (!allPortsFound) { mainLayout->addWidget(new QLabel(tr("Some inputs/outputs could not be found and have been reset!"), this)); @@ -480,6 +509,8 @@ void AudioJack::setupWidget::saveSettings() ConfigManager::inst()->setValue(audioJackClass, clientNameKey, m_clientName->text()); ConfigManager::inst()->setValue(audioJackClass, output1Key, m_outputDevice1->currentText()); ConfigManager::inst()->setValue(audioJackClass, output2Key, m_outputDevice2->currentText()); + ConfigManager::inst()->setValue(audioJackClass, input1Key, m_inputDevice1->currentText()); + ConfigManager::inst()->setValue(audioJackClass, input2Key, m_inputDevice2->currentText()); } std::vector AudioJack::setupWidget::getAudioPortNames(JackPortFlags portFlags) const @@ -507,6 +538,11 @@ std::vector AudioJack::setupWidget::getAudioOutputNames() const return getAudioPortNames(JackPortIsInput); } +std::vector AudioJack::setupWidget::getAudioInputNames() const +{ + return getAudioPortNames(JackPortIsOutput); +} + bool AudioJack::setupWidget::populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry) { QStringList playbackDevices; From 1a79242bf4a688387e21db93ef598a683b2d8462 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sun, 1 Jun 2025 10:12:12 +0200 Subject: [PATCH 11/21] Present inputs/outputs in hierarchical menu Present the available inputs and outputs in a hierarchical sorted menu which shows clients with their ports. The heavy lifting of creating the menu for the tool button is done in the new method `buildMenu`. It takes the input/output names in Jack's "Client name:Port name" format. If an input/output name can be successfully split into the client name and port name then a sub menu with the client name is created (if it was not already created before) and the port name is added as an entry. If the name cannot be split into exactly two components then it is simply added to the top level menu as is. Ports of the LMMS client are filtered out to prevent loops. The menu starts with the client's sub menus in alphabetical order. Then the top level entries are added in alphabetical order as well. The callbacks for the `QAction` instances are implemented with lambdas because MOC does not support nested classes like the setup widget is. For now the used lambda only sets the text of the `QToolButton` as these are used for persisting the configuration anyway. Replace the `QComboxBox` instances with `QToolButton`. --- include/AudioJack.h | 13 ++-- src/core/audio/AudioJack.cpp | 114 +++++++++++++++++++++++++---------- 2 files changed, 88 insertions(+), 39 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index 82971e06fa6..201210575d0 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -47,7 +47,8 @@ #endif class QLineEdit; -class QComboBox; +class QMenu; +class QToolButton; namespace lmms { @@ -84,18 +85,18 @@ class AudioJack : public QObject, public AudioDevice std::vector getAudioPortNames(JackPortFlags portFlags) const; std::vector getAudioInputNames() const; std::vector getAudioOutputNames() const; - bool populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry); + QMenu* buildMenu(QToolButton* toolButton, const std::vector& names, const QString& filteredLMMSClientName); private: QLineEdit* m_clientName; // Because we do not have access to a JackAudio driver instance we have to be our own client to display inputs and outputs... jack_client_t* m_client; - QComboBox* m_outputDevice1 = nullptr; - QComboBox* m_outputDevice2 = nullptr; + QToolButton* m_outputDevice1 = nullptr; + QToolButton* m_outputDevice2 = nullptr; - QComboBox* m_inputDevice1 = nullptr; - QComboBox* m_inputDevice2 = nullptr; + QToolButton* m_inputDevice1 = nullptr; + QToolButton* m_inputDevice2 = nullptr; }; private slots: diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 6ee6c152996..572e8ad7e91 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -26,11 +26,12 @@ #ifdef LMMS_HAVE_JACK -#include #include #include #include +#include #include +#include #include #include "AudioEngine.h" @@ -447,9 +448,7 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) jack_status_t status; m_client = jack_client_open("LMMS-Setup Dialog", JackNullOption, &status, serverName); - QVBoxLayout* mainLayout = new QVBoxLayout(this); QFormLayout * form = new QFormLayout(this); - mainLayout->addLayout(form); const auto cm = ConfigManager::inst(); QString cn = cm->value(audioJackClass, clientNameKey); @@ -458,42 +457,39 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) form->addRow(tr("Client name"), m_clientName); - // This variable stores if all ports from the configuration - // could be found in the current system, i.e. that no device - // has been disconnected during invocations of LMMS. - bool allPortsFound = true; + auto buildToolButton = [this](QWidget* parent, const QString& currentSelection, const std::vector& names, const QString& filteredLMMSClientName) + { + auto toolButton = new QToolButton(parent); + toolButton->setPopupMode(QToolButton::MenuButtonPopup); + toolButton->setText(currentSelection); + auto menu = this->buildMenu(toolButton, names, filteredLMMSClientName); + toolButton->setMenu(menu); + + return toolButton; + }; // Outputs const auto audioOutputNames = getAudioOutputNames(); - m_outputDevice1 = new QComboBox(this); const auto output1 = cm->value(audioJackClass, output1Key); - allPortsFound &= populateComboBox(m_outputDevice1, audioOutputNames, output1); + m_outputDevice1 = buildToolButton(this, output1, audioOutputNames, cn); form->addRow(tr("Output 1"), m_outputDevice1); - m_outputDevice2 = new QComboBox(this); const auto output2 = cm->value(audioJackClass, output2Key); - allPortsFound &= populateComboBox(m_outputDevice2, audioOutputNames, output2); + m_outputDevice2 = buildToolButton(this, output2, audioOutputNames, cn); form->addRow(tr("Output 2"), m_outputDevice2); // Inputs const auto audioInputNames = getAudioInputNames(); - m_inputDevice1 = new QComboBox(this); const auto input1 = cm->value(audioJackClass, input1Key); - allPortsFound &= populateComboBox(m_inputDevice1, audioInputNames, input1); + m_inputDevice1 = buildToolButton(this, input1, audioInputNames, cn); form->addRow(tr("Input 1"), m_inputDevice1); - m_inputDevice2 = new QComboBox(this); const auto input2 = cm->value(audioJackClass, input2Key); - allPortsFound &= populateComboBox(m_inputDevice2, audioInputNames, input2); + m_inputDevice2 = buildToolButton(this, input2, audioInputNames, cn); form->addRow(tr("Input 2"), m_inputDevice2); - if (!allPortsFound) - { - mainLayout->addWidget(new QLabel(tr("Some inputs/outputs could not be found and have been reset!"), this)); - } - if (m_client != nullptr) { jack_deactivate(m_client); @@ -502,15 +498,13 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) } - - void AudioJack::setupWidget::saveSettings() { ConfigManager::inst()->setValue(audioJackClass, clientNameKey, m_clientName->text()); - ConfigManager::inst()->setValue(audioJackClass, output1Key, m_outputDevice1->currentText()); - ConfigManager::inst()->setValue(audioJackClass, output2Key, m_outputDevice2->currentText()); - ConfigManager::inst()->setValue(audioJackClass, input1Key, m_inputDevice1->currentText()); - ConfigManager::inst()->setValue(audioJackClass, input2Key, m_inputDevice2->currentText()); + ConfigManager::inst()->setValue(audioJackClass, output1Key, m_outputDevice1->text()); + ConfigManager::inst()->setValue(audioJackClass, output2Key, m_outputDevice2->text()); + ConfigManager::inst()->setValue(audioJackClass, input1Key, m_inputDevice1->text()); + ConfigManager::inst()->setValue(audioJackClass, input2Key, m_inputDevice2->text()); } std::vector AudioJack::setupWidget::getAudioPortNames(JackPortFlags portFlags) const @@ -543,19 +537,73 @@ std::vector AudioJack::setupWidget::getAudioInputNames() const return getAudioPortNames(JackPortIsOutput); } -bool AudioJack::setupWidget::populateComboBox(QComboBox* comboBox, const std::vector& inputNames, const QString& selectedEntry) +QMenu* AudioJack::setupWidget::buildMenu(QToolButton* toolButton, const std::vector& names, const QString& filteredLMMSClientName) { - QStringList playbackDevices; - for (const auto & inputName : inputNames) + auto menu = new QMenu(toolButton); + QMap clientNameToSubMenuMap; + QList topLevelActions; + for (const auto& currentName : names) { - playbackDevices.append(QString::fromStdString(inputName)); + const auto clientNameWithPortName = QString::fromStdString(currentName); + + auto actionLambda = [toolButton, clientNameWithPortName](bool checked) + { + toolButton->setText(clientNameWithPortName); + }; + + // Split into individual client name and port name + const auto list = clientNameWithPortName.split(":"); + if (list.size() == 2) + { + const auto& clientName = list[0]; + const auto& portName = list[1]; + + if (clientName == filteredLMMSClientName) + { + // Prevent loops by not adding port of the LMMS client to the menu + continue; + } + + QMenu* clientSubMenu = nullptr; + + auto it = clientNameToSubMenuMap.find(clientName); + if (it == clientNameToSubMenuMap.end()) + { + clientSubMenu = new QMenu(menu); + clientSubMenu->setTitle(clientName); + clientNameToSubMenuMap.insert(clientName, clientSubMenu); + } + else + { + clientSubMenu = *it; + } + + auto action = new QAction(portName, clientSubMenu); + connect(action, &QAction::triggered, actionLambda); + clientSubMenu->addAction(action); + } + else + { + // We cannot split into client and port name. Add the whole thing to the top level menu + auto action = new QAction(QString::fromStdString(currentName), menu); + connect(action, &QAction::triggered, actionLambda); + topLevelActions.append(action); + } + } - comboBox->addItems(playbackDevices); + // First add the sub menus. By iterating the map they will be sorted automatically + for (auto it = clientNameToSubMenuMap.begin(); it != clientNameToSubMenuMap.end(); ++it) + { + menu->addMenu(it.value()); + } - comboBox->setCurrentText(selectedEntry); + // Now add potential top level actions, i.e. the entries which cannot be split at exactly one ":" + // They must be sorted explicitly + std::sort(topLevelActions.begin(), topLevelActions.end(), [](QAction* a, QAction* b) { return a->text() < b->text(); }); + menu->addActions(topLevelActions); - return comboBox->findText(selectedEntry) >= 0; + return menu; } From cc6b87ba4368efad0775bcf22b561d68db5d6f92 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 14 Jun 2025 16:52:33 +0200 Subject: [PATCH 12/21] Let the tool buttons use available space Let the tool buttons stretch so that they look uniform and use all the available space. --- src/core/audio/AudioJack.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 572e8ad7e91..dd86db44488 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -450,6 +450,9 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) QFormLayout * form = new QFormLayout(this); + // Set the field growth policy to allow fields to expand horizontally + form->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); + const auto cm = ConfigManager::inst(); QString cn = cm->value(audioJackClass, clientNameKey); if (cn.isEmpty()) { cn = "lmms"; } @@ -460,6 +463,8 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) auto buildToolButton = [this](QWidget* parent, const QString& currentSelection, const std::vector& names, const QString& filteredLMMSClientName) { auto toolButton = new QToolButton(parent); + // Make sure that the tool button will fill out the available space in the form layout + toolButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); toolButton->setPopupMode(QToolButton::MenuButtonPopup); toolButton->setText(currentSelection); auto menu = this->buildMenu(toolButton, names, filteredLMMSClientName); From cd3fa37664f841d59c38fb8a81b353c6f88560fc Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 14 Jun 2025 16:59:20 +0200 Subject: [PATCH 13/21] Remove debug output Remove the printing of the audio port names in `getAudioPortNames`. --- src/core/audio/AudioJack.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index dd86db44488..883a854f205 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -522,7 +522,6 @@ std::vector AudioJack::setupWidget::getAudioPortNames(JackPortFlags for (int i = 0; inputAudioPorts[i] != nullptr; ++i) { auto currentPortName = inputAudioPorts[i]; - printf("Port %d: %s\n", i, currentPortName); audioPorts.push_back(currentPortName); } From 34a9879cd3b5aac9abb12e83ed3dd90a01adfa26 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 14 Jun 2025 17:08:46 +0200 Subject: [PATCH 14/21] Show technical output/input names Show the technical output and input port names used by LMMS in the setup dialog. Note: these are the names that are shown in tools like `qjackctl` or `qpwgraph`. This was proposed in a review. Personally I like the non-technical names better but let's see what's accepted. --- src/core/audio/AudioJack.cpp | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 883a854f205..9f9fbe0f4dc 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -55,6 +55,20 @@ static const QString input2Key("input2"); namespace lmms { +static QString buildChannelSuffix(ch_cnt_t ch) +{ + return (ch % 2 ? "R" : "L") + QString::number(ch / 2 + 1); +} + +static QString buildOutputName(ch_cnt_t ch) +{ + return QString("master out ") + buildChannelSuffix(ch); +} + +static QString buildInputName(ch_cnt_t ch) +{ + return QString("master in ") + buildChannelSuffix(ch); +} AudioJack::AudioJack(bool& successful, AudioEngine* audioEngineParam) : AudioDevice( @@ -184,11 +198,11 @@ bool AudioJack::initJackClient() for (ch_cnt_t ch = 0; ch < channels(); ++ch) { - QString name = QString("master out ") + ((ch % 2) ? "R" : "L") + QString::number(ch / 2 + 1); + const QString name = buildOutputName(ch); m_outputPorts.push_back( jack_port_register(m_client, name.toLatin1().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)); - QString input_name = QString("master in ") + ((ch % 2) ? "R" : "L") + QString::number(ch / 2 + 1); + const QString input_name = buildInputName(ch); m_inputPorts.push_back(jack_port_register(m_client, input_name.toLatin1().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)); if (m_outputPorts.back() == nullptr) @@ -478,22 +492,22 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) const auto output1 = cm->value(audioJackClass, output1Key); m_outputDevice1 = buildToolButton(this, output1, audioOutputNames, cn); - form->addRow(tr("Output 1"), m_outputDevice1); + form->addRow(buildOutputName(0) + ":", m_outputDevice1); const auto output2 = cm->value(audioJackClass, output2Key); m_outputDevice2 = buildToolButton(this, output2, audioOutputNames, cn); - form->addRow(tr("Output 2"), m_outputDevice2); + form->addRow(buildOutputName(1) + ":", m_outputDevice2); // Inputs const auto audioInputNames = getAudioInputNames(); const auto input1 = cm->value(audioJackClass, input1Key); m_inputDevice1 = buildToolButton(this, input1, audioInputNames, cn); - form->addRow(tr("Input 1"), m_inputDevice1); + form->addRow(buildInputName(0) + ":", m_inputDevice1); const auto input2 = cm->value(audioJackClass, input2Key); m_inputDevice2 = buildToolButton(this, input2, audioInputNames, cn); - form->addRow(tr("Input 2"), m_inputDevice2); + form->addRow(buildInputName(1) + ":", m_inputDevice2); if (m_client != nullptr) { From 420acb2d6f28b6393903836175ff5cc04b7a6c05 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 14 Jun 2025 17:28:56 +0200 Subject: [PATCH 15/21] Switch back to InstantPopup Switch the popup mode of the tool buttons from `MenuButtonPopup` back to `InstantPopup` so that users can click everywhere. This works better now because the buttons use all the space and the little triangle can be seen better. --- src/core/audio/AudioJack.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 9f9fbe0f4dc..0061653344b 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -479,7 +479,7 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) auto toolButton = new QToolButton(parent); // Make sure that the tool button will fill out the available space in the form layout toolButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - toolButton->setPopupMode(QToolButton::MenuButtonPopup); + toolButton->setPopupMode(QToolButton::InstantPopup); toolButton->setText(currentSelection); auto menu = this->buildMenu(toolButton, names, filteredLMMSClientName); toolButton->setMenu(menu); From 6e1e4625ef494a4255d4dad4bddcfcb5f7e23995 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 14 Jun 2025 17:34:31 +0200 Subject: [PATCH 16/21] Make `buildMenu` static --- include/AudioJack.h | 2 +- src/core/audio/AudioJack.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index 201210575d0..19de1ea22cb 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -85,7 +85,7 @@ class AudioJack : public QObject, public AudioDevice std::vector getAudioPortNames(JackPortFlags portFlags) const; std::vector getAudioInputNames() const; std::vector getAudioOutputNames() const; - QMenu* buildMenu(QToolButton* toolButton, const std::vector& names, const QString& filteredLMMSClientName); + static QMenu* buildMenu(QToolButton* toolButton, const std::vector& names, const QString& filteredLMMSClientName); private: QLineEdit* m_clientName; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 0061653344b..8716421c70d 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -481,7 +481,7 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) toolButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); toolButton->setPopupMode(QToolButton::InstantPopup); toolButton->setText(currentSelection); - auto menu = this->buildMenu(toolButton, names, filteredLMMSClientName); + auto menu = AudioJack::setupWidget::buildMenu(toolButton, names, filteredLMMSClientName); toolButton->setMenu(menu); return toolButton; From 0a0054a7e50f5b449dec23f72c80d14309e680f7 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Sat, 14 Jun 2025 17:54:16 +0200 Subject: [PATCH 17/21] Remove unnecessary whitespace Make CodeFactor happy. --- src/core/audio/AudioJack.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 8716421c70d..874e80c2d7d 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -607,7 +607,6 @@ QMenu* AudioJack::setupWidget::buildMenu(QToolButton* toolButton, const std::vec connect(action, &QAction::triggered, actionLambda); topLevelActions.append(action); } - } // First add the sub menus. By iterating the map they will be sorted automatically From 174ebcf7600983d5eeb852dbeb6314335812ca05 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Thu, 19 Jun 2025 09:14:32 +0200 Subject: [PATCH 18/21] Generalize number of inputs/outputs Generalize the number of inputs and outputs by using for loops. This affects the number of widgets that are created and the amount of configuration that is stored. This change is a result of a code review discussion. In my opinion it adds unnecessary complexity to something that should later be implemented completely different anyway. It is for example now necessary to compute the key names that are used during the saving of the configuration based on the channel number. The commit exists so that its changes can be discussed further. It might be reverted in one of the next commits. --- include/AudioJack.h | 7 +--- src/core/audio/AudioJack.cpp | 74 +++++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 31 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index 19de1ea22cb..702dfa141a9 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -92,11 +92,8 @@ class AudioJack : public QObject, public AudioDevice // Because we do not have access to a JackAudio driver instance we have to be our own client to display inputs and outputs... jack_client_t* m_client; - QToolButton* m_outputDevice1 = nullptr; - QToolButton* m_outputDevice2 = nullptr; - - QToolButton* m_inputDevice1 = nullptr; - QToolButton* m_inputDevice2 = nullptr; + std::vector m_outputDevices; + std::vector m_inputDevices; }; private slots: diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 874e80c2d7d..a76d33c707a 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -46,10 +46,17 @@ namespace { static const QString audioJackClass("audiojack"); static const QString clientNameKey("clientname"); -static const QString output1Key("output1"); -static const QString output2Key("output2"); -static const QString input1Key("input1"); -static const QString input2Key("input2"); + +QString getOutputKeyByChannel(size_t channel) +{ + return "output" + QString::number(channel + 1); +} + +QString getInputKeyByChannel(size_t channel) +{ + return "input" + QString::number(channel + 1); +} + } namespace lmms @@ -278,11 +285,16 @@ void AudioJack::startProcessing() const auto cm = ConfigManager::inst(); - attemptToReconnectOutput(0, cm->value(audioJackClass, output1Key)); - attemptToReconnectOutput(1, cm->value(audioJackClass, output2Key)); + const auto numberOfChannels = channels(); + for (size_t i = 0; i < numberOfChannels; ++i) + { + attemptToReconnectOutput(i, cm->value(audioJackClass, getOutputKeyByChannel(i))); + } - attemptToReconnectInput(0, cm->value(audioJackClass, input1Key)); - attemptToReconnectInput(1, cm->value(audioJackClass, input2Key)); + for (size_t i = 0; i < numberOfChannels; ++i) + { + attemptToReconnectInput(i, cm->value(audioJackClass, getInputKeyByChannel(i))); + } m_stopped = false; } @@ -490,24 +502,28 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) // Outputs const auto audioOutputNames = getAudioOutputNames(); - const auto output1 = cm->value(audioJackClass, output1Key); - m_outputDevice1 = buildToolButton(this, output1, audioOutputNames, cn); - form->addRow(buildOutputName(0) + ":", m_outputDevice1); - - const auto output2 = cm->value(audioJackClass, output2Key); - m_outputDevice2 = buildToolButton(this, output2, audioOutputNames, cn); - form->addRow(buildOutputName(1) + ":", m_outputDevice2); + constexpr size_t numberOfOutputChannels = 2; + for (size_t i = 0; i < numberOfOutputChannels; ++i) + { + const auto outputKey = getOutputKeyByChannel(i); + const auto outputValue = cm->value(audioJackClass, outputKey); + auto outputDevice = buildToolButton(this, outputValue, audioOutputNames, cn); + form->addRow(buildOutputName(i) + ":", outputDevice); + m_outputDevices.push_back(outputDevice); + } // Inputs const auto audioInputNames = getAudioInputNames(); - const auto input1 = cm->value(audioJackClass, input1Key); - m_inputDevice1 = buildToolButton(this, input1, audioInputNames, cn); - form->addRow(buildInputName(0) + ":", m_inputDevice1); - - const auto input2 = cm->value(audioJackClass, input2Key); - m_inputDevice2 = buildToolButton(this, input2, audioInputNames, cn); - form->addRow(buildInputName(1) + ":", m_inputDevice2); + constexpr size_t numberOfInputChannels = 2; + for (size_t i = 0; i < numberOfInputChannels; ++i) + { + const auto inputKey = getInputKeyByChannel(i); + const auto inputValue = cm->value(audioJackClass, inputKey); + auto inputDevice = buildToolButton(this, inputValue, audioInputNames, cn); + form->addRow(buildInputName(i) + ":", inputDevice); + m_inputDevices.push_back(inputDevice); + } if (m_client != nullptr) { @@ -520,10 +536,16 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) void AudioJack::setupWidget::saveSettings() { ConfigManager::inst()->setValue(audioJackClass, clientNameKey, m_clientName->text()); - ConfigManager::inst()->setValue(audioJackClass, output1Key, m_outputDevice1->text()); - ConfigManager::inst()->setValue(audioJackClass, output2Key, m_outputDevice2->text()); - ConfigManager::inst()->setValue(audioJackClass, input1Key, m_inputDevice1->text()); - ConfigManager::inst()->setValue(audioJackClass, input2Key, m_inputDevice2->text()); + + for (size_t i = 0; i < m_outputDevices.size(); ++i) + { + ConfigManager::inst()->setValue(audioJackClass, getOutputKeyByChannel(i), m_outputDevices[i]->text()); + } + + for (size_t i = 0; i < m_inputDevices.size(); ++i) + { + ConfigManager::inst()->setValue(audioJackClass, getInputKeyByChannel(i), m_inputDevices[i]->text()); + } } std::vector AudioJack::setupWidget::getAudioPortNames(JackPortFlags portFlags) const From b04ff1b504563d1bc8d4cbc6c5dd03b11968a39e Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Wed, 9 Jul 2025 16:29:29 +0200 Subject: [PATCH 19/21] Output messages only in debug builds Output the success/failure messages with regard to the reconnects only in debug builds. --- src/core/audio/AudioJack.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index a76d33c707a..8d54586f5c9 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -232,8 +232,12 @@ void AudioJack::resizeInputBuffer(jack_nframes_t nframes) void AudioJack::attemptToConnect(size_t index, const char *lmms_port_type, const char *source_port, const char *destination_port) { + // #ifdef LMMS_DEBUG + const auto result = jack_connect(m_client, source_port, destination_port); + +#ifdef LMMS_DEBUG printf("Attempting to reconnect %s port %u: %s -> %s", lmms_port_type, static_cast(index), source_port, destination_port); - if (!jack_connect(m_client, source_port, destination_port)) + if (!result) { printf(" - Success!\n"); } @@ -241,6 +245,7 @@ void AudioJack::attemptToConnect(size_t index, const char *lmms_port_type, const { printf(" - Failure\n"); } +#endif } void AudioJack::attemptToReconnectOutput(size_t outputIndex, const QString& targetPort) From 776d0bef04e203d574ad620d018a1d9f29d9f405 Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Wed, 9 Jul 2025 17:05:08 +0200 Subject: [PATCH 20/21] Add option to disconnect Add the option to keep inputs/outputs disconnected. The disconnected state is represented by the string "-" which is also what is saved into the configuration in this case. For now the representation in the GUI and of the save state is the same as it has the advantage that no translation is necessary and thus not mapping between display text and save state is necessary. --- src/core/audio/AudioJack.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 8d54586f5c9..5cd33f5c1ca 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -46,6 +46,7 @@ namespace { static const QString audioJackClass("audiojack"); static const QString clientNameKey("clientname"); +static const QString disconnectedRepresentation("-"); QString getOutputKeyByChannel(size_t channel) { @@ -232,7 +233,6 @@ void AudioJack::resizeInputBuffer(jack_nframes_t nframes) void AudioJack::attemptToConnect(size_t index, const char *lmms_port_type, const char *source_port, const char *destination_port) { - // #ifdef LMMS_DEBUG const auto result = jack_connect(m_client, source_port, destination_port); #ifdef LMMS_DEBUG @@ -252,6 +252,14 @@ void AudioJack::attemptToReconnectOutput(size_t outputIndex, const QString& targ { if (outputIndex > m_outputPorts.size()) return; + if (targetPort == disconnectedRepresentation) + { +#ifdef LMMS_DEBUG + printf("Output port %u is not connected.\n", static_cast(outputIndex)); +#endif + return; + } + auto outputName = jack_port_name(m_outputPorts[outputIndex]); auto targetName = targetPort.toLatin1().constData(); @@ -262,6 +270,14 @@ void AudioJack::attemptToReconnectInput(size_t inputIndex, const QString& source { if (inputIndex > m_inputPorts.size()) return; + if (sourcePort == disconnectedRepresentation) + { +#ifdef LMMS_DEBUG + printf("Input port %u is not connected.\n", static_cast(inputIndex)); +#endif + return; + } + auto inputName = jack_port_name(m_inputPorts[inputIndex]); auto sourceName = sourcePort.toLatin1().constData(); @@ -647,6 +663,11 @@ QMenu* AudioJack::setupWidget::buildMenu(QToolButton* toolButton, const std::vec std::sort(topLevelActions.begin(), topLevelActions.end(), [](QAction* a, QAction* b) { return a->text() < b->text(); }); menu->addActions(topLevelActions); + // Add the menu entry which represents the disconnected state at the very end + auto disconnectedAction = new QAction(disconnectedRepresentation, menu); + connect(disconnectedAction, &QAction::triggered, [toolButton](bool checked) { toolButton->setText(disconnectedRepresentation); }); + menu->addAction(disconnectedAction); + return menu; } From e270c7178f2d9ec32793c19b78470a2a21b07e6d Mon Sep 17 00:00:00 2001 From: Michael Gregorius Date: Wed, 9 Jul 2025 17:12:52 +0200 Subject: [PATCH 21/21] Fix release builds by unconditional output Make the output unconditional again because it interfered with some release builds. They complained that the `result` variable in `AudioJack::attemptToConnect` was unused because it was only further used in debug builds. The alternative would have been to duplicate the `jack_connect` call. However, that would have been a worse solution because it would have duplicated critical code which might have deviated due to the duplication. --- src/core/audio/AudioJack.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index 5cd33f5c1ca..59accd3fb55 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -233,11 +233,8 @@ void AudioJack::resizeInputBuffer(jack_nframes_t nframes) void AudioJack::attemptToConnect(size_t index, const char *lmms_port_type, const char *source_port, const char *destination_port) { - const auto result = jack_connect(m_client, source_port, destination_port); - -#ifdef LMMS_DEBUG printf("Attempting to reconnect %s port %u: %s -> %s", lmms_port_type, static_cast(index), source_port, destination_port); - if (!result) + if (!jack_connect(m_client, source_port, destination_port)) { printf(" - Success!\n"); } @@ -245,7 +242,6 @@ void AudioJack::attemptToConnect(size_t index, const char *lmms_port_type, const { printf(" - Failure\n"); } -#endif } void AudioJack::attemptToReconnectOutput(size_t outputIndex, const QString& targetPort) @@ -254,9 +250,7 @@ void AudioJack::attemptToReconnectOutput(size_t outputIndex, const QString& targ if (targetPort == disconnectedRepresentation) { -#ifdef LMMS_DEBUG - printf("Output port %u is not connected.\n", static_cast(outputIndex)); -#endif + printf("Output port %u is not connected.\n", static_cast(outputIndex)); return; } @@ -272,9 +266,7 @@ void AudioJack::attemptToReconnectInput(size_t inputIndex, const QString& source if (sourcePort == disconnectedRepresentation) { -#ifdef LMMS_DEBUG - printf("Input port %u is not connected.\n", static_cast(inputIndex)); -#endif + printf("Input port %u is not connected.\n", static_cast(inputIndex)); return; }