From 352ef8c25e8e26b260eafb1930ce148faa44df21 Mon Sep 17 00:00:00 2001 From: mmeeaallyynn <88937076+mmeeaallyynn@users.noreply.github.com> Date: Sat, 31 May 2025 12:49:14 +0200 Subject: [PATCH] Sample Track Recording with Jack backend (#7567) Enable the `AudioJack` to take recorded input and push it to the `AudioEngine`. This adds functionality for `AudioJack` which already exists for `AudioSdl` and `AudioPortAudio`. Note that sample track recording in the LMMS core is not completed before #7786 . This PR also removes the reading and saving of the channel number configuration in several places as it will default to `DEFAULT_CHANNELS` all the time anyway. Co-authored-by: Johannes Lorenz Co-authored-by: Michael Gregorius --- include/AudioJack.h | 12 +++---- src/core/audio/AudioJack.cpp | 65 ++++++++++++++++++++---------------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/include/AudioJack.h b/include/AudioJack.h index e13b4a5ef..ab87e97f6 100644 --- a/include/AudioJack.h +++ b/include/AudioJack.h @@ -53,11 +53,6 @@ namespace lmms class MidiJack; -namespace gui -{ -class LcdSpinBox; -} - class AudioJack : public QObject, public AudioDevice { @@ -82,13 +77,10 @@ public: { public: setupWidget(QWidget* parent); - ~setupWidget() override; - void saveSettings() override; private: QLineEdit* m_clientName; - gui::LcdSpinBox* m_channels; }; private slots: @@ -96,6 +88,7 @@ private slots: private: bool initJackClient(); + void resizeInputBuffer(jack_nframes_t nframes); void startProcessing() override; void stopProcessing() override; @@ -116,8 +109,11 @@ private: std::atomic m_midiClient; std::vector m_outputPorts; + std::vector m_inputPorts; jack_default_audio_sample_t** m_tempOutBufs; + std::vector m_inputFrameBuffer; SampleFrame* m_outBuf; + SampleFrame* m_inBuf; f_cnt_t m_framesDoneInCurBuf; f_cnt_t m_framesToDoInCurBuf; diff --git a/src/core/audio/AudioJack.cpp b/src/core/audio/AudioJack.cpp index b6da7c845..429c4ddcb 100644 --- a/src/core/audio/AudioJack.cpp +++ b/src/core/audio/AudioJack.cpp @@ -34,7 +34,6 @@ #include "ConfigManager.h" #include "Engine.h" #include "GuiApplication.h" -#include "LcdSpinBox.h" #include "MainWindow.h" #include "MidiJack.h" @@ -44,19 +43,14 @@ namespace lmms AudioJack::AudioJack(bool& successful, AudioEngine* audioEngineParam) : AudioDevice( - // clang-format off - std::clamp( - ConfigManager::inst()->value("audiojack", "channels").toInt(), - DEFAULT_CHANNELS, - DEFAULT_CHANNELS - ), - // clang-format on + DEFAULT_CHANNELS, audioEngineParam) , m_client(nullptr) , m_active(false) , m_midiClient(nullptr) , m_tempOutBufs(new jack_default_audio_sample_t*[channels()]) , m_outBuf(new SampleFrame[audioEngine()->framesPerPeriod()]) + , m_inBuf(new SampleFrame[audioEngine()->framesPerPeriod()]) , m_framesDoneInCurBuf(0) , m_framesToDoInCurBuf(0) { @@ -66,6 +60,8 @@ AudioJack::AudioJack(bool& successful, AudioEngine* audioEngineParam) if (successful) { connect(this, SIGNAL(zombified()), this, SLOT(restartAfterZombified()), Qt::QueuedConnection); } + + m_supportsCapture = true; } @@ -90,6 +86,7 @@ AudioJack::~AudioJack() delete[] m_tempOutBufs; delete[] m_outBuf; + delete[] m_inBuf; } @@ -154,6 +151,16 @@ bool AudioJack::initJackClient() clientName.toLatin1().constData(), jack_get_client_name(m_client)); } + resizeInputBuffer(jack_get_buffer_size(m_client)); + + // set buffer-size callback + jack_set_buffer_size_callback(m_client, + [](jack_nframes_t nframes, void* udata) -> int { + static_cast(udata)->resizeInputBuffer(nframes); + return 0; + }, + this); + // set process-callback jack_set_process_callback(m_client, staticProcessCallback, this); @@ -167,6 +174,10 @@ bool AudioJack::initJackClient() QString name = QString("master out ") + ((ch % 2) ? "R" : "L") + QString::number(ch / 2 + 1); 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); + m_inputPorts.push_back(jack_port_register(m_client, input_name.toLatin1().constData(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)); + if (m_outputPorts.back() == nullptr) { printf("no more JACK-ports available!\n"); @@ -180,6 +191,14 @@ bool AudioJack::initJackClient() +void AudioJack::resizeInputBuffer(jack_nframes_t nframes) +{ + m_inputFrameBuffer.resize(nframes); +} + + + + void AudioJack::startProcessing() { if (m_active || m_client == nullptr) @@ -290,7 +309,6 @@ void AudioJack::renamePort(AudioBusHandle* port) int AudioJack::processCallback(jack_nframes_t nframes) { - // do midi processing first so that midi input can // add to the following sound processing if (m_midiClient && nframes > 0) @@ -356,6 +374,16 @@ int AudioJack::processCallback(jack_nframes_t nframes) } } + for (int c = 0; c < channels(); ++c) + { + jack_default_audio_sample_t* jack_input_buffer = (jack_default_audio_sample_t*) jack_port_get_buffer(m_inputPorts[c], nframes); + + for (jack_nframes_t frame = 0; frame < nframes; frame++) + { + m_inputFrameBuffer[frame][c] = static_cast(jack_input_buffer[frame]); + } + } + audioEngine()->pushInputFrames (m_inputFrameBuffer.data(), nframes); return 0; } @@ -390,24 +418,6 @@ AudioJack::setupWidget::setupWidget(QWidget* parent) m_clientName = new QLineEdit(cn, this); form->addRow(tr("Client name"), m_clientName); - - auto m = new gui::LcdSpinBoxModel(/* this */); - m->setRange(DEFAULT_CHANNELS, DEFAULT_CHANNELS); - m->setStep(2); - m->setValue(ConfigManager::inst()->value("audiojack", "channels").toInt()); - - m_channels = new gui::LcdSpinBox(1, this); - m_channels->setModel(m); - - form->addRow(tr("Channels"), m_channels); -} - - - - -AudioJack::setupWidget::~setupWidget() -{ - delete m_channels->model(); } @@ -416,7 +426,6 @@ AudioJack::setupWidget::~setupWidget() void AudioJack::setupWidget::saveSettings() { ConfigManager::inst()->setValue("audiojack", "clientname", m_clientName->text()); - ConfigManager::inst()->setValue("audiojack", "channels", QString::number(m_channels->value())); }