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 +#include + + +/**************************************************************************** + * + * Project Includes + * + ****************************************************************************/ + +#include + + +/**************************************************************************** + * + * Local Includes + * + ****************************************************************************/ + + + +/**************************************************************************** + * + * Forward declarations + * + ****************************************************************************/ + + + +/**************************************************************************** + * + * Namespace + * + ****************************************************************************/ + +namespace Async +{ + + +/**************************************************************************** + * + * Forward declarations of classes inside of the declared namespace + * + ****************************************************************************/ + + + +/**************************************************************************** + * + * Defines & typedefs + * + ****************************************************************************/ + + + +/**************************************************************************** + * + * Exported Global Variables + * + ****************************************************************************/ + + + +/**************************************************************************** + * + * Class definitions + * + ****************************************************************************/ + +/** +@brief Use a LADSPA plugin as an audio processor +@author Tobias Blomberg / SM0SVX +@date 2023-12-09 + +This class is used to load a LADSPA plugin and use it as an audio processor. + +\include AsyncAudioLADSPAPlugin_demo.cpp +*/ +class AudioLADSPAPlugin : public AudioProcessor +{ + public: + using PortNumber = decltype(LADSPA_Descriptor::PortCount); + using PluginIndex = unsigned long; + using UniqueID = unsigned long; + + /** + * @brief Find any LADSPA plugins in the given subdirectory + * @param dir The path to the directory to look in + * + * This function will go through all files in the given subdirectory, + * reading any *.so files it can find and load them as LADSPA plugins. The + * plugins are not fully initialized in this process but rather just the + * necessary calls are made to extract enough information to determine if + * this is a plugin that is compatible with this class. Any plugin found to + * be usable is put in an index so that we later can find plugins using + * their label. + */ + static bool findPluginsInDir(std::string dir); + + /** + * @brief Find LADSPA plugins in standard subdirectories + * + * LADSPA plugins are typically installed in /usr/lib64/ladspa (on a 64 bit + * x86 system). This default value is adapted automatically to match the + * target system during compilation of the software. + * + * If the LADSPA_PATH environment variable is set it will override the + * default path. + * + * This function is called by the "label variant" of the constructor if the + * plugin index is empty. + */ + static bool findPlugins(void); + + /** + * @brief Constructor + * @param path The full path of the plugin to load + * @param index The index of the plugin to instantiate + * + * This constructor is normally not used directly unless there is a special + * requirement to load a specific LADSPA plugin file with a known plugin + * index. + */ + AudioLADSPAPlugin(const std::string& path, PluginIndex index) + : m_path(path), m_index(index) {} + + /** + * @brief Constructor + * @param id The plugin unique id to look for + * + * Use this constructor for creating a LADSPA plugin instance if you know + * its unique ID. The easiest way normally is to use the label to find the + * plugin. + */ + AudioLADSPAPlugin(UniqueID id); + + /** + * @brief Constructor + * @param label The plugin label to look for + * + * This is the main constructor for creating a LADSPA plugin instance. + */ + AudioLADSPAPlugin(const std::string& label); + + /** + * @brief Disallow copy construction + */ + AudioLADSPAPlugin(const AudioLADSPAPlugin&) = delete; + + /** + * @brief Disallow copy assignment + */ + AudioLADSPAPlugin& operator=(const AudioLADSPAPlugin&) = delete; + + /** + * @brief Destructor + */ + ~AudioLADSPAPlugin(void); + + /** + * @brief Initialize the plugin + * @return Return \em true on success or else \em false is returned + * + * All loading, instantiation and initialization of the plugin is done in + * this function. The LADSPA activate call is called at the end of the + * function so it is directly ready to process audio when this function + * returns \em true. If the function returns \em false, it's not allowed to + * call any other functions so the object should be deleted as soon as + * possible. + */ + bool initialize(void); + + /** + * @brief Set a control input to the given value + * @param portno The port number to set + * @param val The value to set + */ + bool setControl(PortNumber portno, LADSPA_Data val); + + /** + * @brief Activate the plugin + * + * Use this function to activate this plugin. Activation is done in the + * initialize function so manual activation is normally not needed. Read + * more about plugin activation in the LADSPA documentation. + */ + void activate(void); + + /** + * @brief Deactivate the plugin + * + * Use this function to deactivate this plugin. Read more about plugin + * activation in the LADSPA documentation. + */ + void deactivate(void); + + /** + * @brief Get the path to the plugin + * @returns Returns the path to the plugin + */ + std::string path(void) const { return m_path; } + + /** + * @brief Get the unique ID for the plugin + * @returns Returns the unique ID of the plugin + * + * All plugins have a unique ID which can be used to find it. + */ + UniqueID uniqueId(void) const { return m_desc->UniqueID; } + + /** + * @brief Get the unique label for the plugin + * @returns Returns the unique label for the plugin + * + * The label is what most often is used to find a specific plugin. + */ + std::string label(void) const { return m_desc->Label; } + + /** + * @brief Get the name of the plugin + * @returns Returns the name of the plugin + * + * This function return the free text name/display name for the plugin. + */ + std::string name(void) const { return m_desc->Name; } + + /** + * @brief Get information on the maker of the plugin + * @returns Returns a string describing the plugin author. + */ + std::string maker(void) const { return m_desc->Maker; } + + /** + * @brief Get the copyright information for the plugin + * @returns Returns a string describing the copyright + */ + std::string copyright(void) const { return m_desc->Copyright; } + + /** + * @brief Get the number of ports for the plugin + * @returns Returns the total number of control/audio input/output ports + */ + PortNumber portCount(void) const { return m_desc->PortCount; } + + /** + * @brief Check if a port is a control port + * @returns Returns \em true if this is a control port + */ + bool portIsControl(PortNumber portno) const; + + /** + * @brief Check if a port is an audio port + * @returns Returns \em true if this is an audio port + */ + bool portIsAudio(PortNumber portno) const; + + /** + * @brief Check if a port is an input port + * @returns Returns \em true if this is an input port + */ + bool portIsInput(PortNumber portno) const; + + /** + * @brief Check if a port is an output port + * @returns Returns \em true if this is an output port + */ + bool portIsOutput(PortNumber portno) const; + + /** + * @brief Print some useful information for the plugin + */ + void print(const std::string& prefix=""); + + protected: + /** + * @brief Process incoming samples and put them into the output buffer + * @param dest Destination buffer + * @param src Source buffer + * @param count Number of samples in the source buffer + * + * This function should be reimplemented by the inheriting class to + * do the actual processing of the incoming samples. All samples must + * be processed, otherwise they are lost and the output buffer will + * contain garbage. + */ + virtual void processSamples(float *dest, const float *src, + int count) override; + + private: + struct InstanceInfo + { + std::string m_path; + PluginIndex m_index; + UniqueID m_unique_id; + std::string m_label; + }; + using InstanceInfoP = std::shared_ptr; + using LabelMap = std::map; + using IDMap = std::map; + + static const PortNumber NOPORT = 9999UL; + + static IDMap& idMap(void) + { + static IDMap id_map; + return id_map; + } + + static LabelMap& labelMap(void) + { + static LabelMap label_map; + return label_map; + } + + const LADSPA_Descriptor* ladspaDescriptor(void); + bool setDefault(PortNumber portno); + + std::string m_path; + void* m_handle = nullptr; + PluginIndex m_index = 0; + const LADSPA_Descriptor* m_desc = nullptr; + LADSPA_Handle m_inst_handle = nullptr; + bool m_is_active = false; + LADSPA_Data* m_ctrl_buf = nullptr; + PortNumber m_sample_input_port = NOPORT; + PortNumber m_sample_output_port = NOPORT; + +}; /* class AudioLADSPAPlugin */ + + +} /* namespace Async */ + +#endif /* ASYNC_AUDIO_LADSPA_PLUGIN_INCLUDED */ + +/* + * This file has not been truncated + */ diff --git a/src/async/audio/AsyncAudioSplitter.cpp b/src/async/audio/AsyncAudioSplitter.cpp index 905ea9355..77a4b3773 100644 --- a/src/async/audio/AsyncAudioSplitter.cpp +++ b/src/async/audio/AsyncAudioSplitter.cpp @@ -6,7 +6,7 @@ \verbatim Async - A library for programming event driven applications -Copyright (C) 2004-2015 Tobias Blomberg / SM0SVX +Copyright (C) 2004-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 @@ -324,12 +324,12 @@ void AudioSplitter::enableSink(AudioSink *sink, bool enable) int AudioSplitter::writeSamples(const float *samples, int len) { do_flush = false; - - if (len == 0) + + if (len <= 0) { return 0; } - + if (buf_len > 0) { input_stopped = true; diff --git a/src/async/audio/AsyncAudioSplitter.h b/src/async/audio/AsyncAudioSplitter.h index ad9a9b13d..4fa6ce3c0 100644 --- a/src/async/audio/AsyncAudioSplitter.h +++ b/src/async/audio/AsyncAudioSplitter.h @@ -6,7 +6,7 @@ \verbatim Async - A library for programming event driven applications -Copyright (C) 2004-2015 Tobias Blomberg / SM0SVX +Copyright (C) 2004-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 @@ -166,8 +166,8 @@ class AudioSplitter : public Async::AudioSink, public Async::AudioSource, * function in the source have been called. * This function is normally only called from a connected source object. */ - int writeSamples(const float *samples, int len); - + int writeSamples(const float *samples, int len) override; + /** * @brief Tell the sink to flush the previously written samples * @@ -176,9 +176,8 @@ class AudioSplitter : public Async::AudioSink, public Async::AudioSource, * sourceAllSamplesFlushed function. * This function is normally only called from a connected source object. */ - void flushSamples(void); - - + void flushSamples(void) override; + protected: private: diff --git a/src/async/audio/CMakeLists.txt b/src/async/audio/CMakeLists.txt index 504416a52..6e9fcafb4 100644 --- a/src/async/audio/CMakeLists.txt +++ b/src/async/audio/CMakeLists.txt @@ -110,6 +110,11 @@ if(OGG_FOUND AND Opus_FOUND) set(LIBSRC ${LIBSRC} AsyncAudioContainerOpus.cpp) endif(OGG_FOUND AND Opus_FOUND) +if(LADSPA_FOUND) + set(EXPINC ${EXPINC} AsyncAudioLADSPAPlugin.h) + set(LIBSRC ${LIBSRC} AsyncAudioLADSPAPlugin.cpp) +endif(LADSPA_FOUND) + if(USE_ALSA) set(LIBSRC ${LIBSRC} AsyncAudioDeviceAlsa.cpp) find_package(ALSA REQUIRED QUIET) diff --git a/src/async/audio/fidlib.c b/src/async/audio/fidlib.c index 3851bc626..d89fdf407 100644 --- a/src/async/audio/fidlib.c +++ b/src/async/audio/fidlib.c @@ -2150,7 +2150,6 @@ fid_parse(double rate, char **pp, FidFilter **ffp) { char *rv= Alloc(INIT_LEN); char *rvend= rv + INIT_LEN; char *rvp= rv; - char *tmp; #undef INIT_LEN FidFilter *curr; int xtra= FFCSIZE(0,0); @@ -2159,10 +2158,25 @@ fid_parse(double rate, char **pp, FidFilter **ffp) { char dmy; #define ERR(ptr, msg) { free(rv); *pp= ptr; *ffp= 0; return msg; } + +#if 0 #define INCBUF { tmp= realloc(rv, (rvend-rv) * 2); if (!tmp) error("Out of memory"); \ rvend= (rvend-rv) * 2 + tmp; rvp= (rvp-rv) + tmp; \ curr= (void*)(((char*)curr) - rv + tmp); rv= tmp; } - +#else +/* Fix warning: pointer ‘rv’ used after ‘realloc’ [-Wuse-after-free] */ +#define INCBUF { \ + int fsize= rvend - rv; \ + int fpsize= rvp - rv; \ + int fcsize= ((char*)curr) - rv; \ + rv= realloc(rv, fsize * 2); \ + if (!rv) error("Out of memory"); \ + rvend= fsize * 2 + rv; \ + rvp= fpsize + rv; \ + curr= (void*)(fcsize + rv); \ +} +#endif + while (1) { rew= p; if (!grabWord(&p, buf, sizeof(buf))) { @@ -2178,14 +2192,17 @@ fid_parse(double rate, char **pp, FidFilter **ffp) { case ')': case ']': case '}': - // End of filter, return it - tmp= realloc(rv, (rvp-rv) + xtra); - if (!tmp) error("Out of memory"); - curr= (void*)((rvp-rv) + tmp); - curr->typ= 0; curr->cbm= 0; curr->len= 0; - *pp= buf[0] ? (p-1) : p; - *ffp= (FidFilter*)tmp; - return 0; + { + // End of filter, return it + int fpsize = rvp - rv; + rv= realloc(rv, fpsize + xtra); + if (!rv) error("Out of memory"); + curr= (void*)(fpsize + rv); + curr->typ= 0; curr->cbm= 0; curr->len= 0; + *pp= buf[0] ? (p-1) : p; + *ffp= (FidFilter*)rv; + return 0; + } case '/': if (typ > 0) ERR(rew, strdupf("Filter syntax error; unexpected '/'")); typ= 'I'; diff --git a/src/async/core/AsyncDnsLookupWorker.h b/src/async/core/AsyncDnsLookupWorker.h index 5f321b189..af128f23a 100644 --- a/src/async/core/AsyncDnsLookupWorker.h +++ b/src/async/core/AsyncDnsLookupWorker.h @@ -11,7 +11,7 @@ used internally by the async library. \verbatim Async - A library for programming event driven applications -Copyright (C) 2003-2022 Tobias Blomberg +Copyright (C) 2003-2024 Tobias Blomberg 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 @@ -322,7 +322,8 @@ class DnsLookupWorker { if (rr->type() == DnsResourceRecordSRV::staticType()) { - auto srv_rr = static_cast(rr); + auto srv_rr = dynamic_cast(rr); + assert(srv_rr != nullptr); m_srv_records.insert(srv_rr); m_srv_weight_sum[srv_rr->prio()] += srv_rr->weight(); } diff --git a/src/async/core/AsyncDnsResourceRecord.h b/src/async/core/AsyncDnsResourceRecord.h index 32078388d..e6f618fb5 100644 --- a/src/async/core/AsyncDnsResourceRecord.h +++ b/src/async/core/AsyncDnsResourceRecord.h @@ -6,7 +6,7 @@ \verbatim Async - A library for programming event driven applications -Copyright (C) 2003-2022 Tobias Blomberg / SM0SVX +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 @@ -322,7 +322,7 @@ class DnsResourceRecordCRTP : public DnsResourceRecord * @brief The type of record * @return Return the type of this record */ - virtual const Type type(void) const { return Derived::staticType(); } + virtual const Type type(void) const override { return Derived::staticType(); } }; /* DnsResourceRecordCRTP */ diff --git a/src/async/core/AsyncFramedTcpConnection.h b/src/async/core/AsyncFramedTcpConnection.h index 24e108e17..28476906b 100644 --- a/src/async/core/AsyncFramedTcpConnection.h +++ b/src/async/core/AsyncFramedTcpConnection.h @@ -6,7 +6,7 @@ \verbatim Async - A library for programming event driven applications -Copyright (C) 2003-2022 Tobias Blomberg / SM0SVX +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 @@ -209,6 +209,8 @@ class FramedTcpConnection : public TcpConnection sigc::signal dataReceived; sigc::signal sendBufferFull; + FramedTcpConnection& operator=(const FramedTcpConnection&) = delete; + /** * @brief Disconnect from the remote peer * @@ -282,8 +284,7 @@ class FramedTcpConnection : public TcpConnection std::vector m_frame; TxQueue m_txq; - FramedTcpConnection(const FramedTcpConnection&); - FramedTcpConnection& operator=(const FramedTcpConnection&); + FramedTcpConnection(const FramedTcpConnection&) = delete; void onSendBufferFull(bool is_full); void disconnectCleanup(void); diff --git a/src/async/core/AsyncHttpServerConnection.h b/src/async/core/AsyncHttpServerConnection.h index ea089d60d..72e44fe92 100644 --- a/src/async/core/AsyncHttpServerConnection.h +++ b/src/async/core/AsyncHttpServerConnection.h @@ -6,7 +6,7 @@ \verbatim Async - A library for programming event driven applications -Copyright (C) 2003-2022 Tobias Blomberg / SM0SVX +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 @@ -346,6 +346,7 @@ class HttpServerConnection : public TcpConnection HttpServerConnection(const HttpServerConnection&); HttpServerConnection& operator=(const HttpServerConnection&); + using TcpConnection::write; void handleStartLine(void); void handleHeader(void); void onSendBufferFull(bool is_full); diff --git a/src/async/core/AsyncTcpClient.h b/src/async/core/AsyncTcpClient.h index dd2344dc8..fb05823bf 100644 --- a/src/async/core/AsyncTcpClient.h +++ b/src/async/core/AsyncTcpClient.h @@ -9,7 +9,7 @@ to a remote host. See usage instructions in the class definition. \verbatim Async - A library for programming event driven applications -Copyright (C) 2003-2022 Tobias Blomberg +Copyright (C) 2003-2024 Tobias Blomberg 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 @@ -216,6 +216,9 @@ class TcpClient : public ConT, public TcpClientBase TcpClientBase::closeConnection(); } + using ConT::operator=; + using TcpClientBase::operator=; + private: }; /* class TcpClient */ diff --git a/src/async/core/AsyncTcpPrioClient.h b/src/async/core/AsyncTcpPrioClient.h index a68a04823..a078a4a29 100644 --- a/src/async/core/AsyncTcpPrioClient.h +++ b/src/async/core/AsyncTcpPrioClient.h @@ -6,7 +6,7 @@ \verbatim Async - A library for programming event driven applications -Copyright (C) 2003-2022 Tobias Blomberg / SM0SVX +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 @@ -182,6 +182,9 @@ class TcpPrioClient : public ConT, public TcpPrioClientBase //} protected: + using ConT::operator=; + using TcpPrioClientBase::operator=; + /** * @brief Disconnect from the remote peer * diff --git a/src/async/core/AsyncTcpPrioClientBase.h b/src/async/core/AsyncTcpPrioClientBase.h index 366c8b5a5..28e7c472d 100644 --- a/src/async/core/AsyncTcpPrioClientBase.h +++ b/src/async/core/AsyncTcpPrioClientBase.h @@ -9,7 +9,7 @@ Async::TcpPrioClient for more information. \verbatim Async - A library for programming event driven applications -Copyright (C) 2003-2022 Tobias Blomberg +Copyright (C) 2003-2024 Tobias Blomberg 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 @@ -269,6 +269,8 @@ class TcpPrioClientBase : public TcpClientBase bool isPrimary(void) const; + using TcpClientBase::operator=; + protected: /** * @brief Must be called from the inheriting class constructor diff --git a/src/async/demo/AsyncAudioLADSPAPlugin_demo.cpp b/src/async/demo/AsyncAudioLADSPAPlugin_demo.cpp new file mode 100644 index 000000000..733d04c21 --- /dev/null +++ b/src/async/demo/AsyncAudioLADSPAPlugin_demo.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include + +int main(void) +{ + Async::CppApplication app; + + Async::AudioIO audio_io("alsa:default", 0); + audio_io.open(Async::AudioIO::MODE_RDWR); + Async::AudioSource* prev_src = &audio_io; + + std::string label("tap_pitch"); + Async::AudioLADSPAPlugin p1(label); + if (!p1.initialize()) + { + std::cout << "*** ERROR: Could not instantiate LADSPA plugin instance " + "with label " << label << std::endl; + exit(1); + } + p1.setControl(0, 4); + p1.print(); + prev_src->registerSink(&p1); + prev_src = &p1; + + label = "tap_vibrato"; + Async::AudioLADSPAPlugin p2(label); + if (!p2.initialize()) + { + std::cout << "*** ERROR: Could not instantiate LADSPA plugin instance " + "with label " << label << std::endl; + exit(1); + } + p2.setControl(0, 10); + p2.setControl(1, 10); + p2.print(); + prev_src->registerSink(&p2); + prev_src = &p2; + + prev_src->registerSink(&audio_io); + + app.exec(); + + return 0; +} diff --git a/src/async/demo/CMakeLists.txt b/src/async/demo/CMakeLists.txt index 231c8fbe4..2394aad1d 100644 --- a/src/async/demo/CMakeLists.txt +++ b/src/async/demo/CMakeLists.txt @@ -11,6 +11,10 @@ set(CPPPROGS AsyncAudioIO_demo AsyncDnsLookup_demo AsyncFdWatch_demo set(QTPROGS AsyncQtApplication_demo) +if(LADSPA_FOUND) + set(CPPPROGS ${CPPPROGS} AsyncAudioLADSPAPlugin_demo) +endif(LADSPA_FOUND) + # Build all demo applications foreach(prog ${CPPPROGS}) diff --git a/src/async/demo/DemoPluginBase.h b/src/async/demo/DemoPluginBase.h index 657f90d7f..c9fd419e6 100644 --- a/src/async/demo/DemoPluginBase.h +++ b/src/async/demo/DemoPluginBase.h @@ -8,7 +8,7 @@ class DemoPluginBase : public Async::Plugin // Return the name of this plugin type static std::string typeName(void) { return "DemoPlugin"; } - // Pluging base class constructor + // Plugin base class constructor DemoPluginBase(void) { std::cout << "### DemoPluginBase::DemoPluginBase" << std::endl; diff --git a/src/cmake/Modules/FindLADSPA.cmake b/src/cmake/Modules/FindLADSPA.cmake new file mode 100644 index 000000000..f4382a1ad --- /dev/null +++ b/src/cmake/Modules/FindLADSPA.cmake @@ -0,0 +1,84 @@ +#.rst: +# FindLADSPA +# -------- +# Find the LADSPA include file +# +# LADSPA_FOUND - Set to true if the ladspa include file is found +# LADSPA_INCLUDE_DIRS - The directory where ladspa.h can be found +# LADSPA_PLUGIN_DIRS - The directories where LADSPA plugins can be found +# LADSPA_VERSION - Full version string (if available) +# LADSPA_VERSION_MAJOR - Major version (if available) +# LADSPA_VERSION_MINOR - Minor version (if available) + +#============================================================================= +# 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 +#============================================================================= + +if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12) + message(AUTHOR_WARNING + "Your project should require at least CMake 2.6 to use FindLADSPA.cmake") +endif() + +# Try to find the directory where the ladspa.h header file is located +find_path(LADSPA_INCLUDE_DIR + NAMES ladspa.h + DOC "LADSPA include directory" +) + +# Set up version variables if the include file was found +if (LADSPA_INCLUDE_DIR) + file(READ "${LADSPA_INCLUDE_DIR}/ladspa.h" ladspa_h) + string(REGEX MATCH "#define LADSPA_VERSION \"([^ ]+)\"" _ ${ladspa_h}) + set(LADSPA_VERSION ${CMAKE_MATCH_1}) + string(REGEX MATCH "#define LADSPA_VERSION_MAJOR ([0-9]+)" _ ${ladspa_h}) + set(LADSPA_VERSION_MAJOR ${CMAKE_MATCH_1}) + string(REGEX MATCH "#define LADSPA_VERSION_MINOR ([0-9]+)" _ ${ladspa_h}) + set(LADSPA_VERSION_MINOR ${CMAKE_MATCH_1}) +endif (LADSPA_INCLUDE_DIR) + +# Find the LADSPA plugin directory +include(GNUInstallDirs) +find_file(LADSPA_PLUGIN_DIR + NAMES ladspa + DOC "LADSPA plugin directory" + PATHS /usr/${CMAKE_INSTALL_LIBDIR} /usr/lib + NO_DEFAULT_PATH +) + +# Handle the version, QUIETLY and REQUIRED arguments and set LADSPA_FOUND to +# TRUE if all required variables are available +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LADSPA + FOUND_VAR LADSPA_FOUND + REQUIRED_VARS LADSPA_INCLUDE_DIR LADSPA_PLUGIN_DIR + VERSION_VAR LADSPA_VERSION +) + +# Set up other standard variables if LADSPA was found +if(LADSPA_FOUND) + set(LADSPA_INCLUDE_DIRS ${LADSPA_INCLUDE_DIR}) + set(LADSPA_PLUGIN_DIRS "${LADSPA_PLUGIN_DIR}") + set(LADSPA_DEFINITIONS + -DLADSPA_PLUGIN_DIRS="${LADSPA_PLUGIN_DIRS}" + -DLADSPA_VERSION=${LADSPA_VERSION} + -DLADSPA_VERSION_MAJOR=${LADSPA_VERSION_MAJOR} + -DLADSPA_VERSION_MINOR=${LADSPA_VERSION_MINOR}) +endif(LADSPA_FOUND) + +# Hide these variables for normal usage +mark_as_advanced(LADSPA_INCLUDE_DIR LADSPA_PLUGIN_DIR) + diff --git a/src/doc/man/svxlink.conf.5 b/src/doc/man/svxlink.conf.5 index fdc771020..9215f7cc9 100644 --- a/src/doc/man/svxlink.conf.5 +++ b/src/doc/man/svxlink.conf.5 @@ -1,4 +1,4 @@ -.TH SVXLINK.CONF 5 "OCTOBER 2023" Linux "File Formats" +.TH SVXLINK.CONF 5 "JANUARY 2024" Linux "File Formats" . .SH NAME . @@ -1636,11 +1636,13 @@ or commenting it out will disable DTMF detection. .TP .B DTMF_MUTING Mute the audio during the time when a DTMF digit is being received. Note that -the audio will be delayed 75ms to give the DTMF detector time to do its work. +the audio will be delayed like 40-75ms, depending on the DTMF decoder used, to +give the decoder time to do its work. This does not matter much on a simplex link but on a repeater it could be -annoying since you will hear the last 75 milliseconds of your own transmission. +annoying since you will hear the last 40-75 milliseconds of your own +transmission. To counteract the added delay one can set up the SQL_TAIL_ELIM configuration -variable to at least 75 milliseconds. +variable to at least 40 milliseconds. Legal values for DTMF_MUTING are 0=disabled, 1=enabled. .TP .B DTMF_HANGTIME @@ -1812,6 +1814,35 @@ be mFM;. It is possible to specify the same PTY for multiple functions (e.g. squelch, ptt etc) in both TX and RX configurations. This may be good if there is one script handling all functions. +.TP +.B LADSPA_PLUGINS +Used to set up one or more LADSPA plugins to process the received audio. The +processing chain is applied just before the final stages of the receiver audio +processing and will only affect how the audio sounds. Any digital signal +processing done on the audio before that, like DTMF, CTCSS etc, is unaffected. + +LADSPA is a framework for sharing pluggable audio components and there are +numerous plugins to choose from. Each plugin has a name ("label") and zero or +more configuration parameters ("control input ports"). To find out which +plugins are installed on your system, use the "listplugins" command. If you +are missing some plugin it may be available to install using your package +system (rpm/yum/dnf, deb/apt etc). To find out more information about a +plugin, use the "analyseplugin" command (e.g. analyseplugin filter hpf). Learn +more about the LADSPA framework in their official documentation. + +In SvxLink only plugins that have exactly one audio input and one audio +output can be used, so all stereo plugins are excluded for example. The same +thing is valid for input-only or output-only plugins. + +The analyseplugin utility will, among other things, list the ports. All ports +that are typed as "input, control" can be used to configure the plugin. Valid +range and a default value may also be listed. The format for the +LADSPA_PLUGINS configuration variable in SvxLink is +"label1:port1:port2:...:portN,label2:port1:port2:...:portN,...". Note that +only control input ports are counted here so when applying the parameters any +intermingled ports of other types are skipped. + +Example: LADSPA_PLUGINS=hpf:1000,tap_dynamics_m:4:500:15:15:13 . .SS Ddr Receiver Section . diff --git a/src/svxlink/ChangeLog b/src/svxlink/ChangeLog index 8d2f4bf70..1aacba37a 100644 --- a/src/svxlink/ChangeLog +++ b/src/svxlink/ChangeLog @@ -278,6 +278,13 @@ * New configuration variable TG_SELECT_INHIBIT_TIMEOUT that is used to separately control the TG select inhibit feature. +* Bugfix for the 1750_MUTING configuration variable. Setting it to 0 did not + disable the feature. It had to be commented out. + +* It's now possible to use LADSPA plugins to shape received audio. Fore more + information have a look at the documentation for the LADSPA_PLUGINS + configuration variable. + 1.7.0 -- 01 Sep 2019 diff --git a/src/svxlink/svxlink/CMakeLists.txt b/src/svxlink/svxlink/CMakeLists.txt index 30481b8bb..2b0bea147 100644 --- a/src/svxlink/svxlink/CMakeLists.txt +++ b/src/svxlink/svxlink/CMakeLists.txt @@ -81,6 +81,7 @@ set(LIBS trx locationinfo asynccpp asyncaudio asynccore svxmisc ${LIBS}) add_executable(svxlink ${SVXLINK_SRCS} ${VERSION_DEPENDS}) target_link_libraries(svxlink ${LIBS}) set_target_properties(svxlink PROPERTIES + ENABLE_EXPORTS on RUNTIME_OUTPUT_DIRECTORY ${RUNTIME_OUTPUT_DIRECTORY} ) diff --git a/src/svxlink/svxlink/svxlink.conf.in b/src/svxlink/svxlink/svxlink.conf.in index cbc747e9b..380468326 100644 --- a/src/svxlink/svxlink/svxlink.conf.in +++ b/src/svxlink/svxlink/svxlink.conf.in @@ -270,6 +270,7 @@ DTMF_SERIAL=/dev/ttyS0 #1750_MUTING=1 #SEL5_DEC_TYPE=INTERNAL #SEL5_TYPE=ZVEI1 +#LADSPA_PLUGINS=hpf:1000 #FQ=433475000 #MODULATION=FM #WBRX=WbRx1 diff --git a/src/svxlink/trx/CMakeLists.txt b/src/svxlink/trx/CMakeLists.txt index cb52bc7ea..ca4643341 100644 --- a/src/svxlink/trx/CMakeLists.txt +++ b/src/svxlink/trx/CMakeLists.txt @@ -62,7 +62,7 @@ else (RTLSDR_FOUND) endif (RTLSDR_FOUND) # Find GPIOD -find_package(GPIOD) +find_package(GPIOD 1 EXACT) if (GPIOD_FOUND) set(LIBS ${LIBS} ${GPIOD_LIBRARIES}) include_directories(${GPIOD_INCLUDE_DIRS}) diff --git a/src/svxlink/trx/LocalRxBase.cpp b/src/svxlink/trx/LocalRxBase.cpp index 86acb62c5..e354673ca 100644 --- a/src/svxlink/trx/LocalRxBase.cpp +++ b/src/svxlink/trx/LocalRxBase.cpp @@ -64,6 +64,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include #include #include +#ifdef LADSPA_VERSION +#include +#endif /**************************************************************************** @@ -266,7 +269,8 @@ bool LocalRxBase::initialize(void) int delay_line_len = 0; bool mute_1750 = false; - if (cfg().getValue(name(), "1750_MUTING", mute_1750)) + cfg().getValue(name(), "1750_MUTING", mute_1750); + if (mute_1750) { delay_line_len = max(delay_line_len, TONE_1750_MUTING_PRE); } @@ -597,11 +601,65 @@ bool LocalRxBase::initialize(void) // elimination), create it if (delay_line_len > 0) { + std::cout << name() << ": Delay line (for DTMF muting etc) set to " + << delay_line_len << " ms" << std::endl; delay = new AudioDelayLine(delay_line_len); prev_src->registerSink(delay, true); prev_src = delay; } +#ifdef LADSPA_VERSION + std::vector ladspa_plugin_cfg; + if (cfg().getValue(name(), "LADSPA_PLUGINS", ladspa_plugin_cfg)) + { + for (const auto& pcfg : ladspa_plugin_cfg) + { + std::istringstream is(pcfg); + std::string label; + std::getline(is, label, ':'); + //std::cout << "### pcfg=" << pcfg << " label=" << label << std::endl; + auto plug = new Async::AudioLADSPAPlugin(label); + if (!plug->initialize()) + { + std::cout << "*** ERROR: Could not instantiate LADSPA plugin instance " + "with label \"" << label << "\"" << std::endl; + return false; + } + unsigned long portno = 0; + LADSPA_Data val; + while (is >> val) + { + while ((portno < plug->portCount()) && + !(plug->portIsControl(portno) && plug->portIsInput(portno))) + { + ++portno; + } + if (portno >= plug->portCount()) + { + std::cerr << "*** ERROR: Too many parameters specified for LADSPA " + "plugin \"" << plug->label() + << "\" in configuration variable " << name() + << "/LADSPA_PLUGINS." << std::endl; + return false; + } + plug->setControl(portno++, val); + char colon = 0; + if ((is >> colon) && (colon != ':')) + { + std::cerr << "*** ERROR: Illegal format for " << name() + << "/LADSPA_PLUGINS configuration variable" << std::endl; + return false; + } + } + + plug->print(name() + ": "); + + prev_src->registerSink(plug, true); + prev_src = plug; + } + } +#endif + // Add a limiter to smoothly limit the audio before hard clipping it double limiter_thresh = DEFAULT_LIMITER_THRESH; cfg().getValue(name(), "LIMITER_THRESH", limiter_thresh); @@ -634,7 +692,7 @@ bool LocalRxBase::initialize(void) // Set the previous audio pipe object to handle audio distribution for // the LocalRxBase class - setHandler(prev_src); + setAudioSourceHandler(prev_src); cfg().getValue(name(), "AUDIO_DEV_KEEP_OPEN", audio_dev_keep_open); diff --git a/src/svxlink/trx/NetTrxTcpClient.h b/src/svxlink/trx/NetTrxTcpClient.h index e9e846ec6..4f8caff94 100644 --- a/src/svxlink/trx/NetTrxTcpClient.h +++ b/src/svxlink/trx/NetTrxTcpClient.h @@ -6,7 +6,7 @@ \verbatim SvxLink - A Multi Purpose Voice Services System for Ham Radio Use -Copyright (C) 2003-2008 Tobias Blomberg / SM0SVX +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 @@ -225,6 +225,8 @@ class NetTrxTcpClient : public Async::TcpClient<> DiscReason disc_reason; NetTrxTcpClient(const NetTrxTcpClient&); + using TcpClientBase::operator=; + using TcpConnection::operator=; NetTrxTcpClient& operator=(const NetTrxTcpClient&); void tcpConnected(void); void tcpDisconnected(Async::TcpConnection *con, diff --git a/src/svxlink/trx/Rx.cpp b/src/svxlink/trx/Rx.cpp index 742764671..498bef18e 100644 --- a/src/svxlink/trx/Rx.cpp +++ b/src/svxlink/trx/Rx.cpp @@ -6,7 +6,7 @@ \verbatim SvxLink - A Multi Purpose Voice Services System for Ham Radio Use -Copyright (C) 2003-2008 Tobias Blomberg / SM0SVX +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 @@ -224,6 +224,7 @@ Rx::Rx(Config &cfg, const string& name) Rx::~Rx(void) { delete m_sql_tmo_timer; + m_sql_tmo_timer = nullptr; } /* Rx::~Rx */ @@ -242,9 +243,9 @@ bool Rx::initialize(void) } } */ - + return true; - + } /* Rx::initialize */ @@ -341,6 +342,11 @@ void Rx::setSquelchState(bool is_open, const std::string& info) } /* Rx::setSquelchState */ +void Rx::setAudioSourceHandler(Async::AudioSource* src) +{ + setHandler(src); +} /* Rx::setAudioSourceHandler */ + /**************************************************************************** * diff --git a/src/svxlink/trx/Rx.h b/src/svxlink/trx/Rx.h index dbaa3faf8..24de8d019 100644 --- a/src/svxlink/trx/Rx.h +++ b/src/svxlink/trx/Rx.h @@ -6,7 +6,7 @@ \verbatim SvxLink - A Multi Purpose Voice Services System for Ham Radio Use -Copyright (C) 2003-2018 Tobias Blomberg / SM0SVX +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 @@ -323,14 +323,16 @@ class Rx : public sigc::trackable, public Async::AudioSource */ void setSquelchState(bool is_open, const std::string& info=""); + void setAudioSourceHandler(Async::AudioSource* src); + private: - std::string m_name; - bool m_verbose; - bool m_sql_open; - Async::Config& m_cfg; - Async::Timer* m_sql_tmo_timer; - std::string m_sql_info; - MuteState m_mute_state; + std::string m_name; + bool m_verbose; + bool m_sql_open; + Async::Config& m_cfg; + Async::Timer* m_sql_tmo_timer; + std::string m_sql_info; + MuteState m_mute_state; void sqlTimeout(Async::Timer *t); diff --git a/src/svxlink/trx/SquelchCombine.cpp b/src/svxlink/trx/SquelchCombine.cpp index 614470f81..ed631bd23 100644 --- a/src/svxlink/trx/SquelchCombine.cpp +++ b/src/svxlink/trx/SquelchCombine.cpp @@ -423,7 +423,7 @@ bool SquelchCombine::initialize(Async::Config& cfg, return false; } - std::cout << rx_name << " combined squelch structure: "; + std::cout << rx_name << ": Combined squelch structure is "; m_comb->print(std::cout); std::cout << std::endl; diff --git a/src/template.cpp b/src/template.cpp index cabd50878..7c1c5e1e2 100644 --- a/src/template.cpp +++ b/src/template.cpp @@ -2,13 +2,13 @@ @file MyNamespaceTemplate.cpp @brief A_brief_description_for_this_file @author Tobias Blomberg / SM0SVX -@date 2023- +@date 2024- A_detailed_description_for_this_file \verbatim -Copyright (C) 2003-2023 Tobias Blomberg / SM0SVX +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 diff --git a/src/template.h b/src/template.h index 82970b6ae..ed6ccb748 100644 --- a/src/template.h +++ b/src/template.h @@ -2,13 +2,13 @@ @file MyNamespaceTemplate.h @brief A_brief_description_for_this_file @author Tobias Blomberg / SM0SVX -@date 2023- +@date 2024- A_detailed_description_for_this_file \verbatim -Copyright (C) 2003-2023 Tobias Blomberg / SM0SVX +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 @@ -109,7 +109,7 @@ namespace MyNamespace /** @brief A_brief_class_description @author Tobias Blomberg / SM0SVX -@date 2023- +@date 2024- A_detailed_class_description diff --git a/src/versions b/src/versions index c2ca7ba8a..e02ba274b 100644 --- a/src/versions +++ b/src/versions @@ -8,10 +8,10 @@ QTEL=1.2.4.99.6 LIBECHOLIB=1.3.3.99.3 # Version for the Async library -LIBASYNC=1.6.99.26 +LIBASYNC=1.6.99.28 # SvxLink versions -SVXLINK=1.7.99.91 +SVXLINK=1.7.99.95 MODULE_HELP=1.0.0 MODULE_PARROT=1.1.1 MODULE_ECHO_LINK=1.5.99.5