commit: 06c2a1810f75a013607a1c71083922144c7f7ed5 Author: Adrian Schollmeyer <nex+b-g-o <AT> nexadn <DOT> de> AuthorDate: Wed Jan 4 21:30:13 2023 +0000 Commit: Adrian Schollmeyer <nex+b-g-o <AT> nexadn <DOT> de> CommitDate: Wed Jan 4 21:30:13 2023 +0000 URL: https://gitweb.gentoo.org/repo/proj/guru.git/commit/?id=06c2a181
media-sound/opensoundmeter: add 1.2.2, 1.2.2_p20220104 Signed-off-by: Adrian Schollmeyer <nex+b-g-o <AT> nexadn.de> media-sound/opensoundmeter/Manifest | 1 + .../files/opensoundmeter-deadlock-fix.patch | 64 ++ .../files/opensoundmeter-jack-support.patch | 668 +++++++++++++++++++++ .../opensoundmeter/opensoundmeter-1.2.2.ebuild | 60 ++ .../opensoundmeter-1.2.2_p20220104.ebuild | 76 +++ 5 files changed, 869 insertions(+) diff --git a/media-sound/opensoundmeter/Manifest b/media-sound/opensoundmeter/Manifest index 3d1338830..1cbf64c3a 100644 --- a/media-sound/opensoundmeter/Manifest +++ b/media-sound/opensoundmeter/Manifest @@ -1 +1,2 @@ DIST opensoundmeter-1.2.1.gh.tar.gz 41006647 BLAKE2B a3ab132f8a90497132dae3144dd91d162111754e79056bb95ae2f638f65dc6074d2dbc1739c07897f6b2771edfe82c284aea1a48cc9af9454a91698c6915fb5b SHA512 41701377b5df85e08664b68fe102cb6da5d57e70c0366bb5aafc681c926ba7da0622cceb218998bd677e3313f2727b7ecfcb4fcfb5f80fa1fd87334e2a27c377 +DIST opensoundmeter-1.2.2.gh.tar.gz 41041122 BLAKE2B f04441b5d672bb3f2dfcafcd0a69a272f2196972eff5ec99f3990c822edb35513bc9c27e07adeb58711a5355c1de280433f39baf0002ce3a9424c507988f758e SHA512 3cc848133b51a0409401347bdfccfe12b82b7fb07b8925593ee5b14053ac51ccb84c71960fc0d6e888f7a31baa78dba970588150a41a5f5d13fa54792806edc9 diff --git a/media-sound/opensoundmeter/files/opensoundmeter-deadlock-fix.patch b/media-sound/opensoundmeter/files/opensoundmeter-deadlock-fix.patch new file mode 100644 index 000000000..4719fd788 --- /dev/null +++ b/media-sound/opensoundmeter/files/opensoundmeter-deadlock-fix.patch @@ -0,0 +1,64 @@ +diff --git a/src/audio/plugins/alsa.cpp b/src/audio/plugins/alsa.cpp +index 9987ba3..61537f2 100644 +--- a/src/audio/plugins/alsa.cpp ++++ b/src/audio/plugins/alsa.cpp +@@ -16,6 +16,7 @@ + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + #include "alsa.h" ++#include <cassert> + #include <cstring> + #include <QCoreApplication> + #define ALSA_BUFFER_SIZE 1024 +@@ -175,23 +176,26 @@ Format AlsaPlugin::deviceFormat(const DeviceInfo::Id &id, const Plugin::Directio + Stream *AlsaPlugin::open(const DeviceInfo::Id &id, const Plugin::Direction &mode, const Format &format, + QIODevice *endpoint) + { +- std::lock_guard<std::mutex> lock(m_deviceListMutex); ++ std::unique_lock<std::mutex> lock(m_deviceListMutex, std::defer_lock); + if (id.isNull()) { + return nullptr; + } + ++ assert(!lock.owns_lock()); ++ lock.lock(); + AlsaPCMDevice *device = m_devices[ {mode, id}]; + if (!device) { + device = new AlsaPCMDevice(id, mode, format, m_deviceListMutex); + connect (device, &AlsaPCMDevice::closed, this, [this, mode, id, device]() { +- //mutex is locked here by device ++ std::lock_guard<std::mutex> lock{m_deviceListMutex}; + m_devices[ {mode, id}] = nullptr; + device->deleteLater(); +- m_deviceListMutex.unlock(); + }); + + m_devices[ {mode, id}] = device; + } ++ assert(lock.owns_lock()); ++ lock.unlock(); + if (!device->start()) { + return nullptr; + } +@@ -326,14 +330,18 @@ bool AlsaPCMDevice::start() + } + QCoreApplication::processEvents(); + if (m_callbacks.empty()) { +- m_mutex.lock(); ++ std::unique_lock<std::mutex> lock{m_mutex}; + if (m_keepAlive) { ++ lock.unlock(); + QCoreApplication::processEvents(); ++ lock.lock(); + } else { +- m_threadActive = false; ++ assert(lock.owns_lock()); ++ lock.unlock(); + break; + } +- m_mutex.unlock(); ++ assert(lock.owns_lock()); ++ lock.unlock(); + } + m_threadActive = true; + m_keepAlive = false; diff --git a/media-sound/opensoundmeter/files/opensoundmeter-jack-support.patch b/media-sound/opensoundmeter/files/opensoundmeter-jack-support.patch new file mode 100644 index 000000000..b5c7ad9c9 --- /dev/null +++ b/media-sound/opensoundmeter/files/opensoundmeter-jack-support.patch @@ -0,0 +1,668 @@ +diff --git a/.travis.yml b/.travis.yml +index ce9e997..2b6fbe4 100644 +--- a/.travis.yml ++++ b/.travis.yml +@@ -10,11 +10,12 @@ before_install: + install: + - sudo apt-get install build-essential libgl1-mesa-dev + - sudo apt-get install qt514base qt514multimedia qt514quickcontrols2 ++ - sudo apt-get install libjack-dev + - source /opt/qt514/bin/qt514-env.sh + + + script: +- - /opt/qt514/bin/qmake PREFIX=/usr ++ - /opt/qt514/bin/qmake PREFIX=/usr CONFIG+=jack + - make + - # Generate AppImage + # - sudo apt-get -y install checkinstall +diff --git a/OpenSoundMeter.pro b/OpenSoundMeter.pro +index a52b502..f9cef4a 100644 +--- a/OpenSoundMeter.pro ++++ b/OpenSoundMeter.pro +@@ -300,6 +300,18 @@ unix:!macx:!ios { + src/audio/plugins/alsa.cpp + + LIBS += -lasound ++ ++ CONFIG(jack) { ++ HEADERS += \ ++ src/audio/plugins/jack.h ++ ++ SOURCES += \ ++ src/audio/plugins/jack.cpp ++ ++ LIBS += -ljack ++ ++ DEFINES += USE_JACK ++ } + } + + GRAPH = $$(GRAPH_BACKEND) +diff --git a/src/audio/client.cpp b/src/audio/client.cpp +index d319c72..db7c377 100644 +--- a/src/audio/client.cpp ++++ b/src/audio/client.cpp +@@ -35,6 +35,10 @@ + #include "plugins/alsa.h" + #endif + ++#ifdef USE_JACK ++#include "plugins/jack.h" ++#endif ++ + #ifdef USE_ASIO + #include "plugins/asioplugin.h" + #endif +@@ -89,6 +93,10 @@ void Client::initPlugins() + m_plugins.push_back(QSharedPointer<Plugin>(new AlsaPlugin())); + #endif + ++#ifdef USE_JACK ++ m_plugins.push_back(QSharedPointer<Plugin>(new JackPlugin())); ++#endif ++ + for (auto &&plugin : m_plugins) { + connect(plugin.data(), &Plugin::deviceListChanged, this, [this]() { + refreshDeviceList(); +diff --git a/src/audio/plugins/jack.cpp b/src/audio/plugins/jack.cpp +new file mode 100644 +index 0000000..3a87edf +--- /dev/null ++++ b/src/audio/plugins/jack.cpp +@@ -0,0 +1,462 @@ ++/** ++ * OSM ++ * Copyright (C) 2022 Adrian Schollmeyer ++ ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see <http://www.gnu.org/licenses/>. ++ */ ++#include "jack.h" ++#include <algorithm> ++#include <cassert> ++ ++namespace { ++constexpr unsigned int DEFAULT_SAMPLE_RATE{48000}; ++constexpr unsigned int DEFAULT_CHANNEL_COUNT_INPUT{2}; ++constexpr unsigned int DEFAULT_CHANNEL_COUNT_OUTPUT{1}; ++ ++constexpr const char* PLUGIN_NAME{"JACK"}; ++constexpr const char* JACK_CLIENT_NAME{"OpenSoundMeter"}; ++constexpr const char* DEFAULT_MEASUREMENT_CHANNEL{"measurement"}; ++constexpr const char* DEFAULT_REFERENCE_CHANNEL{"reference"}; ++constexpr const char* DEFAULT_OUTPUT_CHANNEL{"out"}; ++} ++ ++namespace audio { ++JackClient::JackClient(const Plugin::Direction &direction, QIODevice& endpoint) ++ : m_client(nullptr, jackClientDeleter()), m_direction(direction), m_endpoint(endpoint) ++{ ++ initJackClient(); ++ initPorts(); ++ initSampleRate(); ++ initBufferSize(); ++ initProcessing(); ++} ++ ++JackClient::~JackClient() ++{ ++ // Explicitly stop processing first, to prevent us still running the JACK ++ // processor while the rest of the object is being cleaned up ++ if (m_client) ++ close(); ++} ++ ++jack_nframes_t JackClient::currentSampleRate() const ++{ ++ return m_sampleRate; ++} ++ ++std::size_t JackClient::currentChannelCount() const ++{ ++ return m_ports.size(); ++} ++ ++Plugin::Direction JackClient::direction() const ++{ ++ return m_direction; ++} ++ ++void JackClient::close() ++{ ++ deactivate(); ++ m_client.reset(nullptr); ++} ++ ++void JackClient::initJackClient() ++{ ++ jack_status_t jackStatus; ++ jack_client_t* jackClient = jack_client_open( ++ JACK_CLIENT_NAME, ++ JackOptions::JackNoStartServer, ++ &jackStatus ++ ); ++ ++ if (!jackClient) ++ throw JackPluginException{"Failed to open JACK client"}; ++ ++ m_client.reset(jackClient); ++} ++ ++void JackClient::initSampleRate() ++{ ++ assert(m_client); ++ m_sampleRate = jack_get_sample_rate(m_client.get()); ++ qDebug() << "[JACK] Sample rate set: " << m_sampleRate; ++ ++ int res = jack_set_sample_rate_callback( ++ m_client.get(), ++ jackSampleRateCallback, ++ this ++ ); ++ if (res != 0) ++ throw JackPluginException{"Failed to register sample rate callback"}; ++} ++ ++void JackClient::initBufferSize() ++{ ++ assert(m_client); ++ assert(!m_ports.empty()); ++ m_bufferSize = jack_get_buffer_size(m_client.get()); ++ m_sampleBuffer.resize(m_bufferSize * m_ports.size()); ++ qDebug() << "[JACK] Buffer size set: " << m_bufferSize; ++ ++ int res = jack_set_buffer_size_callback( ++ m_client.get(), ++ jackBufferSizeCallback, ++ this ++ ); ++ if (res != 0) ++ throw JackPluginException{"Failed to register buffer size callback"}; ++} ++ ++void JackClient::initPorts() ++{ ++ assert(m_client); ++ switch (m_direction) { ++ case Plugin::Direction::Input: ++ registerInputs(); ++ break; ++ ++ case Plugin::Direction::Output: ++ registerOutputs(); ++ break; ++ ++ default: ++ throw JackPluginException{"Unsupported signal direction"}; ++ } ++} ++ ++void JackClient::initProcessing() ++{ ++ assert(m_client); ++ int res = jack_set_process_callback( ++ m_client.get(), ++ jackProcessCallback, ++ this ++ ); ++ if (res != 0) ++ throw JackPluginException{"Failed to register process callback"}; ++} ++ ++void JackClient::activate() ++{ ++ assert(m_client); ++ if (m_active) ++ return; ++ ++ qDebug() << "[JACK] Activating"; ++ int res = jack_activate(m_client.get()); ++ if (res != 0) ++ throw JackPluginException{"Failed to activate client"}; ++ ++ m_active = true; ++} ++ ++void JackClient::deactivate() ++{ ++ if (!m_active) ++ return; ++ ++ m_active = false; ++ ++ assert(m_client); ++ int res = jack_deactivate(m_client.get()); ++ if (res != 0) ++ throw JackPluginException{"Failed to deactivate client"}; ++} ++ ++void JackClient::registerInputs() ++{ ++ qDebug() << "[JACK] Registering Inputs"; ++ jack_port_t *measurementPort = jack_port_register( ++ m_client.get(), ++ DEFAULT_MEASUREMENT_CHANNEL, ++ JACK_DEFAULT_AUDIO_TYPE, ++ JackPortFlags::JackPortIsInput, ++ 0 ++ ); ++ if (!measurementPort) ++ throw JackPluginException{"Failed to register measurement port"}; ++ m_ports.emplace_back(measurementPort, jackPortDeleter(m_client.get())); ++ m_portBuffers.push_back(nullptr); ++ ++ jack_port_t *referencePort = jack_port_register( ++ m_client.get(), ++ DEFAULT_REFERENCE_CHANNEL, ++ JACK_DEFAULT_AUDIO_TYPE, ++ JackPortFlags::JackPortIsInput, ++ 0 ++ ); ++ if (!referencePort) ++ throw JackPluginException{"Failed to register reference port"}; ++ m_ports.emplace_back(referencePort, jackPortDeleter(m_client.get())); ++ m_portBuffers.push_back(nullptr); ++} ++ ++void JackClient::registerOutputs() ++{ ++ qDebug() << "[JACK] Registering Outputs"; ++ jack_port_t *outputPort = jack_port_register( ++ m_client.get(), ++ DEFAULT_OUTPUT_CHANNEL, ++ JACK_DEFAULT_AUDIO_TYPE, ++ JackPortFlags::JackPortIsOutput, ++ 0 ++ ); ++ if (!outputPort) ++ throw JackPluginException{"Failed to register output port"}; ++ m_ports.emplace_back(outputPort, jackPortDeleter(m_client.get())); ++ m_portBuffers.push_back(nullptr); ++} ++ ++std::function<void(jack_client_t *)> JackClient::jackClientDeleter() ++{ ++ return [this](jack_client_t *client) -> void { ++ m_ports.resize(0); ++ jack_client_close(client); ++ }; ++} ++ ++std::function<void(jack_port_t *)> JackClient::jackPortDeleter(jack_client_t *client) ++{ ++ // We can't use the object's client member here as it might already have ++ // changed if this deleter is called in the client object's deleter, ++ // resulting in an assertion failure or segfault. ++ assert(client); ++ return [client](jack_port_t *port) -> void { ++ jack_port_unregister(client, port); ++ }; ++} ++ ++int JackClient::jackSampleRateCallback(jack_nframes_t newSampleRate, void* obj) ++{ ++ assert(obj); ++ return reinterpret_cast<JackClient*>(obj)->jackSampleRateCallbackInt(newSampleRate); ++} ++ ++int JackClient::jackSampleRateCallbackInt(jack_nframes_t newSampleRate) ++{ ++ m_sampleRate = newSampleRate; ++ qInfo() << "[JACK] Sample rate updated: " << m_sampleRate; ++ emit sampleRateChanged(m_sampleRate); ++ return 0; ++} ++ ++int JackClient::jackBufferSizeCallback(jack_nframes_t newBufferSize, void* obj) ++{ ++ assert(obj); ++ return reinterpret_cast<JackClient*>(obj)->jackBufferSizeCallbackInt(newBufferSize); ++} ++ ++int JackClient::jackBufferSizeCallbackInt(jack_nframes_t newBufferSize) ++{ ++ m_bufferSize = newBufferSize; ++ qInfo() << "[JACK] Buffer size updated: " << m_bufferSize; ++ if (newBufferSize > 0) ++ m_sampleBuffer.resize(newBufferSize * m_ports.size()); ++ ++ return 0; ++} ++ ++int JackClient::jackProcessCallback(jack_nframes_t nframes, void* obj) ++{ ++ assert(obj); ++ return reinterpret_cast<JackClient*>(obj)->jackProcessCallbackInt(nframes); ++} ++ ++int JackClient::jackProcessCallbackInt(jack_nframes_t nframes) ++{ ++ // Yes, this can actually happen ++ if (m_bufferSize == 0) ++ return 1; ++ ++ if (!m_active) ++ return 0; ++ ++ assert(m_ports.size() == m_portBuffers.size()); ++ assert(nframes == m_bufferSize); ++ assert(m_endpoint.isOpen()); ++ ++ if (m_direction == Plugin::Direction::Input) { ++ processInputPorts(nframes); ++ } else { ++ processOutputPorts(nframes); ++ } ++ return 0; ++} ++ ++void JackClient::processInputPorts(jack_nframes_t nframes) ++{ ++ if (!m_endpoint.isWritable()) ++ return; ++ ++ for (std::size_t i = 0; i < m_ports.size(); ++i) { ++ m_portBuffers[i] = reinterpret_cast<float*>( ++ jack_port_get_buffer(m_ports[i].get(), nframes)); ++ } ++ ++ std::size_t sampleBufferPos{0}; ++ for (jack_nframes_t i = 0; i < nframes; ++i) { ++ for (const float* portBuf : m_portBuffers) { ++ assert(sampleBufferPos < m_sampleBuffer.size()); ++ m_sampleBuffer[sampleBufferPos] = portBuf[i]; ++ sampleBufferPos++; ++ } ++ } ++ assert(sampleBufferPos == nframes * m_portBuffers.size()); ++ m_endpoint.write( ++ reinterpret_cast<char*>(m_sampleBuffer.data()), ++ sampleBufferPos * sizeof(float) ++ ); ++} ++ ++void JackClient::processOutputPorts(jack_nframes_t nframes) ++{ ++ if (!m_endpoint.isReadable()) ++ return; ++ ++ for (std::size_t i = 0; i < m_ports.size(); ++i) { ++ m_portBuffers[i] = reinterpret_cast<float*>( ++ jack_port_get_buffer(m_ports[i].get(), nframes)); ++ } ++ ++ const std::size_t samplesToWrite = std::max( ++ static_cast<std::size_t>(nframes), ++ static_cast<std::size_t>(m_endpoint.bytesAvailable() / sizeof(float)) ++ ); ++ assert(samplesToWrite % m_ports.size() == 0); ++ ++ std::size_t portIdx{0}; ++ std::size_t portBufferPos{0}; ++ for (std::size_t i = 0; i < samplesToWrite; ++i) { ++ assert(portIdx < m_portBuffers.size()); ++ assert(portBufferPos < m_bufferSize); ++ const std::size_t bytesRead = m_endpoint.read( ++ reinterpret_cast<char*>(m_portBuffers[portIdx] + portBufferPos), ++ sizeof(float) ++ ); ++ assert(bytesRead == sizeof(float)); ++ ++ portIdx = (portIdx + 1) % m_portBuffers.size(); ++ if (portIdx == 0) ++ portBufferPos++; ++ } ++} ++ ++JackPlugin::JackPlugin() ++{ ++ DeviceInfo m_defaultDeviceInfo{PLUGIN_NAME, PLUGIN_NAME}; ++ m_defaultDeviceInfo.setName(PLUGIN_NAME); ++ m_defaultDeviceInfo.setInputChannels({ ++ DEFAULT_MEASUREMENT_CHANNEL, ++ DEFAULT_REFERENCE_CHANNEL ++ }); ++ m_defaultDeviceInfo.setOutputChannels({DEFAULT_OUTPUT_CHANNEL}); ++ m_defaultDeviceInfo.setDefaultSampleRate(DEFAULT_SAMPLE_RATE); ++ m_list.push_back(m_defaultDeviceInfo); ++} ++ ++QString JackPlugin::name() const ++{ ++ return PLUGIN_NAME; ++} ++ ++DeviceInfo::List JackPlugin::getDeviceInfoList() const ++{ ++ return m_list; ++} ++ ++DeviceInfo::Id JackPlugin::defaultDeviceId( ++ [[maybe_unused]] const Plugin::Direction &mode) const ++{ ++ return DeviceInfo::Id(); ++} ++ ++Format JackPlugin::deviceFormat( ++ [[maybe_unused]] const DeviceInfo::Id &id, ++ const Plugin::Direction &mode) const ++{ ++ const auto client_it = std::find_if( ++ m_clients.begin(), ++ m_clients.end(), ++ [mode](const auto& client) { ++ ++ return client.second->direction() == mode; ++ }); ++ ++ if (client_it == m_clients.end()) { ++ if (mode == Plugin::Direction::Input) ++ return {DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_COUNT_INPUT}; ++ else ++ return {DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_COUNT_OUTPUT}; ++ } else { ++ return { ++ static_cast<unsigned int>(client_it->second->currentSampleRate()), ++ static_cast<unsigned int>(client_it->second->currentChannelCount()) ++ }; ++ } ++} ++ ++Stream *JackPlugin::open( ++ [[maybe_unused]] const DeviceInfo::Id &id, ++ const Plugin::Direction &mode, ++ [[maybe_unused]] const Format &format, ++ QIODevice *endpoint) ++{ ++ // The sample rate is set by JACK, so we don't mess with it. ++ // The channel count is fixed to the minimum amount of channels necessary ++ // for OSM to work as we can freely patch the cannels in JACK. ++ // ++ // The ID can be ignored as we create individual JACK clients ++ // for each requested Stream. ++ try { ++ std::unique_ptr<JackClient> jack = std::make_unique<JackClient>(mode, *endpoint); ++ ++ Format f{ ++ jack->currentSampleRate(), ++ static_cast<unsigned int>(jack->currentChannelCount()) ++ }; ++ Stream *stream = new Stream(f); ++ ++ m_clients.emplace(stream, std::move(jack)); ++ JackClient& jackClient = *m_clients.at(stream).get(); ++ ++ connect(stream, &Stream::closeMe, this, [this, stream, endpoint]() { ++ // Order is important here to prevent JACK still writing data to ++ // the endpoint after it has been closed. ++ m_clients.erase(stream); ++ ++ if (endpoint->isOpen()) ++ endpoint->close(); ++ ++ stream->deleteLater(); ++ }); ++ ++ connect( ++ &jackClient, ++ &JackClient::sampleRateChanged, ++ this, ++ [stream](jack_nframes_t newRate) { ++ assert(stream); ++ stream->setSampleRate(static_cast<unsigned int>(newRate)); ++ }); ++ ++ endpoint->open(mode == Input ? QIODevice::WriteOnly : QIODevice::ReadOnly); ++ jackClient.activate(); ++ ++ return stream; ++ } catch (JackPluginException& e) { ++ return nullptr; ++ } ++} ++ ++} +diff --git a/src/audio/plugins/jack.h b/src/audio/plugins/jack.h +new file mode 100644 +index 0000000..9685f65 +--- /dev/null ++++ b/src/audio/plugins/jack.h +@@ -0,0 +1,127 @@ ++/** ++ * OSM ++ * Copyright (C) 2022 Adrian Schollmeyer ++ ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see <http://www.gnu.org/licenses/>. ++ */ ++#ifndef AUDIO_JACKPLUGIN_H ++#define AUDIO_JACKPLUGIN_H ++ ++#include "../plugin.h" ++#include <QObject> ++#include <functional> ++#include <jack/jack.h> ++#include <jack/types.h> ++#include <map> ++#include <memory> ++#include <stdexcept> ++#include <vector> ++ ++namespace audio { ++class JackClient : public QObject ++{ ++ Q_OBJECT ++ ++public: ++ constexpr static std::size_t BUFFER_SIZE_MULTIPLIER{4}; ++ ++ JackClient(const Plugin::Direction &direction, QIODevice& endpoint); ++ JackClient(JackClient&&) = default; ++ JackClient(const JackClient &) = delete; ++ ~JackClient(); ++ ++ void activate(); ++ void deactivate(); ++ ++ jack_nframes_t currentSampleRate() const; ++ std::size_t currentChannelCount() const; ++ ++ Plugin::Direction direction() const; ++ ++signals: ++ void sampleRateChanged(jack_nframes_t newSampleRate); ++ ++public slots: ++ void close(); ++ ++private: ++ void initJackClient(); ++ void initPorts(); ++ void initSampleRate(); ++ void initBufferSize(); ++ void initProcessing(); ++ ++ void registerInputs(); ++ void registerOutputs(); ++ ++ std::function<void(jack_client_t *)> jackClientDeleter(); ++ static std::function<void(jack_port_t *)> jackPortDeleter(jack_client_t *client); ++ ++ static int jackSampleRateCallback(jack_nframes_t newSampleRate, void* obj); ++ int jackSampleRateCallbackInt(jack_nframes_t newSampleRate); ++ ++ static int jackBufferSizeCallback(jack_nframes_t newBufferSize, void* obj); ++ int jackBufferSizeCallbackInt(jack_nframes_t newBufferSize); ++ ++ static int jackProcessCallback(jack_nframes_t nframes, void* obj); ++ int jackProcessCallbackInt(jack_nframes_t nframes); ++ ++ void processInputPorts(jack_nframes_t nframes); ++ void processOutputPorts(jack_nframes_t nframes); ++ ++ std::unique_ptr<jack_client_t, std::function<void(jack_client_t *)>> m_client; ++ std::vector<std::unique_ptr<jack_port_t, std::function<void(jack_port_t *)>>> m_ports; ++ std::vector<float*> m_portBuffers; ++ jack_nframes_t m_sampleRate; ++ jack_nframes_t m_bufferSize; ++ const Plugin::Direction m_direction; ++ QIODevice& m_endpoint; ++ std::vector<float> m_sampleBuffer; ++ bool m_active{false}; ++}; ++ ++class JackPlugin : public Plugin ++{ ++ Q_OBJECT ++ ++public: ++ JackPlugin(); ++ JackPlugin(const JackPlugin &) = delete; ++ virtual ~JackPlugin() = default; ++ ++ QString name() const override; ++ DeviceInfo::List getDeviceInfoList() const override; ++ DeviceInfo::Id defaultDeviceId(const Direction &mode) const override; ++ ++ Format deviceFormat(const DeviceInfo::Id &id, const Direction &mode) const override; ++ Stream *open(const DeviceInfo::Id &id, const Direction &mode, const Format &format, QIODevice *endpoint) override; ++ ++private: ++ DeviceInfo::List m_list{}; ++ DeviceInfo m_defaultDeviceInfoInput; ++ DeviceInfo m_defaultDeviceInfoOutput; ++ std::map<Stream *, std::unique_ptr<JackClient>> m_clients{}; ++}; ++ ++class JackPluginException : public std::runtime_error ++{ ++public: ++ template<typename T> ++ JackPluginException(const T& arg) ++ : std::runtime_error(arg) ++ {} ++}; ++} ++ ++#endif // AUDIO_JACKPLUGIN_H diff --git a/media-sound/opensoundmeter/opensoundmeter-1.2.2.ebuild b/media-sound/opensoundmeter/opensoundmeter-1.2.2.ebuild new file mode 100644 index 000000000..372bb74c4 --- /dev/null +++ b/media-sound/opensoundmeter/opensoundmeter-1.2.2.ebuild @@ -0,0 +1,60 @@ +# Copyright 2022 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +EAPI=8 + +inherit qmake-utils desktop + +DESCRIPTION="FFT based application for tuning sound systems" +HOMEPAGE="https://opensoundmeter.com/en/ https://github.com/psmokotnin/osm" +SRC_URI=" + https://github.com/psmokotnin/osm/archive/refs/tags/v${PV}.tar.gz -> ${P}.gh.tar.gz +" + +# GPL-3 for the codebase +# N-Noise-EULA for the M-Noise noise generator +LICENSE="GPL-3 M-Noise-EULA" +SLOT="0" +KEYWORDS="~amd64" + +DEPEND=" + dev-qt/qtcore:5= + dev-qt/qtnetwork:5= + dev-qt/qtopengl:5= + dev-qt/qtquickcontrols2:5= + dev-qt/qtwidgets:5= + media-libs/alsa-lib +" +RDEPEND="${DEPEND}" +# qtcore for qmake5 +BDEPEND=" + dev-qt/qtcore:5 +" + +S="${WORKDIR}/osm-${PV}" +DOCS=( "README.md" ) + +src_prepare() { + default + mkdir -p build || die +} + +src_configure() { + cd build || die + eqmake5 ../OpenSoundMeter.pro +} + +src_compile() { + cd build || die + emake +} + +src_install() { + # The default OpenSoundMeter doesn't respect standard dirs, so we install + # manually + dobin build/OpenSoundMeter + + sed "s/Icon=white/Icon=${PN}/g" "OpenSoundMeter.desktop" || die + domenu "OpenSoundMeter.desktop" + newicon icons/white.png "${PN}.png" +} diff --git a/media-sound/opensoundmeter/opensoundmeter-1.2.2_p20220104.ebuild b/media-sound/opensoundmeter/opensoundmeter-1.2.2_p20220104.ebuild new file mode 100644 index 000000000..a35275cec --- /dev/null +++ b/media-sound/opensoundmeter/opensoundmeter-1.2.2_p20220104.ebuild @@ -0,0 +1,76 @@ +# Copyright 2022 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 + +EAPI=8 + +inherit qmake-utils desktop + +MY_PV="$(ver_cut 1-3)" + +DESCRIPTION="FFT based application for tuning sound systems" +HOMEPAGE="https://opensoundmeter.com/en/ https://github.com/psmokotnin/osm" +SRC_URI=" + https://github.com/psmokotnin/osm/archive/refs/tags/v${MY_PV}.tar.gz -> opensoundmeter-${MY_PV}.gh.tar.gz +" + +# GPL-3 for the codebase +# N-Noise-EULA for the M-Noise noise generator +LICENSE="GPL-3 M-Noise-EULA" +SLOT="0" +KEYWORDS="~amd64" +IUSE="jack" + +DEPEND=" + dev-qt/qtcore:5= + dev-qt/qtnetwork:5= + dev-qt/qtopengl:5= + dev-qt/qtquickcontrols2:5= + dev-qt/qtwidgets:5= + media-libs/alsa-lib + + jack? ( + virtual/jack + ) +" +RDEPEND="${DEPEND}" +# qtcore for qmake5 +BDEPEND=" + dev-qt/qtcore:5 +" + +PATCHES=( + # Two patches that just take far too long upstream + "${FILESDIR}/${PN}-jack-support.patch" + "${FILESDIR}/${PN}-deadlock-fix.patch" +) + +S="${WORKDIR}/osm-${MY_PV}" +DOCS=( "README.md" ) + +src_prepare() { + default + mkdir -p build || die +} + +src_configure() { + cd build || die + local myeqmakeargs=() + use jack && myeqmakeargs+=( "CONFIG+=jack" ) + + eqmake5 "${myeqmakeargs[@]}" ../OpenSoundMeter.pro +} + +src_compile() { + cd build || die + emake +} + +src_install() { + # The default OpenSoundMeter doesn't respect standard dirs, so we install + # manually + dobin build/OpenSoundMeter + + sed "s/Icon=white/Icon=${PN}/g" "OpenSoundMeter.desktop" || die + domenu "OpenSoundMeter.desktop" + newicon icons/white.png "${PN}.png" +}