diff --git a/docker/debian-build/Dockerfile b/docker/debian-build/Dockerfile
index e5f65ac90..19b4fcf2f 100644
--- a/docker/debian-build/Dockerfile
+++ b/docker/debian-build/Dockerfile
@@ -1,56 +1,95 @@
-# Build with:
-# docker build --pull -t svxlink-debian-build .
#
-# Run with:
-# docker run -it --rm --hostname debian-build svxlink-debian-build
+# Build container image with:
+# podman build --pull -t svxlink-debian-build .
+#
+# Optional build arguments:
+# --build-arg=FROM=
+# --build-arg=SOUNDS_VER=
+# --build-arg=SOUNDS_LANG=
+# --build-arg=SOUNDS_VOICE=
+# --build-arg=SOUNDS_RATE=
+# --build-arg=SOUNDS_URL=
+# --build-arg=GIT_URL_DEFAULT=
+# --build-arg=GIT_SSL_NO_VERIFY_DEFAULT=
+# --build-arg=GIT_BRANCH_DEFAULT=
+# --build-arg=NUM_CORES_DEFAULT=
+#
+# Run container with:
+# podman run -it --rm --hostname debian-build --userns=keep-id svxlink-debian-build
#
# For using sound inside the docker container add:
-# --privileged -v /dev/snd:/dev/snd
-# -e HOSTAUDIO_GID=$(stat -c "%g" /dev/snd/timer)
+# --privileged -v /dev/snd:/dev/snd
#
-# To import your git config add (mileage may vary):
-# -v ${HOME}/.gitconfig:/home/svxlink/.gitconfig:ro
+# To import your git config add:
+# -v ${HOME}/.gitconfig:/home/svxlink/.gitconfig:ro
#
-# To use a specific git repositoty instead of the default one:
-# -e GIT_URL=username@your.repo:/path/to/svxlink.git
+# To use a specific git repository instead of the default one:
+# -e GIT_URL=username@your.repo.srv:/path/to/svxlink.git
+# -e GIT_URL=https://your.repo.srv/path/to/svxlink.git
+# -e GIT_SSL_NO_VERIFY=true
#
# To build another branch than master:
-# -e GIT_BRANCH=the_branch
+# -e GIT_BRANCH=the_branch
+#
+# To use the local workingcopy rather then cloning the repo in the container:
+# -v $(pwd)/../..:/home/svxlink/svxlink:ro
#
# To use more than one CPU core when compiling:
-# -e NUM_CORES=8
+# -e NUM_CORES=8
+#
+# Build software with:
+# ./build-svxlink.sh
#
+# Run software with:
+# svxlink
-FROM debian
+ARG FROM=debian:latest
+
+FROM ${FROM}
MAINTAINER Tobias Blomberg
# Install required packages and set up the svxlink user
RUN apt-get update && \
apt-get -y install git cmake g++ make libsigc++-2.0-dev libgsm1-dev \
libpopt-dev tcl8.6-dev libgcrypt20-dev libspeex-dev \
- libasound2-dev alsa-utils vorbis-tools libqt4-dev \
- libopus-dev librtlsdr-dev libcurl4-openssl-dev curl sudo
+ libasound2-dev alsa-utils vorbis-tools qtbase5-dev \
+ qttools5-dev qttools5-dev-tools libopus-dev \
+ librtlsdr-dev libjsoncpp-dev libcurl4-openssl-dev \
+ libgpiod-dev libogg-dev curl sudo
#RUN apt-get -y install groff doxygen
+ARG SOUNDS_VER="19.09.99.3"
+ARG SOUNDS_LANG="en_US"
+ARG SOUNDS_VOICE="heather"
+ARG SOUNDS_RATE="16k"
+ARG SOUNDS_URL="https://github.com/sm0svx/svxlink-sounds-${SOUNDS_LANG}-${SOUNDS_VOICE}/releases/download/${SOUNDS_VER}/svxlink-sounds-${SOUNDS_LANG}-${SOUNDS_VOICE}-${SOUNDS_RATE}-${SOUNDS_VER}.tar.bz2"
+
# Install svxlink audio files
RUN mkdir -p /usr/share/svxlink/sounds && \
cd /usr/share/svxlink/sounds && \
- curl -LO https://github.com/sm0svx/svxlink-sounds-en_US-heather/releases/download/14.08/svxlink-sounds-en_US-heather-16k-13.12.tar.bz2 && \
+ curl -LO ${SOUNDS_URL} && \
tar xvaf svxlink-sounds-* && \
- ln -s en_US-heather-16k en_US && \
+ ln -s ${SOUNDS_LANG}-${SOUNDS_VOICE}-${SOUNDS_RATE} ${SOUNDS_LANG} && \
rm svxlink-sounds-*
-# Set up password less sudo for user svxlink
+# Set up password-less sudo for user svxlink
ADD sudoers-svxlink /etc/sudoers.d/svxlink
RUN chmod 0440 /etc/sudoers.d/svxlink
-ENV GIT_URL=https://github.com/sm0svx/svxlink.git \
- GIT_BRANCH=master \
- NUM_CORES=1
+ARG GIT_URL_DEFAULT="https://github.com/sm0svx/svxlink.git"
+ARG GIT_SSL_NO_VERIFY_DEFAULT="false"
+ARG GIT_BRANCH_DEFAULT="master"
+ARG NUM_CORES_DEFAULT="1"
+
+ENV GIT_URL=${GIT_URL_DEFAULT} \
+ GIT_SSL_NO_VERIFY=${GIT_SSL_NO_VERIFY_DEFAULT} \
+ GIT_BRANCH=${GIT_BRANCH_DEFAULT} \
+ NUM_CORES=${NUM_CORES_DEFAULT}
RUN useradd -s /bin/bash svxlink
ADD build-svxlink.sh /home/svxlink/
-RUN chown -R svxlink.svxlink /home/svxlink
+RUN chown -R svxlink:svxlink /home/svxlink
-ADD entrypoint.sh /
-ENTRYPOINT ["/entrypoint.sh"]
+WORKDIR /home/svxlink
+USER svxlink:svxlink
+ENTRYPOINT ["/bin/bash"]
diff --git a/docker/debian-build/build-svxlink.sh b/docker/debian-build/build-svxlink.sh
index 902993e19..a5cb4309a 100755
--- a/docker/debian-build/build-svxlink.sh
+++ b/docker/debian-build/build-svxlink.sh
@@ -1,25 +1,26 @@
-#!/bin/bash -xe
+#!/bin/bash -x
+
+set -euo pipefail
+
+GIT_BRANCH=${GIT_BRANCH:-master}
# Make sure that we are in the home directory
cd
# Clone or update the repo
if [[ ! -d svxlink ]]; then
- git clone $GIT_URL svxlink
+ git clone --branch=$GIT_BRANCH $GIT_URL svxlink
cd svxlink
else
cd svxlink
- git fetch
- git checkout master
- git reset --hard origin/master
-fi
-
-# Checkout the wanted branch
-if [ -n "$GIT_BRANCH" ]; then
- git checkout $GIT_BRANCH
+ if [[ -w . ]]; then
+ git fetch
+ git checkout $GIT_BRANCH
+ git reset --hard origin/$GIT_BRANCH
+ fi
fi
-# Find out how many cores we've got
+# How many cores to use during the build
num_cores=${NUM_CORES:-1}
# Create a build directory and build svxlink
diff --git a/docker/ubuntu-build/Dockerfile b/docker/ubuntu-build/Dockerfile
index 43b7ff37e..c7e53e74e 100644
--- a/docker/ubuntu-build/Dockerfile
+++ b/docker/ubuntu-build/Dockerfile
@@ -1,59 +1,96 @@
-# Build with:
-# docker build --pull -t svxlink-ubuntu-build .
#
-# Run with:
-# docker run -it --rm --hostname ubuntu-build svxlink-ubuntu-build
+# Build container image with:
+# podman build --pull -t svxlink-ubuntu-build .
+#
+# Optional build arguments:
+# --build-arg=FROM=
+# --build-arg=SOUNDS_VER=
+# --build-arg=SOUNDS_LANG=
+# --build-arg=SOUNDS_VOICE=
+# --build-arg=SOUNDS_RATE=
+# --build-arg=SOUNDS_URL=
+# --build-arg=GIT_URL_DEFAULT=
+# --build-arg=GIT_SSL_NO_VERIFY_DEFAULT=
+# --build-arg=GIT_BRANCH_DEFAULT=
+# --build-arg=NUM_CORES_DEFAULT=
+#
+# Run container with:
+# podman run -it --rm --hostname ubuntu-build --userns=keep-id svxlink-ubuntu-build
#
# For using sound inside the docker container add:
-# --privileged -v /dev/snd:/dev/snd
-# -e HOSTAUDIO_GID=$(stat -c "%g" /dev/snd/timer)
+# --privileged -v /dev/snd:/dev/snd
#
-# To import your git config add (your mileage may vary):
-# -v ${HOME}/.gitconfig:/home/svxlink/.gitconfig:ro
+# To import your git config add:
+# -v ${HOME}/.gitconfig:/home/svxlink/.gitconfig:ro
#
-# To use a specific git repositoty instead of the default one:
-# -e GIT_URL=username@your.repo:/path/to/svxlink.git
+# To use a specific git repository instead of the default one:
+# -e GIT_URL=username@your.repo.srv:/path/to/svxlink.git
+# -e GIT_URL=https://your.repo.srv/path/to/svxlink.git
+# -e GIT_SSL_NO_VERIFY=true
#
# To build another branch than master:
-# -e GIT_BRANCH=the_branch
+# -e GIT_BRANCH=the_branch
+#
+# To use the local workingcopy rather then cloning the repo in the container:
+# -v $(pwd)/../..:/home/svxlink/svxlink:ro
#
# To use more than one CPU core when compiling:
-# -e NUM_CORES=8
+# -e NUM_CORES=8
+#
+# Build software with:
+# ./build-svxlink.sh
#
+# Run software with:
+# svxlink
-FROM ubuntu
-MAINTAINER Tobias Blomberg
+ARG FROM=ubuntu:latest
+
+FROM ${FROM}
+MAINTAINER Tobias Blomberg
# Install required packages and set up the svxlink user
-RUN apt update && \
+RUN apt-get update && \
export DEBIAN_FRONTEND=noninteractive && \
- apt -y install git cmake g++ make libsigc++-2.0-dev libgsm1-dev \
- libpopt-dev tcl-dev libgcrypt20-dev libspeex-dev \
- libasound2-dev alsa-utils vorbis-tools qtbase5-dev \
- qttools5-dev qttools5-dev-tools libopus-dev \
- librtlsdr-dev libjsoncpp-dev libcurl4-openssl-dev \
- curl sudo
-#RUN apt -y install groff doxygen
+ apt-get -y install git cmake g++ make libsigc++-2.0-dev libgsm1-dev \
+ libpopt-dev tcl8.6-dev libgcrypt20-dev libspeex-dev \
+ libasound2-dev alsa-utils vorbis-tools qtbase5-dev \
+ qttools5-dev qttools5-dev-tools libopus-dev \
+ librtlsdr-dev libjsoncpp-dev libcurl4-openssl-dev \
+ libgpiod-dev libogg-dev curl sudo
+#RUN apt-get -y install groff doxygen
+
+ARG SOUNDS_VER="19.09.99.3"
+ARG SOUNDS_LANG="en_US"
+ARG SOUNDS_VOICE="heather"
+ARG SOUNDS_RATE="16k"
+ARG SOUNDS_URL="https://github.com/sm0svx/svxlink-sounds-${SOUNDS_LANG}-${SOUNDS_VOICE}/releases/download/${SOUNDS_VER}/svxlink-sounds-${SOUNDS_LANG}-${SOUNDS_VOICE}-${SOUNDS_RATE}-${SOUNDS_VER}.tar.bz2"
# Install svxlink audio files
RUN mkdir -p /usr/share/svxlink/sounds && \
cd /usr/share/svxlink/sounds && \
- curl -LO https://github.com/sm0svx/svxlink-sounds-en_US-heather/releases/download/19.09.99.1/svxlink-sounds-en_US-heather-16k-19.09.99.1.tar.bz2 && \
+ curl -LO ${SOUNDS_URL} && \
tar xvaf svxlink-sounds-* && \
- ln -s en_US-heather-16k en_US && \
+ ln -s ${SOUNDS_LANG}-${SOUNDS_VOICE}-${SOUNDS_RATE} ${SOUNDS_LANG} && \
rm svxlink-sounds-*
-# Set up password less sudo for user svxlink
+# Set up password-less sudo for user svxlink
ADD sudoers-svxlink /etc/sudoers.d/svxlink
RUN chmod 0440 /etc/sudoers.d/svxlink
-ENV GIT_URL=https://github.com/sm0svx/svxlink.git \
- GIT_BRANCH=master \
- NUM_CORES=1
+ARG GIT_URL_DEFAULT="https://github.com/sm0svx/svxlink.git"
+ARG GIT_SSL_NO_VERIFY_DEFAULT="false"
+ARG GIT_BRANCH_DEFAULT="master"
+ARG NUM_CORES_DEFAULT="1"
+
+ENV GIT_URL=${GIT_URL_DEFAULT} \
+ GIT_SSL_NO_VERIFY=${GIT_SSL_NO_VERIFY_DEFAULT} \
+ GIT_BRANCH=${GIT_BRANCH_DEFAULT} \
+ NUM_CORES=${NUM_CORES_DEFAULT}
RUN useradd -s /bin/bash svxlink
ADD build-svxlink.sh /home/svxlink/
-RUN chown -R svxlink.svxlink /home/svxlink
+RUN chown -R svxlink:svxlink /home/svxlink
-ADD entrypoint.sh /
-ENTRYPOINT ["/entrypoint.sh"]
+WORKDIR /home/svxlink
+USER svxlink:svxlink
+ENTRYPOINT ["/bin/bash"]
diff --git a/docker/ubuntu-build/build-svxlink.sh b/docker/ubuntu-build/build-svxlink.sh
index 902993e19..a5cb4309a 100755
--- a/docker/ubuntu-build/build-svxlink.sh
+++ b/docker/ubuntu-build/build-svxlink.sh
@@ -1,25 +1,26 @@
-#!/bin/bash -xe
+#!/bin/bash -x
+
+set -euo pipefail
+
+GIT_BRANCH=${GIT_BRANCH:-master}
# Make sure that we are in the home directory
cd
# Clone or update the repo
if [[ ! -d svxlink ]]; then
- git clone $GIT_URL svxlink
+ git clone --branch=$GIT_BRANCH $GIT_URL svxlink
cd svxlink
else
cd svxlink
- git fetch
- git checkout master
- git reset --hard origin/master
-fi
-
-# Checkout the wanted branch
-if [ -n "$GIT_BRANCH" ]; then
- git checkout $GIT_BRANCH
+ if [[ -w . ]]; then
+ git fetch
+ git checkout $GIT_BRANCH
+ git reset --hard origin/$GIT_BRANCH
+ fi
fi
-# Find out how many cores we've got
+# How many cores to use during the build
num_cores=${NUM_CORES:-1}
# Create a build directory and build svxlink
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7703b3121..7cf6e7367 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -20,7 +20,7 @@
##############################################################################
# Project setup
##############################################################################
-cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
project(svxlink C CXX)
#enable_testing()
@@ -295,6 +295,22 @@ add_definitions(${SIGC2_DEFINITIONS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SIGC2_CXX_FLAGS}")
set(LIBS ${LIBS} ${SIGC2_LIBRARIES})
+find_package(LADSPA)
+if(LADSPA_FOUND)
+ if(DEFINED LADSPA_VERSION_MAJOR)
+ include_directories(${LADSPA_INCLUDE_DIRS})
+ add_definitions(${LADSPA_DEFINITIONS})
+ else()
+ message(WARNING
+ "Found LADSPA but version could not be resolved. "
+ "Will proceed without LADSPA.")
+ endif()
+else(LADSPA_FOUND)
+ message("-- LADSPA is an optional dependency. The build will complete")
+ message("-- without it but support for loading LADSPA plugins will")
+ message("-- be unavailable.")
+endif(LADSPA_FOUND)
+
# Find the chown utility
include(FindCHOWN)
diff --git a/src/async/ChangeLog b/src/async/ChangeLog
index 38446f847..7d53402b7 100644
--- a/src/async/ChangeLog
+++ b/src/async/ChangeLog
@@ -67,6 +67,9 @@
* Bugfix Async::HttpServerConnection: EOL handling failed with newer
compilers.
+* New class Async::AudioLADSPAPlugin which enable the use of LADSPA plugins to
+ process audio.
+
1.6.0 -- 01 Sep 2019
diff --git a/src/async/audio/AsyncAudioContainerWav.h b/src/async/audio/AsyncAudioContainerWav.h
index 633c0f0ab..0761136cd 100644
--- a/src/async/audio/AsyncAudioContainerWav.h
+++ b/src/async/audio/AsyncAudioContainerWav.h
@@ -40,6 +40,7 @@ An example of how to use the Async::AudioContainer class
#include
#include
+#include
/****************************************************************************
diff --git a/src/async/audio/AsyncAudioLADSPAPlugin.cpp b/src/async/audio/AsyncAudioLADSPAPlugin.cpp
new file mode 100644
index 000000000..672de561e
--- /dev/null
+++ b/src/async/audio/AsyncAudioLADSPAPlugin.cpp
@@ -0,0 +1,713 @@
+/**
+@file AsyncAudioLADSPAPlugin.cpp
+@brief A_brief_description_for_this_file
+@author Tobias Blomberg / SM0SVX
+@date 2023-12-09
+
+\verbatim
+Async - A library for programming event driven applications
+Copyright (C) 2003-2024 Tobias Blomberg / SM0SVX
+
+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 2 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, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+\endverbatim
+*/
+
+/****************************************************************************
+ *
+ * System Includes
+ *
+ ****************************************************************************/
+
+//#define _DEFAULT_SOURCE
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+
+/****************************************************************************
+ *
+ * Project Includes
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Local Includes
+ *
+ ****************************************************************************/
+
+#include "AsyncAudioLADSPAPlugin.h"
+
+
+/****************************************************************************
+ *
+ * Namespaces to use
+ *
+ ****************************************************************************/
+
+using namespace Async;
+
+
+/****************************************************************************
+ *
+ * Defines & typedefs
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Static class variables
+ *
+ ****************************************************************************/
+
+
+
+/****************************************************************************
+ *
+ * Local class definitions
+ *
+ ****************************************************************************/
+
+namespace {
+
+
+/****************************************************************************
+ *
+ * Local functions
+ *
+ ****************************************************************************/
+
+
+
+}; /* End of anonymous namespace */
+
+/****************************************************************************
+ *
+ * Public member functions
+ *
+ ****************************************************************************/
+
+bool AudioLADSPAPlugin::findPluginsInDir(std::string dir)
+{
+ if (dir.empty())
+ {
+ return false;
+ }
+ if (dir[dir.length()-1] != '/')
+ {
+ dir += "/";
+ }
+ //std::cout << "### dir=" << dir << std::endl;
+ struct dirent **namelist;
+ int n = scandir(dir.c_str(), &namelist,
+ [](const struct dirent* de) -> int
+ {
+ auto len = strlen(de->d_name);
+ return (len > 3) && (strcmp(de->d_name+len-3, ".so") == 0);
+ },
+ alphasort);
+ if (n == -1)
+ {
+ perror(std::string("scandir(" + dir + ")").c_str());
+ return false;
+ }
+
+ while (n--)
+ {
+ //std::cout << "### " << namelist[n]->d_name << std::endl;
+ PluginIndex i = 0;
+ for (;;)
+ {
+ AudioLADSPAPlugin p(dir + namelist[n]->d_name, i++);
+ if (p.ladspaDescriptor() == nullptr)
+ {
+ break;
+ }
+ auto inst = std::make_shared();
+ inst->m_path = p.path();
+ inst->m_index = i-1;
+ inst->m_unique_id = p.uniqueId();
+ inst->m_label = p.label();
+ labelMap()[inst->m_label] = inst;
+ idMap()[inst->m_unique_id] = inst;
+ }
+ free(namelist[n]);
+ }
+ free(namelist);
+ return true;
+} /* AudioLADSPAPlugin::findPluginsInDir */
+
+
+bool AudioLADSPAPlugin::findPlugins(void)
+{
+ std::string ladspa_path(LADSPA_PLUGIN_DIRS);
+ const char* env_ladspa_path = getenv("LADSPA_PATH");
+ if (env_ladspa_path != nullptr)
+ {
+ ladspa_path = env_ladspa_path;
+ }
+ std::string::size_type begin = 0, end = 0;
+ do
+ {
+ end = ladspa_path.find(':', begin);
+ findPluginsInDir(ladspa_path.substr(begin, end));
+ begin = end + 1;
+ } while (end != std::string::npos);
+ return true;
+} /* AudioLADSPAPlugin::findPlugins */
+
+
+AudioLADSPAPlugin::AudioLADSPAPlugin(AudioLADSPAPlugin::UniqueID id)
+{
+ if (idMap().empty())
+ {
+ findPlugins();
+ }
+ auto instit = idMap().find(id);
+ if (instit == idMap().end())
+ {
+ return;
+ }
+ auto inst_info = instit->second;
+ m_path = inst_info->m_path;
+ m_index = inst_info->m_index;
+} /* AudioLADSPAPlugin::AudioLADSPAPlugin */
+
+
+AudioLADSPAPlugin::AudioLADSPAPlugin(const std::string& label)
+{
+ if (labelMap().empty())
+ {
+ findPlugins();
+ }
+ auto instit = labelMap().find(label);
+ if (instit == labelMap().end())
+ {
+ return;
+ }
+ auto inst_info = instit->second;
+ m_path = inst_info->m_path;
+ m_index = inst_info->m_index;
+} /* AudioLADSPAPlugin::AudioLADSPAPlugin */
+
+
+AudioLADSPAPlugin::~AudioLADSPAPlugin(void)
+{
+ deactivate();
+
+ delete [] m_ctrl_buf;
+ m_ctrl_buf = nullptr;
+
+ if ((m_desc != nullptr) && (m_desc->cleanup != nullptr) &&
+ (m_inst_handle != nullptr))
+ {
+ m_desc->cleanup(m_inst_handle);
+ }
+ m_inst_handle = nullptr;
+ m_desc = nullptr;
+
+ if (m_handle != nullptr)
+ {
+ if (dlclose(m_handle) != 0)
+ {
+ std::cerr << "*** ERROR: Failed to unload plugin "
+ << m_path << ": " << dlerror() << std::endl;
+ }
+ }
+ m_handle = nullptr;
+} /* AudioLADSPAPlugin::~AudioLADSPAPlugin */
+
+
+bool AudioLADSPAPlugin::initialize(void)
+{
+ if (m_path.empty())
+ {
+ //std::cerr << "*** ERROR: Empty LADSPA plugin path" << std::endl;
+ return false;
+ }
+
+ if (ladspaDescriptor() == nullptr)
+ {
+ std::cerr << "*** ERROR: Could not find LADSPA instance for index "
+ << m_index << " in plugin " << m_path << std::endl;
+ return false;
+ }
+
+ m_inst_handle = m_desc->instantiate(m_desc, INTERNAL_SAMPLE_RATE);
+ if (m_inst_handle == nullptr)
+ {
+ std::cerr << "*** ERROR: Could not instantiate LADSPA instance for index "
+ << m_index << " in plugin " << m_path << std::endl;
+ return false;
+ }
+
+ m_ctrl_buf = new LADSPA_Data[m_desc->PortCount];
+
+ for (PortNumber i=0; iPortCount; ++i)
+ {
+ const LADSPA_PortDescriptor& port_desc = m_desc->PortDescriptors[i];
+
+ if (!LADSPA_IS_PORT_INPUT(port_desc) && !LADSPA_IS_PORT_OUTPUT(port_desc))
+ {
+ std::cerr << "*** ERROR: Invalid LADSPA plugin " << m_path
+ << " with index " << m_index << ". Port " << i
+ << " is neither input nor output." << std::endl;
+ return false;
+ }
+ if (!LADSPA_IS_PORT_CONTROL(port_desc) && !LADSPA_IS_PORT_AUDIO(port_desc))
+ {
+ std::cerr << "*** ERROR: Invalid LADSPA plugin " << m_path
+ << " with index " << m_index << ". Port " << i
+ << " is neither type control nor audio." << std::endl;
+ return false;
+ }
+
+ if (LADSPA_IS_PORT_CONTROL(port_desc))
+ {
+ if (!setDefault(i))
+ {
+ std::cerr << "*** ERROR: Illegal default handling in LADSPA instance "
+ "for index " << m_index << " in plugin " << m_path
+ << std::endl;
+ return false;
+ }
+ m_desc->connect_port(m_inst_handle, i, &m_ctrl_buf[i]);
+ }
+ if (LADSPA_IS_PORT_AUDIO(port_desc))
+ {
+ if (LADSPA_IS_PORT_INPUT(port_desc))
+ {
+ if (m_sample_input_port != NOPORT)
+ {
+ std::cerr << "*** ERROR: Only single audio input port LADSPA "
+ "instances supported but index " << m_index
+ << " in plugin " << m_path
+ << " has multiple audio input ports"
+ << std::endl;
+ return false;
+ }
+ m_sample_input_port = i;
+ }
+ else
+ {
+ if (m_sample_output_port != NOPORT)
+ {
+ std::cerr << "*** ERROR: Only single audio output port LADSPA "
+ "instances supported but index " << m_index
+ << " in plugin " << m_path
+ << " has multiple audio output ports"
+ << std::endl;
+ return false;
+ }
+ m_sample_output_port = i;
+ }
+ }
+ }
+
+ if ((m_sample_input_port == NOPORT) || (m_sample_output_port == NOPORT))
+ {
+ std::cerr << "*** ERROR: LADSPA instances must have exactly one input "
+ "port and one output port but index " << m_index
+ << " in plugin " << m_path
+ << " is missing an input or output port" << std::endl;
+ return false;
+ }
+
+ activate();
+
+ return true;
+
+} /* AudioLADSPAPlugin::initialize */
+
+
+bool AudioLADSPAPlugin::setControl(PortNumber portno, LADSPA_Data val)
+{
+ assert(m_desc != nullptr);
+ assert(m_ctrl_buf != nullptr);
+ if (portno >= m_desc->PortCount)
+ {
+ return false;
+ }
+ const LADSPA_PortDescriptor& port_desc = m_desc->PortDescriptors[portno];
+ if (!LADSPA_IS_PORT_CONTROL(port_desc) || !LADSPA_IS_PORT_INPUT(port_desc))
+ {
+ return false;
+ }
+
+ float (*conv)(float) = [](float x) { return x; };
+ const auto& port_range_hint = m_desc->PortRangeHints[portno];
+ const auto& hint_desc = port_range_hint.HintDescriptor;
+ LADSPA_Data mult = 1.0f;
+ if (LADSPA_IS_HINT_SAMPLE_RATE(hint_desc))
+ {
+ mult = INTERNAL_SAMPLE_RATE;
+ }
+ auto lower_bound = port_range_hint.LowerBound * mult;
+ auto upper_bound = port_range_hint.UpperBound * mult;
+ if (LADSPA_IS_HINT_INTEGER(hint_desc))
+ {
+ conv = roundf;
+ }
+ if (LADSPA_IS_HINT_BOUNDED_BELOW(hint_desc) && (val < lower_bound))
+ {
+ val = conv(lower_bound);
+ }
+ if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint_desc) && (val > upper_bound))
+ {
+ val = conv(upper_bound);
+ }
+
+ m_ctrl_buf[portno] = val;
+
+ return true;
+} /* AudioLADSPAPlugin::setControl */
+
+
+void AudioLADSPAPlugin::activate(void)
+{
+ assert((m_desc != nullptr) && (m_inst_handle != nullptr));
+ if ((m_desc->activate != nullptr) && !m_is_active)
+ {
+ m_desc->activate(m_inst_handle);
+ }
+ m_is_active = false;
+} /* AudioLADSPAPlugin::activate */
+
+
+void AudioLADSPAPlugin::deactivate(void)
+{
+ if ((m_desc != nullptr) && (m_desc->deactivate != nullptr) &&
+ (m_inst_handle != nullptr) && m_is_active)
+ {
+ m_desc->deactivate(m_inst_handle);
+ }
+ m_is_active = false;
+} /* AudioLADSPAPlugin::deactivate */
+
+
+bool AudioLADSPAPlugin::portIsControl(PortNumber portno) const
+{
+ assert(m_desc != nullptr);
+ if (portno >= m_desc->PortCount)
+ {
+ return false;
+ }
+ const LADSPA_PortDescriptor& port_desc = m_desc->PortDescriptors[portno];
+ return LADSPA_IS_PORT_CONTROL(port_desc);
+} /* AudioLADSPAPlugin::portIsControl */
+
+
+bool AudioLADSPAPlugin::portIsAudio(PortNumber portno) const
+{
+ assert(m_desc != nullptr);
+ if (portno >= m_desc->PortCount)
+ {
+ return false;
+ }
+ const LADSPA_PortDescriptor& port_desc = m_desc->PortDescriptors[portno];
+ return LADSPA_IS_PORT_AUDIO(port_desc);
+} /* AudioLADSPAPlugin::portIsAudio */
+
+
+bool AudioLADSPAPlugin::portIsInput(PortNumber portno) const
+{
+ assert(m_desc != nullptr);
+ if (portno >= m_desc->PortCount)
+ {
+ return false;
+ }
+ const LADSPA_PortDescriptor& port_desc = m_desc->PortDescriptors[portno];
+ return LADSPA_IS_PORT_INPUT(port_desc);
+} /* AudioLADSPAPlugin::portIsInput */
+
+
+bool AudioLADSPAPlugin::portIsOutput(PortNumber portno) const
+{
+ assert(m_desc != nullptr);
+ if (portno >= m_desc->PortCount)
+ {
+ return false;
+ }
+ const LADSPA_PortDescriptor& port_desc = m_desc->PortDescriptors[portno];
+ return LADSPA_IS_PORT_OUTPUT(port_desc);
+} /* AudioLADSPAPlugin::portIsOutput */
+
+
+void AudioLADSPAPlugin::print(const std::string& prefix)
+{
+ assert(m_desc != nullptr);
+
+ std::cout << prefix << "\"" << m_desc->Name << "\""
+ << " (" << m_desc->Label << ")"
+ << " by \"" << m_desc->Maker << "\""
+ << " (C) " << m_desc->Copyright
+ << std::endl;
+ std::cout << prefix << " Path: " << m_path << std::endl;
+
+ for (PortNumber i=0; iPortCount; ++i)
+ {
+ LADSPA_PortDescriptor port_desc = m_desc->PortDescriptors[i];
+
+ if (LADSPA_IS_PORT_AUDIO(port_desc))
+ {
+ continue;
+ }
+
+ std::cout << prefix << " "
+ << (LADSPA_IS_PORT_INPUT(port_desc) ? "In " : "Out")
+ << ": \"" << m_desc->PortNames[i] << "\" ";
+
+ //if (LADSPA_IS_PORT_CONTROL(port_desc))
+ //{
+ // std::cout << "control ";
+ //}
+
+ float (*conv)(float) = [](float x) { return x; };
+ const auto& port_range_hint = m_desc->PortRangeHints[i];
+ const auto& hint_desc = port_range_hint.HintDescriptor;
+ if (LADSPA_IS_HINT_INTEGER(hint_desc))
+ {
+ //std::cout << "int:";
+ conv = roundf;
+ }
+ //else if (LADSPA_IS_HINT_TOGGLED(hint_desc))
+ //{
+ // std::cout << "bool:";
+ //}
+ //else
+ //{
+ // std::cout << "float:";
+ //}
+
+ bool bounded_below = LADSPA_IS_HINT_BOUNDED_BELOW(hint_desc);
+ bool bounded_above = LADSPA_IS_HINT_BOUNDED_ABOVE(hint_desc);
+ if (bounded_below || bounded_above)
+ {
+ LADSPA_Data samp_rate = 1.0f;
+ if (LADSPA_IS_HINT_SAMPLE_RATE(hint_desc))
+ {
+ samp_rate = INTERNAL_SAMPLE_RATE;
+ }
+ std::cout << "[";
+ if (bounded_below)
+ {
+ std::cout << conv(samp_rate * port_range_hint.LowerBound);
+ }
+ std::cout << ",";
+ if (bounded_above)
+ {
+ std::cout << conv(samp_rate * port_range_hint.UpperBound);
+ }
+ std::cout << "]";
+ }
+
+ if (LADSPA_IS_HINT_LOGARITHMIC(hint_desc))
+ {
+ std::cout << " (log)";
+ }
+
+ std::cout << " = " << m_ctrl_buf[i];
+
+ std::cout << std::endl;
+ }
+} /* AudioLADSPAPlugin::print */
+
+
+/****************************************************************************
+ *
+ * Protected member functions
+ *
+ ****************************************************************************/
+
+void AudioLADSPAPlugin::processSamples(float *dest, const float *src,
+ int count)
+{
+ assert(dest != nullptr);
+ assert(src != nullptr);
+ assert(m_sample_input_port != NOPORT);
+ assert(m_sample_output_port != NOPORT);
+ if (count <= 0)
+ {
+ return;
+ }
+ m_desc->connect_port(m_inst_handle, m_sample_input_port,
+ const_cast(src));
+ m_desc->connect_port(m_inst_handle, m_sample_output_port, dest);
+ m_desc->run(m_inst_handle, count);
+} /* AudioLADSPAPlugin::processSamples */
+
+
+/****************************************************************************
+ *
+ * Private member functions
+ *
+ ****************************************************************************/
+
+const LADSPA_Descriptor* AudioLADSPAPlugin::ladspaDescriptor(void)
+{
+ m_handle = dlopen(m_path.c_str(), RTLD_NOW);
+ if (m_handle == nullptr)
+ {
+ std::cerr << "*** ERROR: Failed to load plugin "
+ << m_path << ": " << dlerror() << std::endl;
+ return nullptr;
+ }
+
+ using ConstructFunc = LADSPA_Descriptor_Function;
+ ConstructFunc construct = (ConstructFunc)dlsym(m_handle, "ladspa_descriptor");
+ if (construct == nullptr)
+ {
+ std::cerr << "*** ERROR: Could not find LADSPA descriptor function for "
+ "plugin " << m_path << ": " << dlerror() << std::endl;
+ return nullptr;
+ }
+
+ m_desc = construct(m_index);
+ return m_desc;
+} /* AudioLADSPAPlugin::ladspaDescriptor */
+
+
+bool AudioLADSPAPlugin::setDefault(PortNumber portno)
+{
+ assert(m_desc != nullptr);
+
+ LADSPA_Data& def = m_ctrl_buf[portno];
+ const auto& port_range_hint = m_desc->PortRangeHints[portno];
+ const auto& hint_desc = port_range_hint.HintDescriptor;
+
+ if (!LADSPA_IS_HINT_HAS_DEFAULT(hint_desc))
+ {
+ def = 0.0f;
+ return true;
+ }
+
+ LADSPA_Data mult = 1.0f;
+ if (LADSPA_IS_HINT_SAMPLE_RATE(hint_desc))
+ {
+ mult = INTERNAL_SAMPLE_RATE;
+ }
+ auto lower_bound = port_range_hint.LowerBound * mult;
+ auto upper_bound = port_range_hint.UpperBound * mult;
+
+ if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hint_desc))
+ {
+ if (!LADSPA_IS_HINT_BOUNDED_BELOW(hint_desc))
+ {
+ return false;
+ }
+ def = lower_bound;
+ }
+
+ if (LADSPA_IS_HINT_DEFAULT_LOW(hint_desc))
+ {
+ if (!LADSPA_IS_HINT_BOUNDED_BELOW(hint_desc) ||
+ !LADSPA_IS_HINT_BOUNDED_ABOVE(hint_desc))
+ {
+ return false;
+ }
+ if (LADSPA_IS_HINT_LOGARITHMIC(hint_desc))
+ {
+ def = expf(logf(lower_bound)*0.75 + logf(upper_bound)*0.25);
+ }
+ else
+ {
+ def = lower_bound*0.75 + upper_bound*0.25;
+ }
+ }
+
+ if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hint_desc))
+ {
+ if (!LADSPA_IS_HINT_BOUNDED_BELOW(hint_desc) ||
+ !LADSPA_IS_HINT_BOUNDED_ABOVE(hint_desc))
+ {
+ return false;
+ }
+ if (LADSPA_IS_HINT_LOGARITHMIC(hint_desc))
+ {
+ def = expf(logf(lower_bound)*0.5 + logf(upper_bound)*0.5);
+ }
+ else
+ {
+ def = lower_bound*0.5 + upper_bound*0.5;
+ }
+ }
+
+ if (LADSPA_IS_HINT_DEFAULT_HIGH(hint_desc))
+ {
+ if (!LADSPA_IS_HINT_BOUNDED_BELOW(hint_desc) ||
+ !LADSPA_IS_HINT_BOUNDED_ABOVE(hint_desc))
+ {
+ return false;
+ }
+ if (LADSPA_IS_HINT_LOGARITHMIC(hint_desc))
+ {
+ def = expf(logf(lower_bound)*0.25 + logf(upper_bound)*0.75);
+ }
+ else
+ {
+ def = lower_bound*0.25 + upper_bound*0.75;
+ }
+ }
+
+ if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hint_desc))
+ {
+ if (!LADSPA_IS_HINT_BOUNDED_ABOVE(hint_desc))
+ {
+ return false;
+ }
+ def = upper_bound;
+ }
+
+ if (LADSPA_IS_HINT_DEFAULT_0(hint_desc))
+ {
+ def = 0.0f;
+ }
+
+ if (LADSPA_IS_HINT_DEFAULT_1(hint_desc))
+ {
+ def = 1.0f;
+ }
+
+ if (LADSPA_IS_HINT_DEFAULT_100(hint_desc))
+ {
+ def = 100.0f;
+ }
+
+ if (LADSPA_IS_HINT_DEFAULT_440(hint_desc))
+ {
+ def = 440.0f;
+ }
+
+ if (LADSPA_IS_HINT_INTEGER(hint_desc))
+ {
+ def = roundf(def);
+ }
+
+ return true;
+} /* AudioLADSPAPlugin::setDefault */
+
+
+/*
+ * This file has not been truncated
+ */
diff --git a/src/async/audio/AsyncAudioLADSPAPlugin.h b/src/async/audio/AsyncAudioLADSPAPlugin.h
new file mode 100644
index 000000000..fe4624314
--- /dev/null
+++ b/src/async/audio/AsyncAudioLADSPAPlugin.h
@@ -0,0 +1,382 @@
+/**
+@file AsyncAudioLADSPAPlugin.h
+@brief A class for using a LADSPA plugin as an audio processor
+@author Tobias Blomberg / SM0SVX
+@date 2023-12-09
+
+\verbatim
+Async - A library for programming event driven applications
+Copyright (C) 2003-2024 Tobias Blomberg / SM0SVX
+
+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 2 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, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+\endverbatim
+*/
+
+/** @example AsyncAudioLADSPAPlugin_demo.cpp
+An example of how to use the AudioLADSPAPlugin class
+*/
+
+#ifndef ASYNC_AUDIO_LADSPA_PLUGIN_INCLUDED
+#define ASYNC_AUDIO_LADSPA_PLUGIN_INCLUDED
+
+
+/****************************************************************************
+ *
+ * System Includes
+ *
+ ****************************************************************************/
+
+extern "C" {
+ #include
+};
+
+#include
+#include