Merge branch 'dev_routing' of https://github.com/rerdavies/pipedal into dev_tone3000

This commit is contained in:
Robin E.R. Davies
2026-02-10 20:18:09 -05:00
48 changed files with 2959 additions and 595 deletions
-1
View File
@@ -13,7 +13,6 @@
"/usr/include/x86_64-linux-gnu", "/usr/include/x86_64-linux-gnu",
"/usr/lib" "/usr/lib"
], ],
"compilerPath": "/usr/bin/gcc-12",
"cStandard": "c17", "cStandard": "c17",
"cppStandard": "c++20", "cppStandard": "c++20",
"intelliSenseMode": "linux-gcc-arm64", "intelliSenseMode": "linux-gcc-arm64",
+19 -4
View File
@@ -1,16 +1,27 @@
cmake_minimum_required(VERSION 3.16.0) cmake_minimum_required(VERSION 3.16.0)
project(pipedal project(pipedal
VERSION 1.5.98 VERSION 1.5.99
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi" DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
HOMEPAGE_URL "https://rerdavies.github.io/pipedal" HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
) )
execute_process( COMMAND dpkg --print-architecture COMMAND tr -d '\n' OUTPUT_VARIABLE DEBIAN_ARCHITECTURE ) execute_process( COMMAND dpkg --print-architecture COMMAND tr -d '\n' OUTPUT_VARIABLE DEBIAN_ARCHITECTURE )
set (DISPLAY_VERSION "PiPedal v1.5.98-Beta") set (DISPLAY_VERSION "PiPedal v1.5.99-Beta")
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE}) set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
set (CMAKE_INSTALL_PREFIX "/usr/") set (CMAKE_INSTALL_PREFIX "/usr/")
set (LIBZIP_DO_INSTALL OFF CACHE BOOL "Disable libzip install" FORCE)
set (SQLITECPP_INSTALL OFF CACHE BOOL "Disable SQLiteCpp install" FORCE)
set(SUBMODULE_INSTALL OFF CACHE BOOL "" FORCE)
# Remove SQLiteCPP install files.
set(CPACK_SOURCE_IGNORE_FILES
"*.a;sqlite3.h;$(CPACK_SOURCE_IGNORE_FILES)"
)
set (PIPEDAL_EXCLUDE_TESTS false) set (PIPEDAL_EXCLUDE_TESTS false)
include(CTest) include(CTest)
enable_testing() enable_testing()
@@ -96,9 +107,9 @@ set(CPACK_DEBIAN_PACKAGE_SECTION sound)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE) set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE)
# Accept either libzip4 or libzip5 to satisfy libzip 4 linkage (because Ubuntu nonsense)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libicu-dev ,lv2-dev, ffmpeg, authbind, curl, gpg, alsa-base| pipewire, alsa-utils")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libicu-dev ,lv2-dev,iw, ffmpeg, authbind, curl, gpg, alsa-base| pipewire, alsa-utils")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE}) set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
set(CPACK_PACKAGING_INSTALL_PREFIX /usr) set(CPACK_PACKAGING_INSTALL_PREFIX /usr)
set(CPACK_PROJECT_NAME ${PROJECT_NAME}) set(CPACK_PROJECT_NAME ${PROJECT_NAME})
@@ -118,4 +129,8 @@ set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
) )
include(CPack) include(CPack)
message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
+2 -2
View File
@@ -562,8 +562,8 @@ void json_reader::skip_array()
void json_reader::skip_object() void json_reader::skip_object()
{ {
int c;
consume('{'); consume('{');
int c;
while (true) while (true)
{ {
c = peek(); c = peek();
@@ -571,7 +571,7 @@ void json_reader::skip_object()
if (c == '}') if (c == '}')
{ {
c = get(); c = get();
break; return;
} }
skip_string(); // name. skip_string(); // name.
consume(':'); consume(':');
+2 -2
View File
@@ -9,11 +9,11 @@
<img src="https://img.shields.io/github/downloads/rerdavies/pipedal/total?color=%23808080&link=https%3A%2F%2Frerdavies.github.io%2Fpipedal%2Fdownload.html"/> <img src="https://img.shields.io/github/downloads/rerdavies/pipedal/total?color=%23808080&link=https%3A%2F%2Frerdavies.github.io%2Fpipedal%2Fdownload.html"/>
Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.5.98</a> Download:&nbsp;<a href='https://rerdavies.github.io/pipedal/download.html'>v1.5.99</a>
Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal). Website:&nbsp;[https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html). Documentation:&nbsp;[https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
#### NEW version 1.5.98 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. TooB NAM calibration, manipulation of i/r files in Toob Convolution Reverb, new Parametric EQ and 3 Band EQ plugins, and MORE! #### NEW version 1.5.99 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details. TooB NAM calibration, manipulation of i/r files in Toob Convolution Reverb, new Parametric EQ and 3 Band EQ plugins, and MORE!
&nbsp; &nbsp;
+5 -5
View File
@@ -13,18 +13,18 @@ page_icon: img/Install4.jpg
Download the most recent Debian (.deb) package for your platform: Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (aarch64) v1.5.98](https://github.com/rerdavies/pipedal/releases/download/v1.5.98/pipedal_1.5.98_arm64.deb) - [Raspberry Pi OS bookworm (aarch64) v1.5.99](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_arm64.deb)
- [Ubuntu 24.04 through 25.04 (aarch64) v1.5.98](https://github.com/rerdavies/pipedal/releases/download/v1.5.98/pipedal_1.5.98_arm64.deb) - [Ubuntu 24.04 through 25.04 (aarch64) v1.5.99](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_arm64.deb)
- [Ubuntu 24.04 through 25.04 (amd64) v1.5.98](https://github.com/rerdavies/pipedal/releases/download/v1.5.98/pipedal_1.5.98_amd64.deb) - [Ubuntu 24.04 through 25.04 (amd64) v1.5.99](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_amd64.deb)
Version 1.5.98 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), Ubuntu 24.10 (aarch64), and Ubuntu 25.04 (aarch64). Download the appropriate package for your platform, and install using the following procedure: Version 1.5.99 has been tested on Raspberry Pi OS bookworm, Ubuntu 24.04 (amd64), Ubuntu 24.10 (aarch64), and Ubuntu 25.04 (aarch64). Download the appropriate package for your platform, and install using the following procedure:
``` ```
sudo apt update sudo apt update
sudo apt upgrade sudo apt upgrade
cd ~/Downloads cd ~/Downloads
sudo apt-get install ./pipedal_1.5.98_arm64.deb sudo apt-get install ./pipedal_1.5.99_arm64.deb
``` ```
You MUST use `apt-get`. `apt` will not install downloaded packages; and `dpkg -i` will not install dependencies. You MUST use `apt-get`. `apt` will not install downloaded packages; and `dpkg -i` will not install dependencies.
+5
View File
@@ -1,5 +1,10 @@
# Release Notes # Release Notes
## PiPedal 1.5.99 Beta
### Bug Fixes
- Midi Bindings dialog shows blank screen for pedalboards with splits.
## PiPedal 1.5.98 Beta ## PiPedal 1.5.98 Beta
### Features ### Features
+4 -4
View File
@@ -4,9 +4,9 @@
Download the most recent Debian (.deb) package for your platform: Download the most recent Debian (.deb) package for your platform:
- [Raspberry Pi OS bookworm (aarch64) v1.5.98 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.98/pipedal_1.5.98_arm64.deb) - [Raspberry Pi OS bookworm (aarch64) v1.5.99 Beta](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_arm64.deb)
- [Ubuntu 24.x, 25.04 (aarch64) v1.5.98 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.98/pipedal_1.5.98_arm64.deb) - [Ubuntu 24.x, 25.04 (aarch64) v1.5.99 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_arm64.deb)
- [Ubuntu 24.x, 25.04 (amd64) v1.5.98 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.98/pipedal_1.5.98_amd64.deb) - [Ubuntu 24.x, 25.04 (amd64) v1.5.99 Alpha](https://github.com/rerdavies/pipedal/releases/download/v1.5.99/pipedal_1.5.99_amd64.deb)
Install the package by running Install the package by running
@@ -14,7 +14,7 @@ Install the package by running
``` ```
sudo apt update sudo apt update
cd ~/Downloads cd ~/Downloads
sudo apt-get install ./pipedal_1.5.98_arm64.deb sudo apt-get install ./pipedal_1.5.99_arm64.deb
``` ```
You MUST use `apt-get` to install the package. `apt install` will NOT install the package correctly. The message about missing permissions given by `apt-get` is You MUST use `apt-get` to install the package. `apt install` will NOT install the package correctly. The message about missing permissions given by `apt-get` is
expected, and can be safely ignored. expected, and can be safely ignored.
+4 -1
View File
@@ -1,6 +1,9 @@
cmake_minimum_required(VERSION 3.16.0) cmake_minimum_required(VERSION 3.16.0)
add_subdirectory("SQLiteCpp") # Prevent installation of SQLite built components. (we use the .a library files directly)
add_subdirectory("SQLiteCpp" EXCLUDE_FROM_ALL)
set(SDBUSCPP_BUILD_CODEGEN off) set(SDBUSCPP_BUILD_CODEGEN off)
set(SDBUSCPP_INSTALL off) set(SDBUSCPP_INSTALL off)
+237 -95
View File
@@ -23,6 +23,7 @@
*/ */
#include "pch.h" #include "pch.h"
#include "PiPedalCommon.hpp"
#include "util.hpp" #include "util.hpp"
#include <cmath> #include <cmath>
#include "Finally.hpp" #include "Finally.hpp"
@@ -39,6 +40,7 @@
#include "CrashGuard.hpp" #include "CrashGuard.hpp"
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
#include "ChannelRouterSettings.hpp"
#include "CpuUse.hpp" #include "CpuUse.hpp"
@@ -328,11 +330,29 @@ namespace pipedal
bool inputSwapped = false; bool inputSwapped = false;
bool outputSwapped = false; bool outputSwapped = false;
std::vector<float *> activeCaptureBuffers; std::vector<std::vector<float>> allocatedBuffers;
std::vector<float *> activePlaybackBuffers;
std::vector<float *> captureBuffers; std::set<int64_t> usedOutputChannels;
std::vector<float *> playbackBuffers; std::vector<float*> deviceCaptureBuffers;
std::vector<float*> devicePlaybackBuffers;
float *zeroInputBuffer = nullptr;
float *discardOutputBuffer = nullptr;
std::vector<float *> mainCaptureBuffers;
std::vector<float *> mainPlaybackBuffers;
std::vector<float *> auxCaptureBuffers;
std::vector<float *> auxPlaybackBuffers;
std::vector<float *> sendCaptureBuffers;
std::vector<float *> sendPlaybackBuffers;
struct OutputBufferMix {
size_t outputChannel = 0;
std::vector<float*> inputBuffers;
float *outputBuffer = nullptr;
};
std::vector<OutputBufferMix> outputBufferMixes;
std::vector<uint8_t> rawCaptureBuffer; std::vector<uint8_t> rawCaptureBuffer;
std::vector<uint8_t> rawPlaybackBuffer; std::vector<uint8_t> rawPlaybackBuffer;
@@ -742,7 +762,7 @@ namespace pipedal
{ {
int32_t *p = getCaptureBuffer<int32_t>(rawCaptureBuffer); int32_t *p = getCaptureBuffer<int32_t>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers; std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels; int channels = this->captureChannels;
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
{ {
@@ -760,7 +780,7 @@ namespace pipedal
{ {
float *p = getCaptureBuffer<float>(rawCaptureBuffer); float *p = getCaptureBuffer<float>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers; std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels; int channels = this->captureChannels;
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
{ {
@@ -776,7 +796,7 @@ namespace pipedal
{ {
int16_t *p = getCaptureBuffer<int16_t>(rawCaptureBuffer); int16_t *p = getCaptureBuffer<int16_t>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers; std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels; int channels = this->captureChannels;
constexpr double scale = 1.0f / (std::numeric_limits<int16_t>::max() + 1L); constexpr double scale = 1.0f / (std::numeric_limits<int16_t>::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -792,7 +812,7 @@ namespace pipedal
{ {
int16_t *p = getCaptureBuffer<int16_t>(rawCaptureBuffer); int16_t *p = getCaptureBuffer<int16_t>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers; std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels; int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits<int16_t>::max() + 1L); constexpr float scale = 1.0f / (std::numeric_limits<int16_t>::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -809,7 +829,7 @@ namespace pipedal
{ {
int32_t *p = getCaptureBuffer<int32_t>(rawCaptureBuffer); int32_t *p = getCaptureBuffer<int32_t>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers; std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels; int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1L); constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -825,7 +845,7 @@ namespace pipedal
{ {
uint8_t *p = getCaptureBuffer<uint8_t>(rawCaptureBuffer); uint8_t *p = getCaptureBuffer<uint8_t>(rawCaptureBuffer);
std::vector<float *> &buffers = this->captureBuffers; std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels; int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1LL); constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1LL);
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -842,7 +862,7 @@ namespace pipedal
{ {
uint8_t *p = (uint8_t *)rawCaptureBuffer.data(); uint8_t *p = (uint8_t *)rawCaptureBuffer.data();
std::vector<float *> &buffers = this->captureBuffers; std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels; int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1LL); constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1LL);
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -859,7 +879,7 @@ namespace pipedal
{ {
int32_t *p = (int32_t *)rawCaptureBuffer.data(); int32_t *p = (int32_t *)rawCaptureBuffer.data();
std::vector<float *> &buffers = this->captureBuffers; std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels; int channels = this->captureChannels;
constexpr float scale = 1.0f / (0x00FFFFFFL + 1L); constexpr float scale = 1.0f / (0x00FFFFFFL + 1L);
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -875,7 +895,7 @@ namespace pipedal
{ {
int32_t *p = (int32_t *)rawCaptureBuffer.data(); int32_t *p = (int32_t *)rawCaptureBuffer.data();
std::vector<float *> &buffers = this->captureBuffers; std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels; int channels = this->captureChannels;
constexpr float scale = 1.0f / (0x00FFFFFFL + 1L); constexpr float scale = 1.0f / (0x00FFFFFFL + 1L);
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -891,7 +911,7 @@ namespace pipedal
{ {
int32_t *p = (int32_t *)rawCaptureBuffer.data(); int32_t *p = (int32_t *)rawCaptureBuffer.data();
std::vector<float *> &buffers = this->captureBuffers; std::vector<float *> &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels; int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1L); constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -907,7 +927,7 @@ namespace pipedal
{ {
int16_t *p = (int16_t *)rawPlaybackBuffer.data(); int16_t *p = (int16_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers; std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels; int channels = this->playbackChannels;
constexpr float scale = std::numeric_limits<int16_t>::max(); constexpr float scale = std::numeric_limits<int16_t>::max();
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -927,7 +947,7 @@ namespace pipedal
{ {
int16_t *p = (int16_t *)rawPlaybackBuffer.data(); int16_t *p = (int16_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers; std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels; int channels = this->playbackChannels;
constexpr float scale = std::numeric_limits<int16_t>::max(); constexpr float scale = std::numeric_limits<int16_t>::max();
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -947,7 +967,7 @@ namespace pipedal
{ {
int32_t *p = (int32_t *)rawPlaybackBuffer.data(); int32_t *p = (int32_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers; std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels; int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits<int32_t>::max(); constexpr double scale = std::numeric_limits<int32_t>::max();
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -969,7 +989,7 @@ namespace pipedal
int32_t *p = (int32_t *)rawPlaybackBuffer.data(); int32_t *p = (int32_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers; std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels; int channels = this->playbackChannels;
constexpr double scale = 0x00FFFFFF; constexpr double scale = 0x00FFFFFF;
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -991,7 +1011,7 @@ namespace pipedal
int32_t *p = (int32_t *)rawPlaybackBuffer.data(); int32_t *p = (int32_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers; std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels; int channels = this->playbackChannels;
constexpr double scale = 0x00FFFFFF; constexpr double scale = 0x00FFFFFF;
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -1011,7 +1031,7 @@ namespace pipedal
{ {
int32_t *p = (int32_t *)rawPlaybackBuffer.data(); int32_t *p = (int32_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers; std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels; int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits<int32_t>::max(); constexpr double scale = std::numeric_limits<int32_t>::max();
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -1031,7 +1051,7 @@ namespace pipedal
{ {
uint8_t *p = (uint8_t *)rawPlaybackBuffer.data(); uint8_t *p = (uint8_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers; std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels; int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits<int32_t>::max(); constexpr double scale = std::numeric_limits<int32_t>::max();
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -1056,7 +1076,7 @@ namespace pipedal
{ {
uint8_t *p = (uint8_t *)rawPlaybackBuffer.data(); uint8_t *p = (uint8_t *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers; std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels; int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits<int32_t>::max(); constexpr double scale = std::numeric_limits<int32_t>::max();
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
@@ -1082,7 +1102,7 @@ namespace pipedal
{ {
float *p = (float *)rawPlaybackBuffer.data(); float *p = (float *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers; std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels; int channels = this->playbackChannels;
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
{ {
@@ -1097,7 +1117,7 @@ namespace pipedal
{ {
float *p = (float *)rawPlaybackBuffer.data(); float *p = (float *)rawPlaybackBuffer.data();
std::vector<float *> &buffers = this->playbackBuffers; std::vector<float *> &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels; int channels = this->playbackChannels;
for (size_t frame = 0; frame < frames; ++frame) for (size_t frame = 0; frame < frames; ++frame)
{ {
@@ -1127,9 +1147,9 @@ namespace pipedal
} }
} }
JackChannelSelection channelSelection; ChannelSelection channelSelection;
bool open = false; bool open = false;
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) virtual void Open(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection)
{ {
terminateAudio_ = false; terminateAudio_ = false;
if (open) if (open)
@@ -1249,7 +1269,7 @@ namespace pipedal
rawCaptureBuffer.resize(captureFrameSize * bufferSize * 2); rawCaptureBuffer.resize(captureFrameSize * bufferSize * 2);
memset(rawCaptureBuffer.data(), 0, rawCaptureBuffer.size()); memset(rawCaptureBuffer.data(), 0, rawCaptureBuffer.size());
AllocateBuffers(captureBuffers, captureChannels); AllocateBuffers(deviceCaptureBuffers, captureChannels);
} }
virtual std::string GetConfigurationDescription() virtual std::string GetConfigurationDescription()
@@ -1260,8 +1280,15 @@ namespace pipedal
<< ", " << GetAlsaFormatDescription(this->captureFormat) << ", " << GetAlsaFormatDescription(this->captureFormat)
<< ", " << this->sampleRate << ", " << this->sampleRate
<< ", " << this->bufferSize << "x" << this->numberOfBuffers << ", " << this->bufferSize << "x" << this->numberOfBuffers
<< ", in: " << this->InputBufferCount() << "/" << this->captureChannels << ", " << "device in: " << this->DeviceInputBufferCount()
<< ", out: " << this->OutputBufferCount() << "/" << this->playbackChannels); << ", " << "device out: " << this->DeviceOutputBufferCount()
<< ", main in: " << this->MainInputBufferCount()
<< ", main out: " << this->MainOutputBufferCount()
<< ", aux in: " << this->AuxInputBufferCount()
<< ", aux out: " << this->AuxOutputBufferCount()
<< ", send in: " << this->SendInputBufferCount()
<< ", send out: " << this->SendOutputBufferCount()
);
return result; return result;
} }
void PreparePlaybackFunctions(snd_pcm_format_t playbackFormat) void PreparePlaybackFunctions(snd_pcm_format_t playbackFormat)
@@ -1321,10 +1348,10 @@ namespace pipedal
rawPlaybackBuffer.resize(playbackFrameSize * bufferSize); rawPlaybackBuffer.resize(playbackFrameSize * bufferSize);
memset(rawPlaybackBuffer.data(), 0, playbackFrameSize * bufferSize); memset(rawPlaybackBuffer.data(), 0, playbackFrameSize * bufferSize);
AllocateBuffers(playbackBuffers, playbackChannels); AllocateBuffers(devicePlaybackBuffers, playbackChannels);
} }
void OpenAudio(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) void OpenAudio(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection)
{ {
std::lock_guard lock{restartMutex}; std::lock_guard lock{restartMutex};
@@ -1717,7 +1744,7 @@ namespace pipedal
} }
return 0; return 0;
} }
void AudioThread() PIPEDAL_NON_INLINE void AudioThread()
{ {
SetThreadName("alsaDriver"); SetThreadName("alsaDriver");
@@ -1797,6 +1824,19 @@ namespace pipedal
cpuUse.AddSample(ProfileCategory::Execute); cpuUse.AddSample(ProfileCategory::Execute);
// Perform any neccessary mixing of outputs.
for (auto &bufferMix : outputBufferMixes)
{
float *outputBuffer = bufferMix.outputBuffer;
for (auto &mixBuffer : bufferMix.inputBuffers)
{
for (size_t i = 0; i < framesRead; ++i)
{
outputBuffer[i] += mixBuffer[i];
}
}
}
(this->*copyOutputFn)(framesRead); (this->*copyOutputFn)(framesRead);
cpuUse.AddSample(ProfileCategory::Driver); cpuUse.AddSample(ProfileCategory::Driver);
// process. // process.
@@ -1824,9 +1864,9 @@ namespace pipedal
{ {
this->driverHost->OnAlsaDriverStopped(); this->driverHost->OnAlsaDriverStopped();
// zero out input buffers. // zero out input buffers.
for (size_t i = 0; i < this->captureBuffers.size(); ++i) for (size_t i = 0; i < this->deviceCaptureBuffers.size(); ++i)
{ {
float *pBuffer = captureBuffers[i]; float *pBuffer = deviceCaptureBuffers[i];
for (size_t j = 0; j < this->bufferSize; ++j) for (size_t j = 0; j < this->bufferSize; ++j)
{ {
pBuffer[j] = 0; pBuffer[j] = 0;
@@ -1836,7 +1876,7 @@ namespace pipedal
{ {
while (!terminateAudio()) while (!terminateAudio())
{ {
std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(1));
this->driverHost->OnProcess(this->bufferSize); this->driverHost->OnProcess(this->bufferSize);
} }
} }
@@ -1849,63 +1889,129 @@ namespace pipedal
bool alsaActive = false; bool alsaActive = false;
static int IndexFromPortName(const std::string &s)
{
auto pos = s.find_last_of('_');
if (pos == std::string::npos)
{
throw std::invalid_argument("Bad port name.");
}
const char *p = s.c_str() + (pos + 1);
int v = atoi(p); PIPEDAL_NON_INLINE void AllocateInputChannels(
if (v < 0) const std::vector<int64_t> &channelSelection,
std::vector<float*> &channelBuffers
) {
size_t nChannels = channelSelection.size();
channelBuffers.resize(nChannels);
for (size_t i = 0; i < nChannels; ++i)
{ {
throw std::invalid_argument("Bad port name."); int64_t deviceChannel = channelSelection[i];
if (deviceChannel == -1 || deviceChannel >= captureChannels)
{
channelBuffers[i] = zeroInputBuffer;
}
else
{
channelBuffers[i] = deviceCaptureBuffers[deviceChannel];
}
}
}
PIPEDAL_NON_INLINE void AddMixBuffer(int64_t outputChannel, float *mixBuffer)
{
for (auto&bufferMix: outputBufferMixes)
{
if (bufferMix.outputChannel == outputChannel)
{
bufferMix.inputBuffers.push_back(mixBuffer);
return;
}
}
OutputBufferMix newMix;
newMix.outputChannel = outputChannel;
newMix.outputBuffer = devicePlaybackBuffers[outputChannel];
newMix.inputBuffers.push_back(mixBuffer);
outputBufferMixes.push_back(std::move(newMix));
}
PIPEDAL_NON_INLINE void AllocateOutputChannels(
const std::vector<int64_t> &channelSelection,
std::vector<float*> &channelBuffers
) {
size_t nChannels = channelSelection.size();
channelBuffers.resize(nChannels);
for (size_t i = 0; i < nChannels; ++i)
{
int64_t deviceChannel = channelSelection[i];
if (deviceChannel == -1 || deviceChannel >= playbackChannels)
{
if (discardOutputBuffer == nullptr)
{
discardOutputBuffer = AllocateAudioBuffer();
}
channelBuffers[i] = discardOutputBuffer;
}
else
{
if (!usedOutputChannels.contains(deviceChannel))
{
usedOutputChannels.insert(deviceChannel);
channelBuffers[i] = deviceCaptureBuffers[deviceChannel];
} else {
// Used once already. We need to mix down.
float *mixBuffer = AllocateAudioBuffer();
channelBuffers[i] = mixBuffer;
AddMixBuffer(deviceChannel, mixBuffer);
}
}
} }
return v;
} }
bool activated = false; bool activated = false;
virtual void Activate() PIPEDAL_NON_INLINE virtual void Activate()
{ {
if (activated) if (activated)
{ {
throw PiPedalStateException("Already activated."); throw PiPedalStateException("Already activated.");
} }
activated = true; activated = true;
this->activeCaptureBuffers.resize(channelSelection.GetInputAudioPorts().size()); // Reset previously allocated buffers.
usedOutputChannels.clear();
allocatedBuffers.resize(0);
int ix = 0;
for (auto &x : channelSelection.GetInputAudioPorts()) // Allocate device capture buffers.
zeroInputBuffer = AllocateAudioBuffer();
deviceCaptureBuffers.resize(captureChannels);
for (size_t i = 0; i < captureChannels; ++i)
{ {
int sourceIndex = IndexFromPortName(x); deviceCaptureBuffers[i] = AllocateAudioBuffer();
if (sourceIndex >= captureBuffers.size()) }
{ devicePlaybackBuffers.resize(playbackChannels);
Lv2Log::error(SS("Invalid audio input port: " << x)); for (size_t i = 0; i < playbackChannels; ++i)
} {
else devicePlaybackBuffers[i] = AllocateAudioBuffer();
{
this->activeCaptureBuffers[ix++] = this->captureBuffers[sourceIndex];
}
} }
this->activePlaybackBuffers.resize(channelSelection.GetOutputAudioPorts().size()); AllocateInputChannels(
channelSelection.mainInputChannels(),
ix = 0; this->mainCaptureBuffers);
for (auto &x : channelSelection.GetOutputAudioPorts()) AllocateOutputChannels(
{ channelSelection.mainOutputChannels(),
int sourceIndex = IndexFromPortName(x); this->mainPlaybackBuffers
if (sourceIndex >= playbackBuffers.size()) );
{ AllocateInputChannels(
Lv2Log::error(SS("Invalid audio output port: " << x)); channelSelection.auxInputChannels(),
} this->auxCaptureBuffers);
else AllocateOutputChannels(
{ channelSelection.auxOutputChannels(),
this->activePlaybackBuffers[ix++] = this->playbackBuffers[sourceIndex]; this->auxPlaybackBuffers
} );
} AllocateInputChannels(
channelSelection.sendInputChannels(),
this->sendCaptureBuffers);
AllocateOutputChannels(
channelSelection.sendOutputChannels(),
this->sendPlaybackBuffers
);
audioThread = std::make_unique<std::jthread>([this]() audioThread = std::make_unique<std::jthread>([this]()
{ AudioThread(); }); { AudioThread(); });
@@ -1941,10 +2047,43 @@ namespace pipedal
this->alsaSequencer = alsaSequencer; this->alsaSequencer = alsaSequencer;
} }
virtual size_t InputBufferCount() const { return activeCaptureBuffers.size(); } virtual size_t DeviceInputBufferCount() const override {
virtual float *GetInputBuffer(size_t channel) override return deviceCaptureBuffers.size();
}
virtual size_t DeviceOutputBufferCount() const override {
return devicePlaybackBuffers.size();
}
virtual size_t MainInputBufferCount() const { return mainCaptureBuffers.size(); }
virtual float *GetMainInputBuffer(size_t channel) override
{ {
return activeCaptureBuffers[channel]; if (channel >= (int64_t)mainCaptureBuffers.size())
{
throw std::runtime_error("Argument out of range.");
}
return mainCaptureBuffers[channel];
}
virtual size_t AuxInputBufferCount() const { return auxCaptureBuffers.size(); }
virtual float *GetAuxInputBuffer(size_t channel) override
{
return auxCaptureBuffers[channel];
}
virtual size_t AuxOutputBufferCount() const {
return auxPlaybackBuffers.size(); }
virtual float *GetAuxOutputBuffer(size_t channel) override
{
return auxPlaybackBuffers[channel];
}
virtual size_t SendInputBufferCount() const { return sendCaptureBuffers.size(); }
virtual float *GetSendInputBuffer(size_t channel) override
{
return sendCaptureBuffers[channel];
}
virtual size_t SendOutputBufferCount() const { return sendPlaybackBuffers.size(); }
virtual float *GetSendOutputBuffer(size_t channel) override
{
return sendPlaybackBuffers[channel];
} }
virtual size_t GetMidiInputEventCount() override virtual size_t GetMidiInputEventCount() override
@@ -1956,27 +2095,30 @@ namespace pipedal
return this->midiEvents.data(); return this->midiEvents.data();
} }
virtual size_t OutputBufferCount() const { return activePlaybackBuffers.size(); } virtual size_t MainOutputBufferCount() const { return mainPlaybackBuffers.size(); }
virtual float *GetOutputBuffer(size_t channel) override virtual float *GetMainOutputBuffer(size_t channel) override
{ {
return activePlaybackBuffers[channel]; return mainPlaybackBuffers[channel];
} }
void FreeBuffers(std::vector<float *> &buffer) float* AllocateAudioBuffer() {
{ std::vector<float> buffer;
for (size_t i = 0; i < buffer.size(); ++i) buffer.resize(this->bufferSize);
{ float*pBuffer = buffer.data();
delete[] buffer[i]; allocatedBuffers.push_back(std::move(buffer));
buffer[i] = 0; return pBuffer;
}
buffer.clear();
} }
void DeleteBuffers() void DeleteBuffers()
{ {
activeCaptureBuffers.clear(); mainCaptureBuffers.clear();
activePlaybackBuffers.clear(); mainPlaybackBuffers.clear();
FreeBuffers(this->playbackBuffers); auxCaptureBuffers.clear();
FreeBuffers(this->captureBuffers); auxPlaybackBuffers.clear();
sendCaptureBuffers.clear();
sendPlaybackBuffers.clear();
zeroInputBuffer = nullptr;
discardOutputBuffer = nullptr;
allocatedBuffers.clear();
} }
virtual void Close() virtual void Close()
{ {
@@ -2241,7 +2383,7 @@ namespace pipedal
{ {
value += 1.0f * (c) / (128.0 * 256.0 * 256.0); value += 1.0f * (c) / (128.0 * 256.0 * 256.0);
} }
this->playbackBuffers[c][i] = value; this->devicePlaybackBuffers[c][i] = value;
} }
} }
@@ -2257,7 +2399,7 @@ namespace pipedal
for (size_t c = 0; c < captureChannels; ++c) for (size_t c = 0; c < captureChannels; ++c)
{ {
float error = float error =
this->captureBuffers[c][i] - this->playbackBuffers[c][i]; this->deviceCaptureBuffers[c][i] - this->devicePlaybackBuffers[c][i];
assert(std::abs(error) < 4e-5); assert(std::abs(error) < 4e-5);
} }
+2 -1
View File
@@ -1,7 +1,7 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2025 Robin E. R. Davies * Copyright (c) 2026 Robin E. R. Davies
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of * Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in * this software and associated documentation files (the "Software"), to deal in
@@ -24,6 +24,7 @@
#pragma once #pragma once
#include <lilv/lilv.h>
#include "AudioDriver.hpp" #include "AudioDriver.hpp"
#include "JackServerSettings.hpp" #include "JackServerSettings.hpp"
+113 -98
View File
@@ -1,18 +1,18 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2022 Robin E. R. Davies * Copyright (c) 2022 Robin E. R. Davies
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of * Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in * this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to * the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do * of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions: * so, subject to the following conditions:
* *
* The above copyright notice and this permission notice shall be included in all * The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software. * copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -27,28 +27,34 @@
#include <cmath> #include <cmath>
#include <mutex> #include <mutex>
#include <iostream> #include <iostream>
#include <algorithm>
#include "AlsaDriver.hpp" #include "AlsaDriver.hpp"
#include "JackDriver.hpp" #include "ChannelRouterSettings.hpp"
using namespace pipedal; using namespace pipedal;
using namespace std; using namespace std;
class AlsaTester : private AudioDriverHost
class AlsaTester: private AudioDriverHost { {
public: public:
enum class TestType { Oscillator, LatencyMonitor, NullTest}; enum class TestType
{
Oscillator,
LatencyMonitor,
NullTest
};
private: private:
AudioDriver *audioDriver = nullptr; AudioDriver *audioDriver = nullptr;
TestType testType; TestType testType;
public: public:
AlsaTester(TestType testType) AlsaTester(TestType testType)
: testType(testType) : testType(testType)
{ {
// audioDriver = CreateAlsaDriver(this); // audioDriver = CreateAlsaDriver(this);
// audioDriver = CreateJackDriver(this); // audioDriver = CreateJackDriver(this);
} }
~AlsaTester() ~AlsaTester()
{ {
@@ -57,47 +63,47 @@ public:
delete[] outputBuffers; delete[] outputBuffers;
} }
bool useJack = false; void initializeChannelSelection(JackConfiguration &jackConfiguration, ChannelSelection &channelSelection)
{
size_t nInputs = std::max(jackConfiguration.inputAudioPorts().size(),size_t(2));
size_t nOutputs = std::max(jackConfiguration.outputAudioPorts().size(),size_t(2));
auto &inputSelections = channelSelection.mainInputChannels();
auto &outputSelections = channelSelection.mainOutputChannels();
inputSelections.resize(nInputs);
for (size_t i = 0; i < nInputs; ++i)
{
inputSelections[i] = i;
}
outputSelections.resize(nOutputs);
for (size_t i = 0; i < nOutputs; ++i)
{
outputSelections[i] = i;
}
}
void Test() void Test()
{ {
AlsaFormatEncodeDecodeTest(this); AlsaFormatEncodeDecodeTest(this);
JackServerSettings serverSettings("hw:M2","hw:M2",48000,32,3); JackServerSettings serverSettings("hw:M2", "hw:M2", 48000, 32, 3);
JackConfiguration jackConfiguration; JackConfiguration jackConfiguration;
if (useJack) jackConfiguration.AlsaInitialize(serverSettings);
{
jackConfiguration.JackInitialize();
} else {
jackConfiguration.AlsaInitialize(serverSettings);
}
JackChannelSelection channelSelection( ChannelSelection channelSelection;
jackConfiguration.inputAudioPorts(),
jackConfiguration.outputAudioPorts(),
jackConfiguration.inputMidiDevices());
#if JACK_HOST
if (useJack)
{
audioDriver = CreateJackDriver(this);
} else {
audioDriver = CreateAlsaDriver(this);
}
#else
audioDriver = CreateAlsaDriver(this); audioDriver = CreateAlsaDriver(this);
#endif
initializeChannelSelection(jackConfiguration, channelSelection);
oscillator.Init(440,jackConfiguration.sampleRate()); oscillator.Init(440, jackConfiguration.sampleRate());
latencyMonitor.Init(jackConfiguration.sampleRate()); latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings,channelSelection); audioDriver->Open(serverSettings, channelSelection);
inputBuffers = new float*[channelSelection.GetInputAudioPorts().size()]; inputBuffers = new float *[channelSelection.mainInputChannels().size()];
outputBuffers = new float*[channelSelection.GetOutputAudioPorts().size()]; outputBuffers = new float *[channelSelection.mainOutputChannels().size()];
audioDriver->Activate(); audioDriver->Activate();
@@ -107,7 +113,7 @@ public:
if (testType == TestType::LatencyMonitor) if (testType == TestType::LatencyMonitor)
{ {
auto latency = this->latencyMonitor.GetLatency(); auto latency = this->latencyMonitor.GetLatency();
double ms = 1000.0*latency/jackConfiguration.sampleRate(); double ms = 1000.0 * latency / jackConfiguration.sampleRate();
cout << "Latency: " << latency << " samples " << ms << "ms" << " xruns: " << GetXruns() << " Cpu: " << audioDriver->CpuUse() << "%" << endl; cout << "Latency: " << latency << " samples " << ms << "ms" << " xruns: " << GetXruns() << " Cpu: " << audioDriver->CpuUse() << "%" << endl;
} }
@@ -115,37 +121,40 @@ public:
audioDriver->Deactivate(); audioDriver->Deactivate();
audioDriver->Close(); audioDriver->Close();
} }
float**inputBuffers = nullptr; float **inputBuffers = nullptr;
float**outputBuffers = nullptr; float **outputBuffers = nullptr;
class Oscillator { class Oscillator
{
private: private:
double dx = 0; double dx = 0;
double x = 0; double x = 0;
double dx2 = 0; double dx2 = 0;
double x2 = 0; double x2 = 0;
public:
public:
void Init(float frequency, size_t sampleRate) void Init(float frequency, size_t sampleRate)
{ {
dx = frequency*3.141592736*2/sampleRate; dx = frequency * 3.141592736 * 2 / sampleRate;
dx2 = 0.5*3.141592736*2/sampleRate; dx2 = 0.5 * 3.141592736 * 2 / sampleRate;
} }
float Next() { float Next()
{
float result = (float)std::cos(x); float result = (float)std::cos(x);
float env = (float)std::cos(x2); float env = (float)std::cos(x2);
x += dx; x += dx;
x2 += dx2; x2 += dx2;
return result*env; return result * env;
} }
}; };
class LatencyMonitor { class LatencyMonitor
enum class State { {
enum class State
{
Idle, Idle,
Waiting, Waiting,
}; };
@@ -156,91 +165,99 @@ public:
size_t current_latency = 0; size_t current_latency = 0;
size_t latency = 0; size_t latency = 0;
std::mutex sync; std::mutex sync;
public: public:
void Init(uint64_t sampleRate) void Init(uint64_t sampleRate)
{ {
idle_samples = (uint64_t)sampleRate*2; idle_samples = (uint64_t)sampleRate * 2;
waiting_samples = (uint64_t)sampleRate*2; waiting_samples = (uint64_t)sampleRate * 2;
state = State::Idle; state = State::Idle;
t = idle_samples; t = idle_samples;
latency = 0; latency = 0;
} }
size_t GetLatency() { size_t GetLatency()
std::lock_guard lock { sync}; {
std::lock_guard lock{sync};
return latency; return latency;
} }
float Next(float input) float Next(float input)
{ {
switch(state) switch (state)
{ {
default: default:
case State::Idle: case State::Idle:
{
if (t-- == 0)
{ {
if (t-- == 0) { state = State::Waiting;
state = State::Waiting; current_latency = 0;
current_latency = 0;
}
return 0.01;
} }
break; return 0.01;
case State::Waiting: }
break;
case State::Waiting:
{
if (std::abs(input) > 0.1 || current_latency > 500)
{ {
if (std::abs(input) > 0.1 || current_latency > 500) { {
{ std::lock_guard lock{sync};
std::lock_guard lock { sync}; latency = current_latency;
latency = current_latency;
}
state = State::Idle;
t = idle_samples;
} else {
++current_latency;
} }
return current_latency < 100 ? 0.25 : 0.0; state = State::Idle;
t = idle_samples;
} }
break; else
{
++current_latency;
}
return current_latency < 100 ? 0.25 : 0.0;
}
break;
} }
} }
}; };
Oscillator oscillator; Oscillator oscillator;
LatencyMonitor latencyMonitor; LatencyMonitor latencyMonitor;
virtual void OnAudioTerminated() { virtual void OnAudioTerminated()
{
} }
virtual void OnAlsaDriverStopped() { virtual void OnAlsaDriverStopped()
{
} }
virtual void OnProcess(size_t nFrames) { virtual void OnProcess(size_t nFrames)
if (testType == TestType::NullTest) return; {
if (testType == TestType::NullTest)
return;
size_t inputs = audioDriver->MainInputBufferCount();
size_t inputs = audioDriver->InputBufferCount(); size_t outputs = audioDriver->MainOutputBufferCount();
size_t outputs = audioDriver->OutputBufferCount();
for (size_t i = 0; i < inputs; ++i) for (size_t i = 0; i < inputs; ++i)
{ {
inputBuffers[i] = audioDriver->GetInputBuffer(i); inputBuffers[i] = audioDriver->GetMainInputBuffer(i);
} }
for (size_t i = 0; i < outputs; ++i) for (size_t i = 0; i < outputs; ++i)
{ {
outputBuffers[i] = audioDriver->GetOutputBuffer(i); outputBuffers[i] = audioDriver->GetMainOutputBuffer(i);
} }
if (this->testType == TestType::Oscillator) if (this->testType == TestType::Oscillator)
{ {
for (size_t i = 0; i < nFrames; ++i) for (size_t i = 0; i < nFrames; ++i)
{ {
float v = oscillator.Next()*0.25f; float v = oscillator.Next() * 0.25f;
for (size_t c = 0; c < outputs; ++c) for (size_t c = 0; c < outputs; ++c)
{ {
outputBuffers[c][i] = v; outputBuffers[c][i] = v;
} }
} }
} }
else { else
{
for (size_t i = 0; i < nFrames; ++i) for (size_t i = 0; i < nFrames; ++i)
{ {
float v = latencyMonitor.Next(inputBuffers[0][i]); float v = latencyMonitor.Next(inputBuffers[0][i]);
@@ -249,36 +266,34 @@ public:
outputBuffers[c][i] = v; outputBuffers[c][i] = v;
} }
} }
} }
} }
std::mutex sync; std::mutex sync;
uint64_t xruns; uint64_t xruns;
uint64_t GetXruns() { uint64_t GetXruns()
lock_guard lock { sync}; {
lock_guard lock{sync};
return xruns; return xruns;
} }
virtual void OnUnderrun() { virtual void OnUnderrun()
lock_guard lock { sync }; {
lock_guard lock{sync};
++xruns; ++xruns;
} }
}; };
TEST_CASE("alsa_test", "[alsa_test]")
{
TEST_CASE( "alsa_test", "[alsa_test]" ) {
AlsaTester alsaDriver(AlsaTester::TestType::Oscillator); AlsaTester alsaDriver(AlsaTester::TestType::Oscillator);
alsaDriver.Test(); alsaDriver.Test();
} }
TEST_CASE("alsa_midi_test", "[alsa_midi_test]")
TEST_CASE( "alsa_midi_test", "[alsa_midi_test]" ) { {
AlsaTester alsaDriver(AlsaTester::TestType::Oscillator); AlsaTester alsaDriver(AlsaTester::TestType::Oscillator);
MidiDecoderTest(); MidiDecoderTest();
} }
+19 -5
View File
@@ -35,6 +35,8 @@ namespace pipedal {
using ProcessCallback = std::function<void (size_t)>; using ProcessCallback = std::function<void (size_t)>;
class ChannelSelection;
class AudioDriverHost { class AudioDriverHost {
public: public:
@@ -58,14 +60,26 @@ namespace pipedal {
virtual size_t GetMidiInputEventCount() = 0; virtual size_t GetMidiInputEventCount() = 0;
virtual MidiEvent*GetMidiEvents() = 0; virtual MidiEvent*GetMidiEvents() = 0;
virtual size_t InputBufferCount() const = 0; virtual size_t DeviceInputBufferCount() const = 0;
virtual float*GetInputBuffer(size_t channel) = 0; virtual size_t DeviceOutputBufferCount() const = 0;
virtual size_t MainInputBufferCount() const = 0;
virtual float*GetMainInputBuffer(size_t channel) = 0;
virtual size_t MainOutputBufferCount() const = 0;
virtual float*GetMainOutputBuffer(size_t channe) = 0;
virtual size_t OutputBufferCount() const = 0; virtual size_t AuxInputBufferCount() const = 0;
virtual float*GetOutputBuffer(size_t channe) = 0; virtual float*GetAuxInputBuffer(size_t channel) = 0;
virtual size_t AuxOutputBufferCount() const = 0;
virtual float*GetAuxOutputBuffer(size_t channel) = 0;
virtual void Open(const JackServerSettings & jackServerSettings,const JackChannelSelection &channelSelection) = 0; virtual size_t SendInputBufferCount() const = 0;
virtual float*GetSendInputBuffer(size_t channel) = 0;
virtual size_t SendOutputBufferCount() const = 0;
virtual float*GetSendOutputBuffer(size_t channel) = 0;
virtual void Open(const JackServerSettings & jackServerSettings,const ChannelSelection &channelSelection) = 0;
virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) = 0; virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) = 0;
virtual void Activate() = 0; virtual void Activate() = 0;
virtual void Deactivate() = 0; virtual void Deactivate() = 0;
+91 -25
View File
@@ -18,6 +18,7 @@
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "PiPedalCommon.hpp"
#include "AudioHost.hpp" #include "AudioHost.hpp"
#include "util.hpp" #include "util.hpp"
#include <lv2/atom/atom.h> #include <lv2/atom/atom.h>
@@ -519,12 +520,17 @@ private:
SystemMidiBinding rebootMidiBinding; SystemMidiBinding rebootMidiBinding;
SystemMidiBinding shutdownMidiBinding; SystemMidiBinding shutdownMidiBinding;
JackChannelSelection channelSelection; ChannelSelection channelSelection;
std::atomic<bool> active = false; std::atomic<bool> active = false;
std::atomic<bool> audioStopped = false; std::atomic<bool> audioStopped = false;
std::atomic<bool> isDummyAudioDriver = false; std::atomic<bool> isDummyAudioDriver = false;
std::shared_ptr<Lv2Pedalboard> currentPedalboard; std::shared_ptr<Lv2Pedalboard> currentPedalboard;
std::shared_ptr<Lv2Pedalboard> currentMainInsertPedalboard;
std::shared_ptr<Lv2Pedalboard> currentAuxInsertPedalboard;
std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue. std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue.
Lv2Pedalboard *realtimeActivePedalboard = nullptr; Lv2Pedalboard *realtimeActivePedalboard = nullptr;
@@ -620,9 +626,9 @@ private:
void ZeroOutputBuffers(size_t nframes) void ZeroOutputBuffers(size_t nframes)
{ {
for (size_t i = 0; i < audioDriver->OutputBufferCount(); ++i) for (size_t i = 0; i < audioDriver->MainOutputBufferCount(); ++i)
{ {
float * out = (float *)audioDriver->GetOutputBuffer(i); float * out = (float *)audioDriver->GetMainOutputBuffer(i);
if (out) if (out)
{ {
ZeroBuffer(out, nframes); ZeroBuffer(out, nframes);
@@ -1173,9 +1179,9 @@ private:
float *inputBuffers[4]; float *inputBuffers[4];
float *outputBuffers[4]; float *outputBuffers[4];
bool buffersValid = true; bool buffersValid = true;
for (int i = 0; i < audioDriver->InputBufferCount(); ++i) for (int i = 0; i < audioDriver->MainInputBufferCount(); ++i)
{ {
float *input = (float *)audioDriver->GetInputBuffer(i); float *input = (float *)audioDriver->GetMainInputBuffer(i);
if (input == nullptr) if (input == nullptr)
{ {
buffersValid = false; buffersValid = false;
@@ -1183,11 +1189,11 @@ private:
} }
inputBuffers[i] = input; inputBuffers[i] = input;
} }
inputBuffers[audioDriver->InputBufferCount()] = nullptr; inputBuffers[audioDriver->MainInputBufferCount()] = nullptr;
for (int i = 0; i < audioDriver->OutputBufferCount(); ++i) for (int i = 0; i < audioDriver->MainOutputBufferCount(); ++i)
{ {
float *output = audioDriver->GetOutputBuffer(i); float *output = audioDriver->GetMainOutputBuffer(i);
if (output == nullptr) if (output == nullptr)
{ {
buffersValid = false; buffersValid = false;
@@ -1195,7 +1201,7 @@ private:
} }
outputBuffers[i] = output; outputBuffers[i] = output;
} }
outputBuffers[audioDriver->OutputBufferCount()] = nullptr; outputBuffers[audioDriver->MainOutputBufferCount()] = nullptr;
if (buffersValid) if (buffersValid)
{ {
@@ -1670,7 +1676,7 @@ public:
return result; return result;
} }
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) virtual void Open(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection_)
{ {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
@@ -1692,11 +1698,6 @@ public:
this->audioDriver = std::unique_ptr<AudioDriver>(CreateAlsaDriver(this)); this->audioDriver = std::unique_ptr<AudioDriver>(CreateAlsaDriver(this));
} }
if (channelSelection.GetInputAudioPorts().size() == 0 || channelSelection.GetOutputAudioPorts().size() == 0)
{
return;
}
this->currentSample = 0; this->currentSample = 0;
this->underruns = 0; this->underruns = 0;
@@ -1707,7 +1708,7 @@ public:
this->realtimeReader.Reset(); this->realtimeReader.Reset();
this->realtimeWriter.Reset(); this->realtimeWriter.Reset();
this->channelSelection = channelSelection; this->channelSelection = channelSelection_;
StartReaderThread(); StartReaderThread();
@@ -1795,6 +1796,7 @@ public:
} }
} }
virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> &values) virtual void SetPluginPreset(uint64_t instanceId, const std::vector<ControlValue> &values)
{ {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
@@ -1896,6 +1898,49 @@ public:
pendingSnapshots.clear(); pendingSnapshots.clear();
} }
PIPEDAL_NON_INLINE RealtimePedalboardItemIndex GetRealtimeItemIndex(int64_t instanceId)
{
Pedalboard::PedalboardType instanceType = Pedalboard::GetInstanceTypeFromInstanceId(instanceId);
int64_t index = -1;
switch (instanceType)
{
case Pedalboard::PedalboardType::MainPedalboard:
if (this->currentPedalboard) {
if (instanceId == Pedalboard::START_CONTROL_ID) {
index = Pedalboard::START_CONTROL_ID;
} else if (instanceId == Pedalboard::END_CONTROL_ID) {
index = Pedalboard::END_CONTROL_ID;
} else {
index = this->currentPedalboard->GetIndexOfInstanceId(instanceId);
}
}
break;
case Pedalboard::PedalboardType::MainInsert:
if (this->currentMainInsertPedalboard) {
if (instanceId == Pedalboard::MAIN_INSERT_START_CONTROL_ID) {
index = Pedalboard::MAIN_INSERT_START_CONTROL_ID;
} else if (instanceId == Pedalboard::MAIN_INSERT_END_CONTROL_ID) {
index = Pedalboard::END_CONTROL_ID;
} else {
index = this->currentMainInsertPedalboard->GetIndexOfInstanceId(instanceId);
}
}
break;
case Pedalboard::PedalboardType::AuxInsert:
if (this->currentAuxInsertPedalboard) {
if (instanceId == Pedalboard::AUX_INSERT_START_CONTROL_ID) {
index = Pedalboard::START_CONTROL_ID;
} else if (instanceId == Pedalboard::AUX_INSERT_END_CONTROL_ID) {
index = Pedalboard::END_CONTROL_ID;
} else {
index = this->currentAuxInsertPedalboard->GetIndexOfInstanceId(instanceId);
}
}
break;
}
RealtimePedalboardItemIndex result { instanceType, index};
return result;
}
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds) virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
{ {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
@@ -1917,7 +1962,7 @@ public:
auto effect = this->currentPedalboard->GetEffect(instanceId); auto effect = this->currentPedalboard->GetEffect(instanceId);
if (effect) if (effect)
{ {
int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]); RealtimePedalboardItemIndex index = this->GetRealtimeItemIndex(instanceIds[i]);
vuConfig->enabledIndexes.push_back(index); vuConfig->enabledIndexes.push_back(index);
VuUpdate v; VuUpdate v;
v.instanceId_ = instanceId; v.instanceId_ = instanceId;
@@ -1929,20 +1974,41 @@ public:
vuConfig->vuUpdateWorkingData.push_back(v); vuConfig->vuUpdateWorkingData.push_back(v);
vuConfig->vuUpdateResponseData.push_back(v); vuConfig->vuUpdateResponseData.push_back(v);
} }
else if (instanceId == Pedalboard::INPUT_VOLUME_ID || instanceId == Pedalboard::OUTPUT_VOLUME_ID) else if (
instanceId == Pedalboard::START_CONTROL_ID || instanceId == Pedalboard::END_CONTROL_ID
|| instanceId == Pedalboard::MAIN_INSERT_START_CONTROL_ID || instanceId == Pedalboard::MAIN_INSERT_END_CONTROL_ID
|| instanceId == Pedalboard::AUX_INSERT_START_CONTROL_ID || instanceId == Pedalboard::AUX_INSERT_END_CONTROL_ID
)
{ {
int index = (int)instanceId; auto index = GetRealtimeItemIndex(instanceId);
VuUpdate v; VuUpdate v;
vuConfig->enabledIndexes.push_back(index); vuConfig->enabledIndexes.push_back(index);
v.instanceId_ = instanceId; v.instanceId_ = instanceId;
if (instanceId == Pedalboard::INPUT_VOLUME_ID) size_t nChannels = 0;
switch (instanceId)
{ {
v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfInputAudioChannels() > 1; case Pedalboard::START_CONTROL_ID:
} nChannels = this->pHost->GetChannelSelection().mainInputChannels().size();
else break;
{ case Pedalboard::END_CONTROL_ID:
v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfOutputAudioChannels() > 1; nChannels = this->pHost->GetChannelSelection().mainOutputChannels().size();
break;
case Pedalboard::MAIN_INSERT_START_CONTROL_ID:
nChannels = this->pHost->GetChannelSelection().mainOutputChannels().size();
break;
case Pedalboard::MAIN_INSERT_END_CONTROL_ID:
nChannels = this->pHost->GetChannelSelection().mainOutputChannels().size();
break;
case Pedalboard::AUX_INSERT_START_CONTROL_ID:
nChannels = this->pHost->GetChannelSelection().auxInputChannels().size();
break;
case Pedalboard::AUX_INSERT_END_CONTROL_ID:
nChannels = this->pHost->GetChannelSelection().auxOutputChannels().size();
break;
} }
v.isStereoInput_ = v.isStereoOutput_ = nChannels > 1;
vuConfig->vuUpdateWorkingData.push_back(v); vuConfig->vuUpdateWorkingData.push_back(v);
vuConfig->vuUpdateResponseData.push_back(v); vuConfig->vuUpdateResponseData.push_back(v);
} }
+2 -1
View File
@@ -32,6 +32,7 @@
#include "Promise.hpp" #include "Promise.hpp"
#include "json_variant.hpp" #include "json_variant.hpp"
#include "RealtimeMidiEventType.hpp" #include "RealtimeMidiEventType.hpp"
#include "ChannelRouterSettings.hpp"
namespace pipedal namespace pipedal
{ {
@@ -227,7 +228,7 @@ namespace pipedal
virtual std::string AtomToJson(const LV2_Atom *atom) = 0; virtual std::string AtomToJson(const LV2_Atom *atom) = 0;
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) = 0; virtual void Open(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection) = 0;
virtual void Close() = 0; virtual void Close() = 0;
virtual void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration) = 0; virtual void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration) = 0;
+1 -1
View File
@@ -240,7 +240,7 @@ namespace pipedal
{ {
// zero length? We can never have a zero-length bank. // zero length? We can never have a zero-length bank.
// Add a default preset and make it the selected preset. // Add a default preset and make it the selected preset.
Pedalboard pedalboard = Pedalboard::MakeDefault(); Pedalboard pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard);
this->addPreset(pedalboard); this->addPreset(pedalboard);
newSelection = presets_[0]->instanceId(); newSelection = presets_[0]->instanceId();
} }
+10 -3
View File
@@ -204,6 +204,7 @@ else()
endif() endif()
set (PIPEDAL_SOURCES set (PIPEDAL_SOURCES
ChannelRouterSettings.cpp ChannelRouterSettings.hpp
Curl.cpp Curl.hpp Curl.cpp Curl.hpp
Tone3000DownloadType.cpp Tone3000DownloadType.hpp Tone3000DownloadType.cpp Tone3000DownloadType.hpp
Tone3000DownloadProgress.cpp Tone3000DownloadProgress.hpp Tone3000DownloadProgress.cpp Tone3000DownloadProgress.hpp
@@ -358,8 +359,9 @@ target_compile_definitions(libpipedald PUBLIC "_REENTRANT")
target_include_directories(libpipedald PUBLIC ${PIPEDAL_INCLUDES}) target_include_directories(libpipedald PUBLIC ${PIPEDAL_INCLUDES})
target_link_libraries(libpipedald target_link_libraries(libpipedald
PUBLIC PUBLIC
PiPedalCommon PiPedalCommon
PRIVATE
SQLiteCpp SQLiteCpp
${PipeWire_LIBRARIES} ${PipeWire_LIBRARIES}
) )
@@ -768,6 +770,7 @@ add_executable(pipedal_latency_test
target_link_libraries(pipedal_latency_test PRIVATE pthread asound PiPedalCommon) target_link_libraries(pipedal_latency_test PRIVATE pthread asound PiPedalCommon)
target_include_directories(pipedal_latency_test PRIVATE ${PIPEDAL_INCLUDES})
add_executable(pipedal_alsa_info add_executable(pipedal_alsa_info
alsaCheck.cpp alsaCheck.hpp alsaCheck.cpp alsaCheck.hpp
@@ -865,10 +868,14 @@ add_custom_target (
) )
install (TARGETS pipedalconfig pipedal_kconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin install (TARGETS pipedalconfig pipedal_kconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
EXPORT pipedalTargets) EXPORT pipedalTargets
)
install (TARGETS pipedald pipedaladmind pipedal_update DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin install (TARGETS pipedald pipedaladmind pipedal_update DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
EXPORT pipedalSbinTargets) EXPORT pipedalSbinTargets
)
+151
View File
@@ -0,0 +1,151 @@
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "ChannelRouterSettings.hpp"
#include <stdexcept>
using namespace pipedal;
static uint64_t countChannels(const std::vector<int64_t> &channels)
{
if (channels.size() != 2)
{
throw std::runtime_error("Invalid Channel Router channel configuration.");
}
if (channels[0] == -1)
{
if (channels[1] == -1)
{
return 0;
}
else
{
return 1;
}
}
else
{
if (channels[1] == -1)
{
return 1;
}
if (channels[0] == channels[1])
{
return 1;
}
return 2;
}
}
uint64_t ChannelRouterSettings::numberOfAudioInputChannels() const
{
if (!configured_)
{
return 2;
}
return countChannels(mainInputChannels_);
}
uint64_t ChannelRouterSettings::numberOfAudioOutputChannels() const
{
if (!configured_)
{
return 2;
}
return countChannels(mainOutputChannels_);
}
ChannelSelection::ChannelSelection(ChannelRouterSettings&settings)
: mainInputChannels_(settings.mainInputChannels())
, mainOutputChannels_(settings.mainOutputChannels())
, auxInputChannels_(settings.auxInputChannels())
, auxOutputChannels_(settings.auxOutputChannels())
, sendInputChannels_(settings.sendInputChannels())
, sendOutputChannels_(settings.sendOutputChannels())
{
normalizeChannelSelection();
}
static void normalizeInputChannels(std::vector<int64_t>&channels) {
if (channels.size() == 2) {
if (channels[0] == -1 && channels[1] == -1)
{
channels.resize(0);
} else if (channels[0] == channels[1]) {
channels.resize(1);
} else if (channels[1] == -1) {
channels.resize(1);} {
}
}
}
static void normalizeOutputChannels(std::vector<int64_t>&channels) {
normalizeInputChannels(channels);
}
void ChannelSelection::normalizeChannelSelection() {
normalizeInputChannels(mainInputChannels_);
normalizeOutputChannels(mainOutputChannels_);
normalizeInputChannels(auxInputChannels_);
normalizeOutputChannels(auxOutputChannels_);
normalizeInputChannels(sendInputChannels_);
normalizeOutputChannels(sendOutputChannels_);
// If either aux inputs or outputs are zero, don't do ANY aux processing.
if (auxInputChannels_.size() == 0)
{
auxOutputChannels_.resize(0);
} else if (auxOutputChannels_.size() == 0) {
auxInputChannels_.resize(0);
}
// If either main inputs or outputs are empty, add send/receive dummy buffers. (Lv2 plugins shouldn't have to deal with this)
if (mainInputChannels_.size() == 0)
{
mainInputChannels_.resize(1);
mainInputChannels_[0] = -1;
}
if (mainOutputChannels_.size() == 0) {
mainInputChannels_.resize(1);
mainOutputChannels_[0] = -1;
}
// Send buffers are what they are. Let the send plugin deal with it.
}
ChannelRouterSettings::ChannelRouterSettings()
: mainInserts_(Pedalboard::PedalboardType::MainInsert)
, auxInserts_(Pedalboard::PedalboardType::AuxInsert)
{
}
JSON_MAP_BEGIN(ChannelRouterSettings)
JSON_MAP_REFERENCE(ChannelRouterSettings, configured)
JSON_MAP_REFERENCE(ChannelRouterSettings, channelRouterPresetId)
JSON_MAP_REFERENCE(ChannelRouterSettings, mainInputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, mainOutputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, mainInserts)
JSON_MAP_REFERENCE(ChannelRouterSettings, auxInputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, auxOutputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, auxInserts)
JSON_MAP_REFERENCE(ChannelRouterSettings, sendInputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, sendOutputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, controlValues)
JSON_MAP_END()
+113
View File
@@ -0,0 +1,113 @@
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <vector>
#include "json.hpp"
#include "Pedalboard.hpp"
#include <memory>
namespace pipedal
{
class ChannelRouterSettings
{
protected:
static constexpr int64_t CHANNEL_ROUTER_MAIN_INSERT_ID = -4; // Reserved Instance ID for Router Main Inserts.
static constexpr int64_t CHANNEL_ROUTER_AUX_INSERT_ID = -5; // Reserved Instance ID for Router Aux inserts.
bool configured_ = false;
int64_t channelRouterPresetId_ = -1;
std::vector<int64_t> mainInputChannels_ = {1, 1};
std::vector<int64_t> mainOutputChannels_ = {0, 1};
Pedalboard mainInserts_;
std::vector<int64_t> auxInputChannels_ = {-1, -1};
std::vector<int64_t> auxOutputChannels_ = {-1, -1};
Pedalboard auxInserts_;
std::vector<int64_t> sendInputChannels_ = {-1, -1};
std::vector<int64_t> sendOutputChannels_ = {-1, -1};
std::vector<ControlValue> controlValues_;
public:
using self = ChannelRouterSettings;
using ptr = std::shared_ptr<self>;
ChannelRouterSettings();
uint64_t numberOfAudioInputChannels() const;
uint64_t numberOfAudioOutputChannels() const;
JSON_GETTER_SETTER(configured)
JSON_GETTER_SETTER(channelRouterPresetId)
JSON_GETTER_SETTER_REF(mainInputChannels)
JSON_GETTER_SETTER_REF(mainOutputChannels)
JSON_GETTER_SETTER_REF(mainInserts)
JSON_GETTER_SETTER_REF(auxInputChannels)
JSON_GETTER_SETTER_REF(auxOutputChannels)
JSON_GETTER_SETTER_REF(auxInserts)
JSON_GETTER_SETTER_REF(sendInputChannels)
JSON_GETTER_SETTER_REF(sendOutputChannels)
JSON_GETTER_SETTER_REF(controlValues)
DECLARE_JSON_MAP(ChannelRouterSettings);
};
// just the channel selecttions.
class ChannelSelection
{
public:
ChannelSelection() = default;
ChannelSelection(ChannelRouterSettings &settings);
ChannelSelection(const ChannelSelection &other) = default;
ChannelSelection(ChannelSelection &&other) = default;
ChannelSelection &operator=(const ChannelSelection &other) = default;
ChannelSelection &operator=(ChannelSelection &&other) = default;
~ChannelSelection() = default;
const std::vector<int64_t> &mainInputChannels() const { return mainInputChannels_; }
const std::vector<int64_t> &mainOutputChannels() const { return mainOutputChannels_; }
const std::vector<int64_t> &auxInputChannels() const { return auxInputChannels_; }
const std::vector<int64_t> &auxOutputChannels() const { return auxOutputChannels_; }
const std::vector<int64_t> &sendInputChannels() const { return sendInputChannels_; }
const std::vector<int64_t> &sendOutputChannels() const { return sendOutputChannels_; }
std::vector<int64_t> &mainInputChannels() { return mainInputChannels_; }
std::vector<int64_t> &mainOutputChannels() { return mainOutputChannels_; }
std::vector<int64_t> &auxInputChannels() { return auxInputChannels_; }
std::vector<int64_t> &auxOutputChannels() { return auxOutputChannels_; }
std::vector<int64_t> &sendInputChannels() { return sendInputChannels_; }
std::vector<int64_t> &sendOutputChannels() { return sendOutputChannels_; }
private:
void normalizeChannelSelection();
std::vector<int64_t> mainInputChannels_;
std::vector<int64_t> mainOutputChannels_;
std::vector<int64_t> auxInputChannels_;
std::vector<int64_t> auxOutputChannels_;
std::vector<int64_t> sendInputChannels_;
std::vector<int64_t> sendOutputChannels_;
};
}
+94 -108
View File
@@ -1,7 +1,7 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2024 Robin E. R. Davies * Copyright (c) 2026 Robin E. R. Davies
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of * Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in * this software and associated documentation files (the "Software"), to deal in
@@ -23,6 +23,7 @@
*/ */
#include "pch.h" #include "pch.h"
#include "PiPedalCommon.hpp"
#include "util.hpp" #include "util.hpp"
#include <bit> #include <bit>
#include <memory> #include <memory>
@@ -39,6 +40,7 @@
#include "ss.hpp" #include "ss.hpp"
#include "SchedulerPriority.hpp" #include "SchedulerPriority.hpp"
#include "CrashGuard.hpp" #include "CrashGuard.hpp"
#include "ChannelRouterSettings.hpp"
#include "CpuUse.hpp" #include "CpuUse.hpp"
@@ -85,14 +87,19 @@ namespace pipedal
uint32_t captureFrameSize = 0; uint32_t captureFrameSize = 0;
std::vector<float *> activeCaptureBuffers; std::vector<std::vector<float>> allocatedBuffers;
std::vector<float *> activePlaybackBuffers; std::vector<float *> mainCaptureBuffers;
std::vector<float *> mainPlaybackBuffers;
std::vector<float *> captureBuffers; std::vector<float *> auxCaptureBuffers;
std::vector<float *> playbackBuffers; std::vector<float *> auxPlaybackBuffers;
std::vector<float*> sendCaptureBuffers;
std::vector<float*> sendPlaybackBuffers;
std::vector<float *> deviceCaptureBuffers;
std::vector<float *> devicePlaybackBuffers;
uint8_t *rawCaptureBuffer = nullptr;
uint8_t *rawPlaybackBuffer = nullptr;
AudioDriverHost *driverHost = nullptr; AudioDriverHost *driverHost = nullptr;
uint32_t channels = 2; uint32_t channels = 2;
@@ -145,7 +152,7 @@ namespace pipedal
std::atomic<bool> terminateAudio_ = false; std::atomic<bool> terminateAudio_ = false;
void terminateAudio(bool terminate) PIPEDAL_NON_INLINE void terminateAudio(bool terminate)
{ {
this->terminateAudio_ = terminate; this->terminateAudio_ = terminate;
} }
@@ -161,32 +168,28 @@ namespace pipedal
} }
private: private:
void AllocateBuffers(std::vector<float *> &buffers, size_t n) PIPEDAL_NON_INLINE void AllocateBuffers(std::vector<float *> &buffers, size_t n)
{ {
buffers.resize(n); buffers.resize(n);
for (size_t i = 0; i < n; ++i) for (size_t i = 0; i < n; ++i)
{ {
buffers[i] = new float[this->bufferSize]; buffers[i] = AllocateAudioBuffer();
for (size_t j = 0; j < this->bufferSize; ++j)
{
buffers[i][j] = 0;
}
} }
} }
virtual size_t GetMidiInputEventCount() override PIPEDAL_NON_INLINE virtual size_t GetMidiInputEventCount() override
{ {
return midiEventCount; return midiEventCount;
} }
virtual MidiEvent *GetMidiEvents() override PIPEDAL_NON_INLINE virtual MidiEvent *GetMidiEvents() override
{ {
return this->midiEvents.data(); return this->midiEvents.data();
} }
JackChannelSelection channelSelection; ChannelSelection channelSelection;
bool open = false; bool open = false;
virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) PIPEDAL_NON_INLINE virtual void Open(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection)
{ {
terminateAudio_ = false; terminateAudio_ = false;
if (open) if (open)
@@ -208,7 +211,7 @@ namespace pipedal
throw; throw;
} }
} }
virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) override PIPEDAL_NON_INLINE virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) override
{ {
this->alsaSequencer = alsaSequencer; this->alsaSequencer = alsaSequencer;
} }
@@ -220,23 +223,28 @@ namespace pipedal
<< ", " << "Native float" << ", " << "Native float"
<< ", " << this->sampleRate << ", " << this->sampleRate
<< ", " << this->bufferSize << "x" << this->numberOfBuffers << ", " << this->bufferSize << "x" << this->numberOfBuffers
<< ", in: " << this->InputBufferCount() << "/" << this->captureChannels << ", " << "device in: " << this->DeviceInputBufferCount()
<< ", out: " << this->OutputBufferCount() << "/" << this->playbackChannels); << ", " << "device out: " << this->DeviceOutputBufferCount()
<< ", main in: " << this->MainInputBufferCount()
<< ", main out: " << this->MainOutputBufferCount()
<< ", aux in: " << this->AuxInputBufferCount()
<< ", aux out: " << this->AuxOutputBufferCount()
<< ", send in: " << this->SendInputBufferCount()
<< ", send out: " << this->SendOutputBufferCount()
);
return result; return result;
} }
void OpenAudio(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection) void OpenAudio(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection)
{ {
int err; int err;
this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers(); this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers();
this->bufferSize = jackServerSettings.GetBufferSize(); this->bufferSize = jackServerSettings.GetBufferSize();
AllocateBuffers(captureBuffers, channels);
AllocateBuffers(playbackBuffers, channels);
} }
std::jthread *audioThread; std::unique_ptr<std::jthread> audioThread;
bool audioRunning; bool audioRunning;
bool block = false; bool block = false;
@@ -303,7 +311,7 @@ namespace pipedal
this->driverHost->OnProcess(framesRead); this->driverHost->OnProcess(framesRead);
/// no attempt at realtime. Just as long as we run occasionally. /// no attempt at realtime. Just as long as we run occasionally.
std::this_thread::sleep_for(std::chrono::milliseconds(20)); std::this_thread::sleep_for(std::chrono::milliseconds(5));
} }
@@ -318,22 +326,6 @@ namespace pipedal
bool alsaActive = false; bool alsaActive = false;
static int IndexFromPortName(const std::string &s)
{
auto pos = s.find_last_of('_');
if (pos == std::string::npos)
{
throw std::invalid_argument("Bad port name.");
}
const char *p = s.c_str() + (pos + 1);
int v = atoi(p);
if (v < 0)
{
throw std::invalid_argument("Bad port name.");
}
return v;
}
bool activated = false; bool activated = false;
virtual void Activate() virtual void Activate()
@@ -344,41 +336,17 @@ namespace pipedal
} }
activated = true; activated = true;
this->activeCaptureBuffers.resize(channelSelection.GetInputAudioPorts().size()); AllocateBuffers(deviceCaptureBuffers, channels);
AllocateBuffers(devicePlaybackBuffers, channels);
playbackBuffers.resize(channels); AllocateBuffers(mainCaptureBuffers, channelSelection.mainInputChannels().size());
AllocateBuffers(mainPlaybackBuffers, channelSelection.mainOutputChannels().size());
AllocateBuffers(auxCaptureBuffers, channelSelection.auxInputChannels().size());
AllocateBuffers(auxPlaybackBuffers, channelSelection.auxOutputChannels().size());
AllocateBuffers(sendCaptureBuffers, channelSelection.sendInputChannels().size());
AllocateBuffers(sendPlaybackBuffers, channelSelection.sendOutputChannels().size());
int ix = 0; audioThread = std::make_unique<std::jthread>([this]()
for (auto &x : channelSelection.GetInputAudioPorts())
{
int sourceIndex = IndexFromPortName(x);
if (sourceIndex >= captureBuffers.size())
{
Lv2Log::error(SS("Invalid audio input port: " << x));
}
else
{
this->activeCaptureBuffers[ix++] = this->captureBuffers[sourceIndex];
}
}
this->activePlaybackBuffers.resize(channelSelection.GetOutputAudioPorts().size());
ix = 0;
for (auto &x : channelSelection.GetOutputAudioPorts())
{
int sourceIndex = IndexFromPortName(x);
if (sourceIndex >= playbackBuffers.size())
{
Lv2Log::error(SS("Invalid audio output port: " << x));
}
else
{
this->activePlaybackBuffers[ix++] = this->playbackBuffers[sourceIndex];
}
}
audioThread = new std::jthread([this]()
{ AudioThread(); }); { AudioThread(); });
} }
@@ -393,7 +361,7 @@ namespace pipedal
if (audioThread) if (audioThread)
{ {
this->audioThread->join(); this->audioThread->join();
this->audioThread = 0; this->audioThread = nullptr;
} }
Lv2Log::debug("Audio thread joined."); Lv2Log::debug("Audio thread joined.");
} }
@@ -402,46 +370,64 @@ namespace pipedal
public: public:
virtual size_t DeviceInputBufferCount() const override { return deviceCaptureBuffers.size(); }
virtual size_t DeviceOutputBufferCount() const override { return devicePlaybackBuffers.size(); }
virtual size_t InputBufferCount() const { return activeCaptureBuffers.size(); } virtual size_t MainInputBufferCount() const override { return mainCaptureBuffers.size(); }
virtual float *GetInputBuffer(size_t channel) virtual float *GetMainInputBuffer(size_t channel) override
{ {
return activeCaptureBuffers[channel]; return mainCaptureBuffers[channel];
} }
virtual size_t OutputBufferCount() const { return activePlaybackBuffers.size(); } virtual size_t MainOutputBufferCount() const { return mainPlaybackBuffers.size(); }
virtual float *GetOutputBuffer(size_t channel) virtual float *GetMainOutputBuffer(size_t channel) override
{ {
return activePlaybackBuffers[channel]; return mainPlaybackBuffers[channel];
} }
void FreeBuffers(std::vector<float *> &buffer) virtual size_t AuxInputBufferCount() const override { return auxCaptureBuffers.size(); }
virtual float *GetAuxInputBuffer(size_t channel) override
{ {
for (size_t i = 0; i < buffer.size(); ++i) return auxCaptureBuffers[channel];
{
// delete[] buffer[i];
buffer[i] = 0;
}
buffer.clear();
} }
void DeleteBuffers()
virtual size_t AuxOutputBufferCount() const override { return auxPlaybackBuffers.size(); }
virtual float *GetAuxOutputBuffer(size_t channel) override
{ {
activeCaptureBuffers.clear(); return auxPlaybackBuffers[channel];
activePlaybackBuffers.clear();
FreeBuffers(this->playbackBuffers);
FreeBuffers(this->captureBuffers);
if (rawCaptureBuffer)
{
delete[] rawCaptureBuffer;
rawCaptureBuffer = nullptr;
}
if (rawPlaybackBuffer)
{
delete[] rawPlaybackBuffer;
rawPlaybackBuffer = nullptr;
}
} }
virtual void Close()
virtual size_t SendInputBufferCount() const override { return sendCaptureBuffers.size(); }
virtual float *GetSendInputBuffer(size_t channel) override
{
return sendCaptureBuffers[channel];
}
virtual size_t SendOutputBufferCount() const override { return sendPlaybackBuffers.size(); }
virtual float *GetSendOutputBuffer(size_t channel) override
{
return sendPlaybackBuffers[channel];
}
PIPEDAL_NON_INLINE float*AllocateAudioBuffer() {
allocatedBuffers.push_back(std::vector<float>(bufferSize));
return allocatedBuffers.back().data();
}
PIPEDAL_NON_INLINE void DeleteBuffers()
{
mainCaptureBuffers.clear();
mainPlaybackBuffers.clear();
auxCaptureBuffers.clear();
auxPlaybackBuffers.clear();
sendCaptureBuffers.clear();
sendPlaybackBuffers.clear();
allocatedBuffers.clear();
}
PIPEDAL_NON_INLINE virtual void Close()
{ {
if (!open) if (!open)
{ {
@@ -453,18 +439,18 @@ namespace pipedal
DeleteBuffers(); DeleteBuffers();
} }
virtual float CpuUse() PIPEDAL_NON_INLINE virtual float CpuUse()
{ {
return 0; return 0;
} }
virtual float CpuOverhead() PIPEDAL_NON_INLINE virtual float CpuOverhead()
{ {
return 0.1; return 0.1;
} }
}; };
AudioDriver *CreateDummyAudioDriver(AudioDriverHost *driverHost,const std::string&deviceName) PIPEDAL_NON_INLINE AudioDriver *CreateDummyAudioDriver(AudioDriverHost *driverHost,const std::string&deviceName)
{ {
return new DummyDriverImpl(driverHost,deviceName); return new DummyDriverImpl(driverHost,deviceName);
} }
+1
View File
@@ -24,6 +24,7 @@
#pragma once #pragma once
#include <lilv/lilv.h>
#include "AudioDriver.hpp" #include "AudioDriver.hpp"
#include "JackServerSettings.hpp" #include "JackServerSettings.hpp"
+2 -2
View File
@@ -26,6 +26,7 @@ namespace pipedal {
class Lv2PluginInfo; class Lv2PluginInfo;
class IEffect; class IEffect;
class HostWorkerThread; class HostWorkerThread;
class ChannelSelection;
class IHost class IHost
{ {
@@ -43,13 +44,12 @@ namespace pipedal {
virtual size_t GetAtomBufferSize() const = 0; virtual size_t GetAtomBufferSize() const = 0;
virtual bool HasMidiInputChannel() const = 0; virtual bool HasMidiInputChannel() const = 0;
virtual int GetNumberOfInputAudioChannels() const = 0;
virtual int GetNumberOfOutputAudioChannels() const = 0;
virtual std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri) const = 0; virtual std::shared_ptr<Lv2PluginInfo> GetPluginInfo(const std::string &uri) const = 0;
virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0; virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0;
virtual std::string GetPluginStoragePath() const = 0; virtual std::string GetPluginStoragePath() const = 0;
virtual const ChannelSelection&GetChannelSelection() const = 0;
}; };
} }
-1
View File
@@ -124,5 +124,4 @@ namespace pipedal
DECLARE_JSON_MAP(JackChannelSelection); DECLARE_JSON_MAP(JackChannelSelection);
}; };
} // namespace. } // namespace.
+42 -5
View File
@@ -320,6 +320,7 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList, ExistingEffectMap *existingEffects) void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList, ExistingEffectMap *existingEffects)
{ {
this->pHost = pHost; this->pHost = pHost;
this->pedalboardType = pedalboard.GetInstanceType();
inputVolume.SetSampleRate((float)(this->pHost->GetSampleRate())); inputVolume.SetSampleRate((float)(this->pHost->GetSampleRate()));
outputVolume.SetSampleRate((float)(this->pHost->GetSampleRate())); outputVolume.SetSampleRate((float)(this->pHost->GetSampleRate()));
@@ -329,13 +330,15 @@ void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardE
inputVolume.SetTarget(pedalboard.input_volume_db()); inputVolume.SetTarget(pedalboard.input_volume_db());
outputVolume.SetTarget(pedalboard.output_volume_db()); outputVolume.SetTarget(pedalboard.output_volume_db());
for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i) size_t nInputs = std::max(GetNumberOfAudioInputChannels(),(size_t)1);
for (size_t i = 0; i < nInputs; ++i)
{ {
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize())); this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
} }
auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers, errorList, existingEffects); auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers, errorList, existingEffects);
int nOutputs = pHost->GetNumberOfOutputAudioChannels(); size_t nOutputs = GetNumberOfAudioOutputChannels();
if (nOutputs == 1) if (nOutputs == 1)
{ {
this->pedalboardOutputBuffers.push_back(outputs[0]); this->pedalboardOutputBuffers.push_back(outputs[0]);
@@ -572,9 +575,14 @@ void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp
{ {
for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i) for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i)
{ {
int index = vuConfiguration->enabledIndexes[i]; auto& rtIndex = vuConfiguration->enabledIndexes[i];
if (rtIndex.instanceType != this->pedalboardType)
{
continue;
}
int index = rtIndex.index;
VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i]; VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i];
if (index == Pedalboard::INPUT_VOLUME_ID) if (index == Pedalboard::START_CONTROL_ID)
{ {
if (this->pedalboardInputBuffers.size() > 1) if (this->pedalboardInputBuffers.size() > 1)
{ {
@@ -588,7 +596,7 @@ void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp
pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]), samples); // after input volume applied. pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]), samples); // after input volume applied.
} }
} }
else if (index == Pedalboard::OUTPUT_VOLUME_ID) else if (index == Pedalboard::END_CONTROL_ID)
{ {
if (this->pedalboardOutputBuffers.size() > 1) if (this->pedalboardOutputBuffers.size() > 1)
{ {
@@ -1016,3 +1024,32 @@ void Lv2Pedalboard::handleTapTempo(uint8_t value, const MidiTimestamp& timestamp
mapping.lastTapTimestamp = timestamp; mapping.lastTapTimestamp = timestamp;
} }
} }
size_t Lv2Pedalboard::GetNumberOfAudioInputChannels() const {
switch (pedalboardType)
{
case Pedalboard::PedalboardType::MainPedalboard:
return pHost->GetChannelSelection().mainInputChannels().size();
case Pedalboard::PedalboardType::MainInsert:
return pHost->GetChannelSelection().mainOutputChannels().size();
case Pedalboard::PedalboardType::AuxInsert:
return pHost->GetChannelSelection().auxInputChannels().size();
default:
throw std::runtime_error("Invalid argument");
}
}
size_t Lv2Pedalboard::GetNumberOfAudioOutputChannels() const {
switch (pedalboardType)
{
case Pedalboard::PedalboardType::MainPedalboard:
return pHost->GetChannelSelection().mainOutputChannels().size();
case Pedalboard::PedalboardType::MainInsert:
return pHost->GetChannelSelection().mainOutputChannels().size();
case Pedalboard::PedalboardType::AuxInsert:
return pHost->GetChannelSelection().auxOutputChannels().size();
default:
throw std::runtime_error("Invalid argument");
}
}
+4
View File
@@ -51,6 +51,7 @@ namespace pipedal
class Lv2Pedalboard class Lv2Pedalboard
{ {
private: private:
Pedalboard::PedalboardType pedalboardType;
IHost *pHost = nullptr; IHost *pHost = nullptr;
size_t currentFrameOffset = 0; size_t currentFrameOffset = 0;
DbDezipper inputVolume; DbDezipper inputVolume;
@@ -128,6 +129,9 @@ namespace pipedal
std::vector<IEffect *> &GetEffects() { return realtimeEffects; } std::vector<IEffect *> &GetEffects() { return realtimeEffects; }
std::vector<std::shared_ptr<IEffect>> &GetSharedEffectList() { return effects; } std::vector<std::shared_ptr<IEffect>> &GetSharedEffectList() { return effects; }
size_t GetNumberOfAudioInputChannels() const;
size_t GetNumberOfAudioOutputChannels() const;
int GetIndexOfInstanceId(uint64_t instanceId) int GetIndexOfInstanceId(uint64_t instanceId)
{ {
for (int i = 0; i < this->realtimeEffects.size(); ++i) for (int i = 0; i < this->realtimeEffects.size(); ++i)
+17 -1
View File
@@ -210,7 +210,7 @@ PedalboardItem Pedalboard::MakeSplit()
Pedalboard Pedalboard::MakeDefault() Pedalboard Pedalboard::MakeDefault(PedalboardType instanceType)
{ {
// copy insanity. but it happens so rarely. // copy insanity. but it happens so rarely.
Pedalboard result; Pedalboard result;
@@ -510,6 +510,22 @@ Snapshot Pedalboard::MakeSnapshotFromCurrentSettings(const Pedalboard &previousP
} }
Pedalboard::Pedalboard(PedalboardType instanceType)
{
switch (instanceType)
{
case PedalboardType::MainPedalboard:
nextInstanceId_ = 0;
break;
case PedalboardType::MainInsert:
nextInstanceId_ = MAIN_INSERT_INSTANCE_BASE;;
break;
case PedalboardType::AuxInsert:
nextInstanceId_ = AUX_INSERT_INSTANCE_BASE;;
break;
}
}
JSON_MAP_BEGIN(ControlValue) JSON_MAP_BEGIN(ControlValue)
JSON_MAP_REFERENCE(ControlValue,key) JSON_MAP_REFERENCE(ControlValue,key)
+45 -3
View File
@@ -230,11 +230,53 @@ class Pedalboard {
int64_t selectedPlugin_ = -1; int64_t selectedPlugin_ = -1;
static constexpr int64_t TWO_POWER_52 = 4503599627370496; // Maximum Javascript integer value.
static constexpr int64_t TWO_POWER_48 = 281474976710656; // Number of possible instance IDs for Main Pedalboards.
static constexpr int64_t MAX_INSTANCE_ID = TWO_POWER_48;
public: public:
enum class PedalboardType {
MainPedalboard,
MainInsert,
AuxInsert
};
static constexpr int64_t MAIN_INSERT_INSTANCE_BASE = TWO_POWER_52-2*MAX_INSTANCE_ID;
static constexpr int64_t AUX_INSERT_INSTANCE_BASE = TWO_POWER_52-1*MAX_INSTANCE_ID;
static constexpr int64_t CHANNEL_ROUTER_CONTROLS_INSTANCE_ID = -4; // Reserved Instance ID for Router Controls.
static constexpr int64_t START_CONTROL_ID = -2; // synthetic PedalboardItem for input volume.
static constexpr int64_t END_CONTROL_ID = -3; // synthetic PedalboardItem for output volume.
static constexpr int64_t MAIN_INSERT_START_CONTROL_ID = MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2;
static constexpr int64_t MAIN_INSERT_END_CONTROL_ID = MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3;
static constexpr int64_t AUX_INSERT_START_CONTROL_ID = AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2;
static constexpr int64_t AUX_INSERT_END_CONTROL_ID = AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3;
static PedalboardType GetInstanceTypeFromInstanceId(int64_t instanceId) {
if (instanceId == START_CONTROL_ID) return PedalboardType::MainPedalboard;
if (instanceId == END_CONTROL_ID) return PedalboardType::MainPedalboard;
if (instanceId >= MAIN_INSERT_INSTANCE_BASE) {
return PedalboardType::MainInsert;
} else if (instanceId >= AUX_INSERT_INSTANCE_BASE) {
return PedalboardType::AuxInsert;
} else {
return PedalboardType::MainPedalboard;
}
}
PedalboardType GetInstanceType() const {
return GetInstanceTypeFromInstanceId(this->nextInstanceId_); // instanceId is irrelevant here.
}
Pedalboard(PedalboardType instanceType = PedalboardType::MainPedalboard);
// deep copy, breaking shared pointers. // deep copy, breaking shared pointers.
Pedalboard DeepCopy(); Pedalboard DeepCopy();
static constexpr int64_t INPUT_VOLUME_ID = -2; // synthetic PedalboardItem for input volume.
static constexpr int64_t OUTPUT_VOLUME_ID = -3; // synthetic PedalboardItem for output volume.
bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value); bool SetControlValue(int64_t pedalItemId, const std::string &symbol, float value);
bool SetItemTitle(int64_t pedalItemId, const std::string &title, const std::string&iconColor); bool SetItemTitle(int64_t pedalItemId, const std::string &title, const std::string&iconColor);
bool SetItemEnabled(int64_t pedalItemId, bool enabled); bool SetItemEnabled(int64_t pedalItemId, bool enabled);
@@ -266,7 +308,7 @@ public:
PedalboardItem MakeSplit(); PedalboardItem MakeSplit();
static Pedalboard MakeDefault(); static Pedalboard MakeDefault(Pedalboard::PedalboardType instanceType = Pedalboard::PedalboardType::MainPedalboard);
}; };
#undef GETTER_SETTER_REF #undef GETTER_SETTER_REF
+22 -14
View File
@@ -34,6 +34,7 @@
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include <sched.h> #include <sched.h>
#include "ChannelRouterSettings.hpp"
using namespace pipedal; using namespace pipedal;
@@ -169,19 +170,26 @@ public:
delete[] inputBuffers; delete[] inputBuffers;
delete[] outputBuffers; delete[] outputBuffers;
} }
std::vector<std::string> SelectChannels(const std::vector<std::string> &available, const std::vector<int> &selection) std::vector<int64_t> SelectChannels(const std::vector<std::string> &available, const std::vector<int> &selection)
{ {
std::vector<int64_t> result;
if (selection.size() == 0) if (selection.size() == 0)
return available; {
result.resize(available.size());
for (size_t i = 0; i < result.size(); ++i)
{
result[i] = (int64_t)i;
}
return result;
}
std::vector<std::string> result;
for (int sel : selection) for (int sel : selection)
{ {
if (sel < 0 || sel >= available.size()) if (sel < 0 || sel >= available.size())
{ {
throw PiPedalArgumentException(SS("Invalid channel: " + sel)); throw PiPedalArgumentException(SS("Invalid channel: " + sel));
} }
result.push_back(available[sel]); result.push_back(sel);
} }
return result; return result;
} }
@@ -199,22 +207,22 @@ public:
auto &availableInputs = jackConfiguration.inputAudioPorts(); auto &availableInputs = jackConfiguration.inputAudioPorts();
auto &availableOutputs = jackConfiguration.outputAudioPorts(); auto &availableOutputs = jackConfiguration.outputAudioPorts();
std::vector<std::string> inputAudioPorts, outputAudioPorts; std::vector<int64_t> inputAudioPorts, outputAudioPorts;
inputAudioPorts = SelectChannels(availableInputs, this->inputChannels); inputAudioPorts = SelectChannels(availableInputs, this->inputChannels);
outputAudioPorts = SelectChannels(availableOutputs, this->outputChannels); outputAudioPorts = SelectChannels(availableOutputs, this->outputChannels);
JackChannelSelection channelSelection( ChannelSelection channelSelection;
inputAudioPorts, outputAudioPorts, channelSelection.mainInputChannels() = inputAudioPorts;
std::vector<AlsaMidiDeviceInfo>()); channelSelection.mainOutputChannels() = outputAudioPorts;
audioDriver = CreateAlsaDriver(this); audioDriver = CreateAlsaDriver(this);
latencyMonitor.Init(jackConfiguration.sampleRate()); latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings, channelSelection); audioDriver->Open(serverSettings, channelSelection);
inputBuffers = new float *[channelSelection.GetInputAudioPorts().size()]; inputBuffers = new float *[channelSelection.mainInputChannels().size()];
outputBuffers = new float *[channelSelection.GetOutputAudioPorts().size()]; outputBuffers = new float *[channelSelection.mainOutputChannels().size()];
audioDriver->Activate(); audioDriver->Activate();
@@ -364,16 +372,16 @@ public:
virtual void OnProcess(size_t nFrames) virtual void OnProcess(size_t nFrames)
{ {
size_t inputs = audioDriver->InputBufferCount(); size_t inputs = audioDriver->MainInputBufferCount();
size_t outputs = audioDriver->OutputBufferCount(); size_t outputs = audioDriver->MainOutputBufferCount();
for (size_t i = 0; i < inputs; ++i) for (size_t i = 0; i < inputs; ++i)
{ {
inputBuffers[i] = audioDriver->GetInputBuffer(i); inputBuffers[i] = audioDriver->GetMainInputBuffer(i);
} }
for (size_t i = 0; i < outputs; ++i) for (size_t i = 0; i < outputs; ++i)
{ {
outputBuffers[i] = audioDriver->GetOutputBuffer(i); outputBuffers[i] = audioDriver->GetMainOutputBuffer(i);
} }
for (size_t i = 0; i < nFrames; ++i) for (size_t i = 0; i < nFrames; ++i)
+32
View File
@@ -0,0 +1,32 @@
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#ifndef PIPEDAL_NON_INLINE
#if defined(__GNUC__) || defined(__clang__)
#define PIPEDAL_NON_INLINE __attribute__((noinline))
#elif defined(_MSC_VER)
#define PIPEDAL_NON_INLINE __declspec(noinline)
#else
#define PIPEDAL_NON_INLINE
#endif
#endif
+39 -27
View File
@@ -82,7 +82,7 @@ PiPedalModel::PiPedalModel()
this->updater = Updater::Create(); this->updater = Updater::Create();
this->updater->Start(); this->updater->Start();
this->currentUpdateStatus = updater->GetCurrentStatus(); this->currentUpdateStatus = updater->GetCurrentStatus();
this->pedalboard = Pedalboard::MakeDefault(); this->pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard);
#if JACK_HOST #if JACK_HOST
this->jackServerSettings = this->storage.GetJackServerSettings(); // to get onboarding flag. this->jackServerSettings = this->storage.GetJackServerSettings(); // to get onboarding flag.
this->jackServerSettings.ReadJackDaemonConfiguration(); this->jackServerSettings.ReadJackDaemonConfiguration();
@@ -228,7 +228,14 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration)
this->jackConfiguration = this->jackConfiguration.JackInitialize(); this->jackConfiguration = this->jackConfiguration.JackInitialize();
#else #else
this->jackServerSettings = storage.GetJackServerSettings(); this->jackServerSettings = storage.GetJackServerSettings();
this->jackConfiguration.AlsaInitialize(jackServerSettings);
#endif #endif
this->channelRouterSettings = storage.GetChannelRouterSettings();
pluginHost.OnConfigurationChanged(
jackConfiguration,
storage.GetChannelSelection());
} }
@@ -390,7 +397,7 @@ void PiPedalModel::Load()
if (CrashGuard::HasCrashed()) if (CrashGuard::HasCrashed())
{ {
// ignore the current preset, and load a blank pedalboard in order to avoid a potential plugin crash. // ignore the current preset, and load a blank pedalboard in order to avoid a potential plugin crash.
this->pedalboard = Pedalboard::MakeDefault(); this->pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard);
} }
else else
{ {
@@ -1516,17 +1523,13 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver)
throw std::runtime_error("Audio configuration not valid."); throw std::runtime_error("Audio configuration not valid.");
} }
auto channelSelection = this->storage.GetJackChannelSelection(jackConfiguration); const auto & channelSelection = this->storage.GetChannelSelection();
if (!channelSelection.isValid())
{
throw std::runtime_error("Audio configuration not valid.");
}
this->audioHost->Open(jackServerSettings, channelSelection); this->audioHost->Open(jackServerSettings, channelSelection);
this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection); this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection);
FireChannelSelectionChanged(-1); FireChannelRouterSettingsChanged(-1);
LoadCurrentPedalboard(); LoadCurrentPedalboard();
this->UpdateRealtimeVuSubscriptions(); this->UpdateRealtimeVuSubscriptions();
@@ -1622,33 +1625,18 @@ std::vector<AlsaSequencerPortSelection> PiPedalModel::GetAlsaSequencerPorts()
return result; return result;
} }
void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection)
{
{
std::lock_guard<std::recursive_mutex> lock(mutex); // copy atomically.
this->storage.SetJackChannelSelection(channelSelection);
this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection); void PiPedalModel::FireChannelRouterSettingsChanged(int64_t clientId)
CancelAudioRetry();
}
RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications..
this->FireChannelSelectionChanged(clientId);
}
void PiPedalModel::FireChannelSelectionChanged(int64_t clientId)
{ {
std::lock_guard<std::recursive_mutex> guard{mutex}; std::lock_guard<std::recursive_mutex> guard{mutex};
{ {
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
JackChannelSelection channelSelection = storage.GetJackChannelSelection(this->jackConfiguration); ChannelRouterSettings::ptr channelRouterSettings = this->channelRouterSettings;
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()}; std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
for (auto &subscriber : t) for (auto &subscriber : t)
{ {
subscriber->OnChannelSelectionChanged(clientId, channelSelection); subscriber->OnChannelRouterSettingsChanged(clientId, *channelRouterSettings);
} }
} }
} }
@@ -1765,7 +1753,7 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions()
for (int i = 0; i < activeVuSubscriptions.size(); ++i) for (int i = 0; i < activeVuSubscriptions.size(); ++i)
{ {
auto instanceId = activeVuSubscriptions[i].instanceid; auto instanceId = activeVuSubscriptions[i].instanceid;
if (pedalboard.HasItem(instanceId) || instanceId == Pedalboard::INPUT_VOLUME_ID || instanceId == Pedalboard::OUTPUT_VOLUME_ID) if (pedalboard.HasItem(instanceId) || instanceId == Pedalboard::START_CONTROL_ID || instanceId == Pedalboard::END_CONTROL_ID)
{ {
addedInstances.insert(activeVuSubscriptions[i].instanceid); addedInstances.insert(activeVuSubscriptions[i].instanceid);
} }
@@ -3408,6 +3396,30 @@ int64_t PiPedalModel::CopyPresetsToBank(int64_t bankInstanceId, const std::vecto
} }
ChannelRouterSettings::ptr PiPedalModel::GetChannelRouterSettings()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
return this->channelRouterSettings;
}
void PiPedalModel::SetChannelRouterSettings(int64_t clientId,ChannelRouterSettings::ptr &settings)
{
{
std::lock_guard<std::recursive_mutex> lock(mutex);
this->channelRouterSettings = settings;
this->storage.SetChannelRouterSettings(settings);
this->pluginHost.OnConfigurationChanged(jackConfiguration, *settings);
CancelAudioRetry();
}
RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications..
this->FireChannelRouterSettingsChanged(clientId);
}
std::string PiPedalModel::Tone3000ThumbnailDirectory() std::string PiPedalModel::Tone3000ThumbnailDirectory()
{ {
return "/var/pipedal/tone3000_thumbnails"; return "/var/pipedal/tone3000_thumbnails";
+8 -3
View File
@@ -40,6 +40,7 @@
#include "Promise.hpp" #include "Promise.hpp"
#include "AtomConverter.hpp" #include "AtomConverter.hpp"
#include "FileEntry.hpp" #include "FileEntry.hpp"
#include "ChannelRouterSettings.hpp"
#include <unordered_map> #include <unordered_map>
#include "Tone3000Downloader.hpp" #include "Tone3000Downloader.hpp"
@@ -73,7 +74,7 @@ namespace pipedal
virtual void OnSnapshotModified(int64_t selectedSnapshot, bool modified) = 0; virtual void OnSnapshotModified(int64_t selectedSnapshot, bool modified) = 0;
virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) = 0; virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) = 0;
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0; virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0; virtual void OnChannelRouterSettingsChanged(int64_t clientId, const ChannelRouterSettings &channelRouterSettings) = 0;
virtual void OnVuMeterUpdate(const std::vector<VuUpdate> &updates) = 0; virtual void OnVuMeterUpdate(const std::vector<VuUpdate> &updates) = 0;
virtual void OnBankIndexChanged(const BankIndex &bankIndex) = 0; virtual void OnBankIndexChanged(const BankIndex &bankIndex) = 0;
virtual void OnJackServerSettingsChanged(const JackServerSettings &jackServerSettings) = 0; virtual void OnJackServerSettingsChanged(const JackServerSettings &jackServerSettings) = 0;
@@ -188,6 +189,8 @@ namespace pipedal
AtomConverter atomConverter; // must be AFTER pluginHost! AtomConverter atomConverter; // must be AFTER pluginHost!
Pedalboard pedalboard; Pedalboard pedalboard;
ChannelRouterSettings::ptr channelRouterSettings;
bool previousPedalboardLoaded = false; bool previousPedalboardLoaded = false;
Pedalboard previousPedalboard; Pedalboard previousPedalboard;
Storage storage; Storage storage;
@@ -215,7 +218,7 @@ namespace pipedal
void FirePresetChanged(bool changed); void FirePresetChanged(bool changed);
void FirePluginPresetsChanged(const std::string &pluginUri); void FirePluginPresetsChanged(const std::string &pluginUri);
void FirePedalboardChanged(int64_t clientId, bool reloadAudioThread = true); void FirePedalboardChanged(int64_t clientId, bool reloadAudioThread = true);
void FireChannelSelectionChanged(int64_t clientId); void FireChannelRouterSettingsChanged(int64_t clientId);
void FireBanksChanged(int64_t clientId); void FireBanksChanged(int64_t clientId);
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration); void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
void FireLv2StateChanged(int64_t instanceId, const Lv2PluginState &lv2State); void FireLv2StateChanged(int64_t instanceId, const Lv2PluginState &lv2State);
@@ -424,7 +427,6 @@ namespace pipedal
JackConfiguration GetJackConfiguration(); JackConfiguration GetJackConfiguration();
void SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection);
JackChannelSelection GetJackChannelSelection(); JackChannelSelection GetJackChannelSelection();
void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration); void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration);
@@ -521,6 +523,9 @@ namespace pipedal
std::vector<PresetIndexEntry> RequestBankPresets(int64_t bankInstanceId); std::vector<PresetIndexEntry> RequestBankPresets(int64_t bankInstanceId);
int64_t ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets); int64_t ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
int64_t CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets); int64_t CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
void SetChannelRouterSettings(int64_t clientId, ChannelRouterSettings::ptr &settings);
ChannelRouterSettings::ptr GetChannelRouterSettings();
}; };
} // namespace pipedal. } // namespace pipedal.
+25 -18
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022-2023 Robin Davies // Copyright (c) 2026 Robin Davies
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy of // Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in // this software and associated documentation files (the "Software"), to deal in
@@ -594,17 +594,24 @@ JSON_MAP_REFERENCE(SnapshotModifiedBody, snapshotIndex)
JSON_MAP_REFERENCE(SnapshotModifiedBody, modified) JSON_MAP_REFERENCE(SnapshotModifiedBody, modified)
JSON_MAP_END() JSON_MAP_END()
class ChannelSelectionChangedBody class ChannelRouterSettingsChangedBody
{ {
public: public:
int64_t clientId_ = -1; ChannelRouterSettingsChangedBody(
JackChannelSelection *jackChannelSelection_ = nullptr; int64_t clientId,
const ChannelRouterSettings &channelRouterSettings
): clientId_(clientId), channelRouterSettings_(channelRouterSettings)
{
DECLARE_JSON_MAP(ChannelSelectionChangedBody); }
int64_t clientId_ = -1;
ChannelRouterSettings channelRouterSettings_;
DECLARE_JSON_MAP(ChannelRouterSettingsChangedBody);
}; };
JSON_MAP_BEGIN(ChannelSelectionChangedBody) JSON_MAP_BEGIN(ChannelRouterSettingsChangedBody)
JSON_MAP_REFERENCE(ChannelSelectionChangedBody, clientId) JSON_MAP_REFERENCE(ChannelRouterSettingsChangedBody, clientId)
JSON_MAP_REFERENCE(ChannelSelectionChangedBody, jackChannelSelection) JSON_MAP_REFERENCE(ChannelRouterSettingsChangedBody, channelRouterSettings)
JSON_MAP_END() JSON_MAP_END()
class PresetsChangedBody class PresetsChangedBody
@@ -1481,12 +1488,6 @@ public:
this->model.AddNotificationSubscription(shared_from_this()); this->model.AddNotificationSubscription(shared_from_this());
Reply(replyTo, "ehlo", clientId); Reply(replyTo, "ehlo", clientId);
} }
else if (message == "setJackSettings")
{
JackChannelSelection jackSettings;
pReader->read(&jackSettings);
this->model.SetJackChannelSelection(this->clientId, jackSettings);
}
else if (message == "setShowStatusMonitor") else if (message == "setShowStatusMonitor")
{ {
bool showStatusMonitor; bool showStatusMonitor;
@@ -1902,6 +1903,14 @@ public:
pReader->read(&args); pReader->read(&args);
auto result = this->model.CopyPresetsToBank(args.bankInstanceId_, args.presets_); auto result = this->model.CopyPresetsToBank(args.bankInstanceId_, args.presets_);
this->Reply(replyTo,"copyPresetsToBank",result); this->Reply(replyTo,"copyPresetsToBank",result);
} else if (message == "getChannelRouterSettings")
{
ChannelRouterSettings::ptr result = this->model.GetChannelRouterSettings();
this->Reply(replyTo, "getChannelRouterSettings", result);
} else if (message == "setChannelRouterSettings") {
ChannelRouterSettings::ptr args;
pReader->read(&args);
this->model.SetChannelRouterSettings(this->clientId, args);
} }
else if (message == "downloadModelsFromTone3000") else if (message == "downloadModelsFromTone3000")
{ {
@@ -2100,11 +2109,9 @@ private:
Send("onShowStatusMonitorChanged", show); Send("onShowStatusMonitorChanged", show);
} }
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) virtual void OnChannelRouterSettingsChanged(int64_t clientId, const ChannelRouterSettings &channelRouterSettings)
{ {
ChannelSelectionChangedBody body; ChannelRouterSettingsChangedBody body(clientId, channelRouterSettings);
body.clientId_ = clientId;
body.jackChannelSelection_ = const_cast<JackChannelSelection *>(&channelSelection);
Send("onChannelSelectionChanged", body); Send("onChannelSelectionChanged", body);
} }
+4 -3
View File
@@ -34,6 +34,7 @@
#include "lv2/urid/urid.h" #include "lv2/urid/urid.h"
#include "lv2/ui/ui.h" #include "lv2/ui/ui.h"
#include "lv2/core/lv2.h" #include "lv2/core/lv2.h"
#include "ChannelRouterSettings.hpp"
// #include "lv2.h" // #include "lv2.h"
#include "lv2/atom/atom.h" #include "lv2/atom/atom.h"
@@ -315,15 +316,15 @@ PluginHost::PluginHost()
this->urids = new Urids(mapFeature); this->urids = new Urids(mapFeature);
} }
void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings)
void PluginHost::OnConfigurationChanged(const JackConfiguration &configuration, const ChannelSelection &channelSelection)
{ {
this->sampleRate = configuration.sampleRate(); this->sampleRate = configuration.sampleRate();
if (configuration.isValid()) if (configuration.isValid())
{ {
this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size();
this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size();
this->maxBufferSize = configuration.blockLength(); this->maxBufferSize = configuration.blockLength();
} }
this->channelSelection = channelSelection;
} }
PluginHost::~PluginHost() PluginHost::~PluginHost()
+12 -10
View File
@@ -43,6 +43,7 @@
#include "PiPedalUI.hpp" #include "PiPedalUI.hpp"
#include "MapPathFeature.hpp" #include "MapPathFeature.hpp"
#include "ModGui.hpp" #include "ModGui.hpp"
#include "ChannelRouterSettings.hpp"
namespace pipedal namespace pipedal
{ {
@@ -54,6 +55,7 @@ namespace pipedal
class PluginHost; class PluginHost;
class JackConfiguration; class JackConfiguration;
class JackChannelSelection; class JackChannelSelection;
class ChannelRouterSettings;
#ifndef LV2_PROPERTY_GETSET #ifndef LV2_PROPERTY_GETSET
#define LV2_PROPERTY_GETSET(name) \ #define LV2_PROPERTY_GETSET(name) \
@@ -893,8 +895,8 @@ namespace pipedal
size_t maxBufferSize = 1024; size_t maxBufferSize = 1024;
size_t maxAtomBufferSize = 16 * 1024; size_t maxAtomBufferSize = 16 * 1024;
bool hasMidiInputChannel; bool hasMidiInputChannel;
int numberOfAudioInputChannels = 1; ChannelSelection channelSelection;
int numberOfAudioOutputChannels = 1;
double sampleRate = 48000; double sampleRate = 48000;
std::string vst3CachePath; std::string vst3CachePath;
@@ -955,13 +957,12 @@ namespace pipedal
private: private:
// IHost implementation. // IHost implementation.
virtual void SetMaxAudioBufferSize(size_t size) { maxBufferSize = size; } virtual void SetMaxAudioBufferSize(size_t size) override { maxBufferSize = size; }
virtual size_t GetMaxAudioBufferSize() const { return maxBufferSize; } virtual size_t GetMaxAudioBufferSize() const override { return maxBufferSize; }
virtual size_t GetAtomBufferSize() const { return maxAtomBufferSize; } virtual size_t GetAtomBufferSize() const override { return maxAtomBufferSize; }
virtual bool HasMidiInputChannel() const { return hasMidiInputChannel; } virtual bool HasMidiInputChannel() const override { return hasMidiInputChannel; }
virtual int GetNumberOfInputAudioChannels() const { return numberOfAudioInputChannels; } virtual LV2_Feature *const *GetLv2Features() const override { return (LV2_Feature *const *)&(this->lv2Features[0]); }
virtual int GetNumberOfOutputAudioChannels() const { return numberOfAudioOutputChannels; } virtual const ChannelSelection &GetChannelSelection() const override { return this->channelSelection; }
virtual LV2_Feature *const *GetLv2Features() const { return (LV2_Feature *const *)&(this->lv2Features[0]); }
public: public:
virtual MapFeature &GetMapFeature() override { return this->mapFeature; } virtual MapFeature &GetMapFeature() override { return this->mapFeature; }
@@ -1027,7 +1028,8 @@ namespace pipedal
Urids *urids = nullptr; Urids *urids = nullptr;
ModGuiUris *mod_gui_uris = nullptr; ModGuiUris *mod_gui_uris = nullptr;
void OnConfigurationChanged(const JackConfiguration &configuration, const JackChannelSelection &settings); void OnConfigurationChanged(const JackConfiguration &configuration, const ChannelSelection &channelSelection);
std::shared_ptr<Lv2PluginClass> GetPluginClass(const std::string &uri) const; std::shared_ptr<Lv2PluginClass> GetPluginClass(const std::string &uri) const;
bool is_a(const std::string &class_, const std::string &target_class); bool is_a(const std::string &class_, const std::string &target_class);
+19 -1
View File
@@ -96,6 +96,24 @@ namespace pipedal
}; };
struct RealtimePedalboardItemIndex {
RealtimePedalboardItemIndex()
: instanceType(Pedalboard::PedalboardType::MainPedalboard), index(-1)
{
}
RealtimePedalboardItemIndex(
Pedalboard::PedalboardType instanceType,
int64_t index)
: instanceType(instanceType), index(index)
{
}
RealtimePedalboardItemIndex(const RealtimePedalboardItemIndex& other) = default;
RealtimePedalboardItemIndex& operator=(const RealtimePedalboardItemIndex& other) = default;
Pedalboard::PedalboardType instanceType = Pedalboard::PedalboardType::MainPedalboard;
int64_t index = -1;
};
struct RealtimeMidiEventRequest struct RealtimeMidiEventRequest
{ {
RealtimeMidiEventType eventType; RealtimeMidiEventType eventType;
@@ -159,7 +177,7 @@ namespace pipedal
return &vuUpdateResponseData; return &vuUpdateResponseData;
} }
std::vector<int> enabledIndexes; std::vector<RealtimePedalboardItemIndex> enabledIndexes;
std::vector<VuUpdate> vuUpdateWorkingData; std::vector<VuUpdate> vuUpdateWorkingData;
std::vector<VuUpdate> vuUpdateResponseData; std::vector<VuUpdate> vuUpdateResponseData;
+1 -2
View File
@@ -19,13 +19,12 @@
#pragma once #pragma once
#include "lilv/lilv.h"
#include "lv2/state/state.h" #include "lv2/state/state.h"
#include <cstddef> #include <cstddef>
#include "json_variant.hpp" #include "json_variant.hpp"
#include "MapFeature.hpp" #include "MapFeature.hpp"
#include "IHost.hpp" #include "IHost.hpp"
#include "lilv/lilv.h" #include <lilv/lilv.h>
namespace pipedal namespace pipedal
{ {
+187 -49
View File
@@ -197,11 +197,11 @@ void Storage::SetDataRoot(const std::filesystem::path &path)
this->dataRoot__audio_uploads = dataRoot / "audio_uploads"; this->dataRoot__audio_uploads = dataRoot / "audio_uploads";
} }
const std::filesystem::path &Storage::GetConfigRoot() const std::filesystem::path &Storage::GetConfigRoot() const
{ {
return this->configRoot; return this->configRoot;
} }
const std::filesystem::path &Storage::GetDataRoot() const std::filesystem::path &Storage::GetDataRoot() const
{ {
return this->dataRoot; return this->dataRoot;
} }
@@ -250,7 +250,6 @@ static void removeFileNoThrow(const std::filesystem::path &path)
} }
} }
void Storage::UpgradeFactoryPresets() void Storage::UpgradeFactoryPresets()
{ {
auto presetsDirectory = this->GetPresetsDirectory(); auto presetsDirectory = this->GetPresetsDirectory();
@@ -378,7 +377,7 @@ void Storage::Initialize()
LoadCurrentBank(); LoadCurrentBank();
try try
{ {
LoadChannelSelection(); LoadJackChannelSelection();
} }
catch (const std::exception &) catch (const std::exception &)
{ {
@@ -387,8 +386,10 @@ void Storage::Initialize()
LoadWifiConfigSettings(); LoadWifiConfigSettings();
LoadWifiDirectConfigSettings(); LoadWifiDirectConfigSettings();
this->channelRouterSettings = LoadChannelRouterSettings();
LoadUserSettings(); LoadUserSettings();
UpgradeFactoryPresets(); UpgradeFactoryPresets();
UpgradeChannelRouterSettings();
} }
void Storage::LoadBank(int64_t instanceId) void Storage::LoadBank(int64_t instanceId)
@@ -471,7 +472,7 @@ void Storage::LoadBankIndex()
if (bankIndex.entries().size() == 0) if (bankIndex.entries().size() == 0)
{ {
currentBank.clear(); currentBank.clear();
Pedalboard defaultPedalboard = Pedalboard::MakeDefault(); Pedalboard defaultPedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard);
int64_t instanceId = currentBank.addPreset(defaultPedalboard); int64_t instanceId = currentBank.addPreset(defaultPedalboard);
currentBank.selectedPreset(instanceId); currentBank.selectedPreset(instanceId);
@@ -683,40 +684,46 @@ int64_t Storage::SaveCurrentPresetAs(const Pedalboard &pedalboard, int64_t bankI
} }
} }
static std::string stripNumericSuffix(const std::string &name) { static std::string stripNumericSuffix(const std::string &name)
{
// remove (digit*) from the end of the string. // remove (digit*) from the end of the string.
// Find the last '(' character // Find the last '(' character
size_t pos = name.find_last_of('('); size_t pos = name.find_last_of('(');
if (pos == std::string::npos) { if (pos == std::string::npos)
{
return name; return name;
} }
// Check if everything between '(' and ')' is digits // Check if everything between '(' and ')' is digits
size_t len = name.length()-1; size_t len = name.length() - 1;
if (name[len] != ')') if (name[len] != ')')
{ {
return name; return name;
} }
bool allDigits = true; bool allDigits = true;
for (size_t i = pos + 1; i < len - 1; ++i) { for (size_t i = pos + 1; i < len - 1; ++i)
if (name[i] < '0' || name[i] > '9') { {
if (name[i] < '0' || name[i] > '9')
{
allDigits = false; allDigits = false;
break; break;
} }
} }
if (!allDigits) { if (!allDigits)
{
return name; return name;
} }
// Remove trailing spaces before the '(' // Remove trailing spaces before the '('
while (pos > 0 && name[pos - 1] == ' ') { while (pos > 0 && name[pos - 1] == ' ')
{
--pos; --pos;
} }
return name.substr(0, pos); return name.substr(0, pos);
} }
static std::string makeUniqueName(const std::string &name, const std::set<std::string>&existingNames) static std::string makeUniqueName(const std::string &name, const std::set<std::string> &existingNames)
{ {
if (!existingNames.contains(name)) if (!existingNames.contains(name))
{ {
@@ -725,9 +732,11 @@ static std::string makeUniqueName(const std::string &name, const std::set<std::s
std::string baseName = stripNumericSuffix(name); std::string baseName = stripNumericSuffix(name);
size_t i = 2; size_t i = 2;
while (true) { while (true)
{
std::string newName = SS(baseName << " (" << i << ")"); std::string newName = SS(baseName << " (" << i << ")");
if (!existingNames.contains(newName)) { if (!existingNames.contains(newName))
{
return newName; return newName;
} }
++i; ++i;
@@ -735,23 +744,27 @@ static std::string makeUniqueName(const std::string &name, const std::set<std::s
} }
int64_t Storage::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets) int64_t Storage::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
{ {
if (bankIndex.selectedBank() == bankInstanceId) { if (bankIndex.selectedBank() == bankInstanceId)
{
throw std::runtime_error("Can't import to self."); throw std::runtime_error("Can't import to self.");
} }
std::set<int64_t> presetsSet { presets.begin(), presets.end()}; std::set<int64_t> presetsSet{presets.begin(), presets.end()};
auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId); auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId);
std::set<std::string> existingNames; std::set<std::string> existingNames;
for (auto&preset: this->currentBank.presets()) { for (auto &preset : this->currentBank.presets())
{
existingNames.insert(preset->preset().name()); existingNames.insert(preset->preset().name());
} }
BankFile bankFile; BankFile bankFile;
LoadBankFile(indexEntry.name(),&bankFile); LoadBankFile(indexEntry.name(), &bankFile);
int64_t lastPresetId = -1; int64_t lastPresetId = -1;
for (auto &presetEntry: bankFile.presets()) { for (auto &presetEntry : bankFile.presets())
if (presetsSet.contains(presetEntry->instanceId())) { {
std::string uniqueName = makeUniqueName(presetEntry->preset().name(),existingNames); if (presetsSet.contains(presetEntry->instanceId()))
{
std::string uniqueName = makeUniqueName(presetEntry->preset().name(), existingNames);
existingNames.insert(uniqueName); existingNames.insert(uniqueName);
Pedalboard t = presetEntry->preset(); Pedalboard t = presetEntry->preset();
t.name(uniqueName); t.name(uniqueName);
@@ -763,31 +776,35 @@ int64_t Storage::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector
} }
int64_t Storage::CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets) int64_t Storage::CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
{ {
if (bankIndex.selectedBank() == bankInstanceId) { if (bankIndex.selectedBank() == bankInstanceId)
{
throw std::runtime_error("Can't copy to self."); throw std::runtime_error("Can't copy to self.");
} }
auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId); auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId);
BankFile bankFile; BankFile bankFile;
LoadBankFile(indexEntry.name(),&bankFile); LoadBankFile(indexEntry.name(), &bankFile);
std::set<int64_t> presetsSet { presets.begin(), presets.end()}; std::set<int64_t> presetsSet{presets.begin(), presets.end()};
std::set<std::string> existingNames; std::set<std::string> existingNames;
for (auto&preset: bankFile.presets()) { for (auto &preset : bankFile.presets())
{
existingNames.insert(preset->preset().name()); existingNames.insert(preset->preset().name());
} }
for (auto &presetEntry: this->currentBank.presets()) { for (auto &presetEntry : this->currentBank.presets())
if (presetsSet.contains(presetEntry->instanceId())) { {
std::string uniqueName = makeUniqueName(presetEntry->preset().name(),existingNames); if (presetsSet.contains(presetEntry->instanceId()))
{
std::string uniqueName = makeUniqueName(presetEntry->preset().name(), existingNames);
existingNames.insert(uniqueName); existingNames.insert(uniqueName);
Pedalboard t = presetEntry->preset(); Pedalboard t = presetEntry->preset();
t.name(uniqueName); t.name(uniqueName);
bankFile.addPreset(t); bankFile.addPreset(t);
} }
} }
SaveBankFile(indexEntry.name(),bankFile); SaveBankFile(indexEntry.name(), bankFile);
return -1; return -1;
} }
@@ -797,17 +814,15 @@ std::vector<PresetIndexEntry> Storage::RequestBankPresets(int64_t bankInstanceId
std::vector<PresetIndexEntry> result; std::vector<PresetIndexEntry> result;
BankFile bankFile; BankFile bankFile;
LoadBankFile(indexEntry.name(),&bankFile); LoadBankFile(indexEntry.name(), &bankFile);
for (auto &preset: bankFile.presets()) { for (auto &preset : bankFile.presets())
{
result.push_back( result.push_back(
PresetIndexEntry(preset->instanceId(),preset->preset().name()) PresetIndexEntry(preset->instanceId(), preset->preset().name()));
);
} }
return result; return result;
} }
void Storage::SetPresetIndex(const PresetIndex &presets) void Storage::SetPresetIndex(const PresetIndex &presets)
{ {
// painful because we must move unique_ptrs. // painful because we must move unique_ptrs.
@@ -881,7 +896,8 @@ Pedalboard Storage::GetPreset(int64_t instanceId) const
int64_t Storage::DeletePresets(const std::vector<int64_t> &presetInstanceIds) int64_t Storage::DeletePresets(const std::vector<int64_t> &presetInstanceIds)
{ {
int64_t newSelection = currentBank.selectedPreset(); int64_t newSelection = currentBank.selectedPreset();
for (auto presetId: presetInstanceIds) { for (auto presetId : presetInstanceIds)
{
newSelection = currentBank.deletePreset(presetId); newSelection = currentBank.deletePreset(presetId);
} }
SaveCurrentBank(); SaveCurrentBank();
@@ -973,7 +989,7 @@ int64_t Storage::CreateNewPreset()
} }
} }
newPedalboard.name(name); newPedalboard.name(name);
auto t = this->currentBank.addPreset(newPedalboard, -1); auto t = this->currentBank.addPreset(newPedalboard, -1);
SaveCurrentBank(); SaveCurrentBank();
return t; return t;
} }
@@ -993,7 +1009,7 @@ int64_t Storage::CopyPreset(int64_t fromId, int64_t toId)
{ {
auto &toItem = this->currentBank.getItem(toId); auto &toItem = this->currentBank.getItem(toId);
toItem.preset(fromItem.preset()); toItem.preset(fromItem.preset());
result = toId; result = toId;
} }
SaveCurrentBank(); SaveCurrentBank();
return result; return result;
@@ -1115,7 +1131,7 @@ AlsaSequencerConfiguration Storage::GetAlsaSequencerConfiguration() const
return this->alsaSequencerConfiguration; return this->alsaSequencerConfiguration;
} }
void Storage::LoadChannelSelection() void Storage::LoadJackChannelSelection()
{ {
auto fileName = this->GetChannelSelectionFileName(); auto fileName = this->GetChannelSelectionFileName();
if (std::filesystem::exists(fileName)) if (std::filesystem::exists(fileName))
@@ -2071,21 +2087,21 @@ static bool ensureNoDotDot(const std::filesystem::path &path)
return true; return true;
} }
static bool isInfoFile(const std::string&fileName) static bool isInfoFile(const std::string &fileName)
{ {
if (strcasecmp(fileName.c_str(),"license.txt") == 0) if (strcasecmp(fileName.c_str(), "license.txt") == 0)
{ {
return true; return true;
} }
if (strcasecmp(fileName.c_str(),"license.md") == 0) if (strcasecmp(fileName.c_str(), "license.md") == 0)
{ {
return true; return true;
} }
if (strcasecmp(fileName.c_str(),"readme.txt") == 0) if (strcasecmp(fileName.c_str(), "readme.txt") == 0)
{ {
return true; return true;
} }
if (strcasecmp(fileName.c_str(),"readme.md") == 0) if (strcasecmp(fileName.c_str(), "readme.md") == 0)
{ {
return true; return true;
} }
@@ -2983,11 +2999,13 @@ const PluginPresetIndex &Storage::GetPluginPresetIndex()
std::filesystem::path Storage::FromAbstractPathString(const std::string &stringPath) std::filesystem::path Storage::FromAbstractPathString(const std::string &stringPath)
{ {
if (stringPath.empty()) { if (stringPath.empty())
{
return ""; return "";
} }
fs::path path { stringPath}; fs::path path{stringPath};
if (path.is_absolute()) { if (path.is_absolute())
{
return path; return path;
} }
return GetPluginUploadDirectory() / path; return GetPluginUploadDirectory() / path;
@@ -3010,6 +3028,126 @@ std::string Storage::FromAbstractPathJson(const std::string &pathJson)
return v.to_string(); return v.to_string();
} }
void Storage::SetChannelRouterSettings(ChannelRouterSettings::ptr settings)
{
this->channelRouterSettings = settings;
this->channelSelection = ChannelSelection(*settings);
SaveChannelRouterSettings(settings);
}
ChannelRouterSettings::ptr Storage::GetChannelRouterSettings()
{
return channelRouterSettings;
}
std::filesystem::path Storage::GetChannelRouterSettingsPath() const
{
return GetDataRoot() / "ChannelRouterSettings.json";
}
void Storage::SaveChannelRouterSettings(ChannelRouterSettings::ptr settings)
{
std::ofstream os(GetChannelRouterSettingsPath());
json_writer writer(os);
writer.write(settings);
}
ChannelRouterSettings::ptr Storage::LoadChannelRouterSettings()
{
auto path = GetChannelRouterSettingsPath();
if (!fs::exists(path))
{
// See UpgradeChannelRouterSettings(), which will upgrade from old settings
// or create a default instance.
return nullptr; // will be upgraded later.
}
try {
std::ifstream is(path);
json_reader reader(is);
ChannelRouterSettings::ptr result;
reader.read(&result);
this->channelSelection = ChannelSelection(*result);
return result;
} catch (const std::exception &e) {
Lv2Log::error("Failed to load Channel Router settings: %s", e.what());
return std::make_shared<ChannelRouterSettings>();
}
}
static int64_t GetUpgradedPortIndex(const std::string &portName)
{
auto nPos = portName.find_last_of('_');
if (nPos != std::string::npos)
{
std::string channelStr = portName.substr(nPos + 1);
try
{
int64_t channelIndex = std::stoll(channelStr);
return channelIndex;
} catch (const std::exception &) {
return -1;
}
}
return -1;
}
void Storage::UpgradeChannelRouterSettings()
{
if (channelRouterSettings == nullptr)
{
channelRouterSettings = std::make_shared<ChannelRouterSettings>();
if (jackChannelSelection.isValid())
{
channelRouterSettings->configured(true);
channelRouterSettings->mainInputChannels().resize(2);
const std::vector<std::string>& oldInputs = jackChannelSelection.GetInputAudioPorts();
std::vector<int64_t>& newInputs = channelRouterSettings->mainInputChannels();
if (oldInputs.size() ==1)
{
int64_t leftIndex = GetUpgradedPortIndex(oldInputs[0]);
newInputs.resize(2);
newInputs[0] = leftIndex;
newInputs[1] = leftIndex;
} else if (oldInputs.size() == 2) {
newInputs.resize(2);
for (size_t i = 0; i < std::min(oldInputs.size(), newInputs.size()); ++i)
{
int64_t portIndex = GetUpgradedPortIndex(oldInputs[i]);
newInputs[i] = portIndex;
}
}
const std::vector<std::string>& oldOutputs = jackChannelSelection.GetOutputAudioPorts();
auto &newMainOutputs = channelRouterSettings->mainOutputChannels();
if (oldOutputs.size() == 1) {
int64_t leftIndex = GetUpgradedPortIndex(oldOutputs[0]);
newMainOutputs.resize(2);
newMainOutputs[0] = leftIndex;
newMainOutputs[1] = leftIndex;
} else if (oldOutputs.size() == 2) {
newMainOutputs.resize(2);
for (size_t i = 0; i < std::min(oldOutputs.size(), newMainOutputs.size()); ++i)
{
int64_t portIndex = GetUpgradedPortIndex(oldOutputs[i]);
newMainOutputs[i] = portIndex;
}
}
auto &newAuxInputs = channelRouterSettings->auxInputChannels();
newAuxInputs.resize(2);
newAuxInputs[0] = -1;
newAuxInputs[1] = -1;
auto &newAuxOutputs = channelRouterSettings->auxOutputChannels();
newAuxOutputs.resize(2);
newAuxOutputs[0] = -1;
newAuxOutputs[1] = -1;
SaveChannelRouterSettings(channelRouterSettings);
}
channelSelection = ChannelSelection(*channelRouterSettings);
}
}
const ChannelSelection& Storage::GetChannelSelection() const {
return channelSelection;
}
JSON_MAP_BEGIN(UserSettings) JSON_MAP_BEGIN(UserSettings)
JSON_MAP_REFERENCE(UserSettings, governor) JSON_MAP_REFERENCE(UserSettings, governor)
+19 -3
View File
@@ -32,6 +32,8 @@
#include <map> #include <map>
#include "FilePropertyDirectoryTree.hpp" #include "FilePropertyDirectoryTree.hpp"
#include "AlsaSequencer.hpp" #include "AlsaSequencer.hpp"
#include "ChannelRouterSettings.hpp"
namespace pipedal { namespace pipedal {
@@ -96,7 +98,7 @@ private:
void LoadCurrentBank(); void LoadCurrentBank();
void SaveCurrentBank(); void SaveCurrentBank();
void LoadChannelSelection(); void LoadJackChannelSelection();
void SaveChannelSelection(); void SaveChannelSelection();
void LoadAlsaSequencerConfiguration(); void LoadAlsaSequencerConfiguration();
@@ -115,6 +117,15 @@ private:
WifiDirectConfigSettings wifiDirectConfigSettings; WifiDirectConfigSettings wifiDirectConfigSettings;
UserSettings userSettings; UserSettings userSettings;
ChannelRouterSettings::ptr channelRouterSettings;
ChannelSelection channelSelection;
void UpgradeChannelRouterSettings();
std::filesystem::path GetChannelRouterSettingsPath() const;
void SaveChannelRouterSettings(ChannelRouterSettings::ptr settings);
ChannelRouterSettings::ptr LoadChannelRouterSettings();
public: public:
Storage(); Storage();
void Initialize(); void Initialize();
@@ -122,8 +133,8 @@ public:
void SetDataRoot(const std::filesystem::path& path); void SetDataRoot(const std::filesystem::path& path);
void SetConfigRoot(const std::filesystem::path& path); void SetConfigRoot(const std::filesystem::path& path);
const std::filesystem::path&GetConfigRoot(); const std::filesystem::path&GetConfigRoot() const;
const std::filesystem::path&GetDataRoot(); const std::filesystem::path&GetDataRoot() const;
const std::filesystem::path &GetPluginUploadDirectory() const; const std::filesystem::path &GetPluginUploadDirectory() const;
@@ -204,6 +215,11 @@ public:
void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration); void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration);
AlsaSequencerConfiguration GetAlsaSequencerConfiguration() const; AlsaSequencerConfiguration GetAlsaSequencerConfiguration() const;
void SetChannelRouterSettings(ChannelRouterSettings::ptr settings);
ChannelRouterSettings::ptr GetChannelRouterSettings();
const ChannelSelection& GetChannelSelection() const;
//std::string MapPropertyFileName(Lv2PluginInfo*pluginInfo, const std::string&path); //std::string MapPropertyFileName(Lv2PluginInfo*pluginInfo, const std::string&path);
+1
View File
@@ -1,3 +1,4 @@
A way to force input to mono?
Cab I/Rs: gear="ir" Cab I/Rs: gear="ir"
// yyy: only if the property changed!. // yyy: only if the property changed!.
+181
View File
@@ -0,0 +1,181 @@
import { Pedalboard, ControlValue } from "./Pedalboard.tsx";
import JackServerSettings from "./JackServerSettings.tsx";
import JackConfiguration from "./Jack.tsx";
function isActiveChannel(channels: number[]): boolean {
if (channels.length < 2) return false;
return channels[1] >= 0 || channels[0] >= 0;
}
function chName(ch: number): string {
if (ch === -1) {
return "None";
}
return "Ch " + (ch + 1);
}
function channelPairName(channels: number[], maxChannels: number): string {
if (channels.length !== 2) {
return "Invalid";
}
if (channels[0] === -1 && channels[1] === -1) {
return "None";
}
if (maxChannels === 2) {
if (channels[0] === 0 && channels[1] === 1) {
return "Stereo";
}
if (channels[0] === 0 && (channels[0] === channels[1] || channels[1] === -1)) {
return "Left";
}
if (channels[1] === 1 && (channels[0] === channels[1])) {
return "Right";
}
if (channels[0] === 1 && channels[1] === 0) {
return "Right,Left";
}
}
return chName(channels[0]) + "," + chName(channels[1]);
}
export default class ChannelRouterSettings {
configured: boolean = false;
channelRouterPresetId: number = -1;
mainInputChannels: number[] = [1, 1];
mainOutputChannels: number[] = [0, 1];
mainInserts: Pedalboard = new Pedalboard();
auxInputChannels: number[] = [0, 0];
auxOutputChannels: number[] = [-1, -1];
auxInserts: Pedalboard = new Pedalboard();
sendInputChannels: number[] = [-1, -1];
sendOutputChannels: number[] = [-1, -1];
controlValues: ControlValue[] = [];
// Inserts...
deserialize(obj: any): ChannelRouterSettings {
this.configured = obj.configured;
this.channelRouterPresetId = obj.channelRouterPresetId ? obj.channelRouterPresetId : -1;
this.mainInputChannels = obj.mainInputChannels.slice();
this.mainOutputChannels = obj.mainOutputChannels.slice();
this.mainInserts = new Pedalboard().deserialize(obj.mainInserts);
this.auxInputChannels = obj.auxInputChannels.slice();
this.auxOutputChannels = obj.auxOutputChannels.slice();
this.auxInserts = new Pedalboard().deserialize(obj.auxInserts);
this.sendInputChannels = obj.sendInputChannels.slice();
this.sendOutputChannels = obj.sendOutputChannels.slice();
this.controlValues = ControlValue.deserializeArray(obj.controlValues);
return this;
}
getControlValue(symbol: string): number | null {
for (let control of this.controlValues) {
if (control.key === symbol) {
return control.value;
}
}
return null;
}
setControlValue(symbol: string, value: number): boolean {
for (let control of this.controlValues) {
if (control.key === symbol) {
if (control.value === value) {
return false;
}
control.value = value;
this.controlValues = this.controlValues.slice(); // trigger observers.
return true;
}
}
let newValue = new ControlValue(symbol, value);
this.controlValues.push(newValue);
this.controlValues = this.controlValues.slice(); // trigger observers.
return true;
}
getDescription(jackConfiguration: JackConfiguration): string {
if (!this.configured) {
return "Not configured";
}
if (!jackConfiguration.isValid)
{
return "Not configured"
}
if (!this.isValid(jackConfiguration)) {
return "Invalid configuration";
}
let nInputs = jackConfiguration.inputAudioPorts.length;
let nOutputs = jackConfiguration.outputAudioPorts.length;
let description = channelPairName(this.mainInputChannels, nInputs) + " -> " +
channelPairName(this.mainOutputChannels, nOutputs);
if (isActiveChannel(this.auxInputChannels) && isActiveChannel(this.auxOutputChannels))
{
description += " " + "+ re-amp: " + channelPairName(this.auxInputChannels, nInputs) + " -> " +
channelPairName(this.auxOutputChannels, nOutputs);
}
if (isActiveChannel(this.sendInputChannels) && isActiveChannel(this.sendOutputChannels)) {
description += " " + "+ send: " +channelPairName(this.sendOutputChannels, nOutputs)
+ " -> " + channelPairName(this.sendInputChannels, nInputs);
}
return description;
}
canEdit(jackConfiguration: JackConfiguration): boolean {
return jackConfiguration.isValid;
}
isValid(jackConfiguration: JackConfiguration): boolean {
if (!this.configured) {
return false;
}
let maxInputChannels = jackConfiguration.inputAudioPorts.length;
let maxOutputChannels = jackConfiguration.outputAudioPorts.length;
let hasInput = false;
let hasOutput = false;
for (let ch of this.mainInputChannels) {
if (ch >= 0) {
hasInput = true;
if (ch >= maxInputChannels) {
return false;
}
}
}
if (!hasInput) {
return false;
}
for (let ch of this.mainOutputChannels) {
if (ch >= 0) {
hasOutput = true;
if (ch >= maxOutputChannels) {
return false;
}
}
}
if (!hasOutput) {
return false;
}
for (let ch of this.auxInputChannels) {
if (ch >= maxInputChannels) {
return false;
}
}
for (let ch of this.auxOutputChannels) {
if (ch >= maxOutputChannels) {
return false;
}
}
for (let ch of this.sendInputChannels) {
if (ch >= maxInputChannels) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,868 @@
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import React from 'react';
import { useState } from 'react';
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
import Divider from '@mui/material/Divider';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import IconButtonEx from './IconButtonEx';
import Button from '@mui/material/Button';
import useWindowSize from "./UseWindowSize";
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import ChannelRouterSettingsHelpDialog from './ChannelRouterSettingsHelpDialog';
import SpeakerIcon from '@mui/icons-material/Speaker';
import MicIcon from '@mui/icons-material/Mic';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import Typography from '@mui/material/Typography';
import ChannelRouterSettings from './ChannelRouterSettings';
import { Pedalboard, ControlValue } from './Pedalboard';
import DialogEx from './DialogEx';
import { PiPedalModelFactory } from './PiPedalModel';
let debugInputChannels: number | null = 2;
let debugOutputChannels: number | null = 4;
export interface ChannelRouterSettingsDialogProps {
open: boolean;
onClose: () => void;
}
enum RouteType {
Main,
Aux,
Send
}
interface ChannelRouterSettingsPreset {
id: number;
name: string;
settings: ChannelRouterSettings;
}
let LEFT_LEFT_PRESET_ID = -2;
let RIGHT_STEREO_PRESET_ID = -3;
let channelRouterPresets: ChannelRouterSettingsPreset[] = [
{
id: -2, name: "Left -> Left", settings: new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -2,
mainInputChannels: [0, 0],
mainOutputChannels: [0, 0],
mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1],
auxOutputChannels: [-1, -1],
auxInserts: new Pedalboard(),
sendInputChannels: [-1, -1],
sendOutputChannels: [-1, -1],
controlValues: [],
})
},
{
id: -3, name: "Left -> Stereo", settings: new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -3,
mainInputChannels: [0, 0],
mainOutputChannels: [0, 1],
mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1],
auxOutputChannels: [-1, -1],
auxInserts: new Pedalboard(),
sendInputChannels: [-1, -1],
sendOutputChannels: [-1, -1],
controlValues: [],
})
},
{
id: -4, name: "Right -> Stereo", settings: new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -4,
mainInputChannels: [1, 1],
mainOutputChannels: [0, 1],
mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1],
auxOutputChannels: [-1, -1],
auxInserts: new Pedalboard(),
sendInputChannels: [-1, -1],
sendOutputChannels: [-1, -1],
controlValues: [],
})
},
{
id: -5, name: "Right -> Left + re-amp", settings: new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -5,
mainInputChannels: [1, 1],
mainOutputChannels: [0, 0],
mainInserts: new Pedalboard(),
auxInputChannels: [1, 1],
auxOutputChannels: [1, 1],
auxInserts: new Pedalboard(),
sendInputChannels: [-1, - 1],
sendOutputChannels: [-1, -1],
controlValues: [],
})
},
{
id: -6, name: "Left -> Left + send", settings: new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -6,
mainInputChannels: [0, 0],
mainOutputChannels: [0, 0],
mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1],
auxOutputChannels: [-1, - 1],
auxInserts: new Pedalboard(),
sendInputChannels: [1, 1],
sendOutputChannels: [1, 1],
controlValues: [],
})
},
{
id: -7, name: "Right -> Right + send", settings: new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -7,
mainInputChannels: [1, 1],
mainOutputChannels: [1, 1],
mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1],
auxOutputChannels: [-1, - 1],
auxInserts: new Pedalboard(),
sendInputChannels: [0, 0],
sendOutputChannels: [0, 0],
controlValues: [],
})
},
{
id: -8, name: "Left -> Stereo + re-amp", settings: new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -8,
mainInputChannels: [0, 0],
mainOutputChannels: [0, 1],
mainInserts: new Pedalboard(),
auxInputChannels: [0, 0],
auxOutputChannels: [2, 2],
auxInserts: new Pedalboard(),
sendInputChannels: [-1, -1],
sendOutputChannels: [-1, -1],
controlValues: [],
})
},
{
id: -9, name: "Left -> Stereo + stereo send", settings: new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -9,
mainInputChannels: [0, 0],
mainOutputChannels: [0, 1],
mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1],
auxOutputChannels: [-1, - 1],
auxInserts: new Pedalboard(),
sendInputChannels: [2, 3],
sendOutputChannels: [2, 3],
controlValues: [],
})
},
];
let cellPortraitInOutDiv: React.CSSProperties = {
width: 80, display: "flex", columnGap: 4, flexDirection: "row", alignItems: "center", justifyContent: "right",
};
let cellPortraitSectionHead: React.CSSProperties = {
border: "0px",
paddingTop: 4,
paddingBottom: 4,
paddingLeft: 0,
paddingRight: 12,
width: 50,
margin: "0px",
textAlign: "left",
verticalAlign: "middle",
};
let cellPortraitControlStrip: React.CSSProperties = {
border: "0px",
paddingTop: 12,
paddingBottom: 12,
paddingLeft: 0,
paddingRight: 0,
margin: "0px",
textAlign: "left",
verticalAlign: "middle"
};
let cellLeft: React.CSSProperties = {
border: "0px",
paddingTop: 4,
paddingBottom: 4,
paddingLeft: 4,
paddingRight: 12,
margin: "0px",
textAlign: "left",
whiteSpace: "nowrap",
verticalAlign: "middle"
};
let cellLandscapeTitle: React.CSSProperties = {
border: "0px",
paddingTop: 4,
paddingBottom: 4,
paddingLeft: 4,
paddingRight: 20,
margin: "0px",
textAlign: "left",
verticalAlign: "middle"
};
let cellPortraitInOut: React.CSSProperties = {
border: "0px",
paddingTop: 4,
paddingBottom: 4,
paddingLeft: 12,
paddingRight: 12,
margin: "0px",
width: 80,
textAlign: "right",
verticalAlign: "middle"
};
function MakeChannelMenu(channelCount: number): React.ReactElement[] {
let items: React.ReactElement[] = [(<MenuItem key={-1} value={-1}>None</MenuItem>)]
if (channelCount === 0) {
return items;
}
if (channelCount === 2) {
items.push((<MenuItem key={0} value={0}>Left</MenuItem>));
items.push((<MenuItem key={1} value={1}>Right</MenuItem>));
return items;
}
for (let i = 0; i < channelCount; ++i) {
items.push((<MenuItem key={i} value={i}>Ch {i + 1}</MenuItem>));
}
return items;
}
function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
//const classes = useStyles();
const { open, onClose } = props;
let model = PiPedalModelFactory.getInstance();
let config = model.jackConfiguration.get();
const [settings, setSettings] = useState<ChannelRouterSettings>(model.channelRouterSettings.get());
const [controlValues, setControlValues] = useState<ControlValue[]>(model.channelRouterControlValues.get());
const [showHelp, setShowHelp] = useState<boolean>(false);
if (settings) {
}
if (controlValues) {
}
React.useEffect(() => {
if (open) {
let handleSettingsChanged = () => {
setSettings(model.channelRouterSettings.get());
};
model.channelRouterSettings.addOnChangedHandler(handleSettingsChanged);
let handleControlsChanged = () => {
setControlValues(model.channelRouterControlValues.get());
};
model.channelRouterControlValues.addOnChangedHandler(handleControlsChanged);
return () => {
model.channelRouterSettings.removeOnChangedHandler(handleSettingsChanged);
model.channelRouterControlValues.removeOnChangedHandler(handleControlsChanged);
}
} else {
return () => { };
}
}, [open]);
const handleClose = (): void => {
onClose();
};
let [windowSize] = useWindowSize();
let landscape = windowSize.width > windowSize.height;
let fullScreen = windowSize.width < 450 || windowSize.height < 600;
let ChannelSelect = (routeType: RouteType, channelIndex: number, input: boolean, icon: boolean = true) => {
let channelCount = input ? config.inputAudioPorts.length : config.outputAudioPorts.length;
if (input && debugInputChannels != null) {
channelCount = debugInputChannels;
}
if (!input && debugOutputChannels != null) {
channelCount = debugOutputChannels;
}
let value: number;
switch (routeType) {
case RouteType.Main:
value = input ? settings.mainInputChannels[channelIndex] : settings.mainOutputChannels[channelIndex];
break;
case RouteType.Aux:
value = input ? settings.auxInputChannels[channelIndex] : settings.auxOutputChannels[channelIndex];
break;
case RouteType.Send:
value = input ? settings.sendInputChannels[channelIndex] : settings.sendOutputChannels[channelIndex];
break;
}
if (value >= channelCount) {
value = -1;
}
return (
<Select variant="standard" style={{ width: 90 }} value={value}
startAdornment={
icon ? (
input ? (<MicIcon style={{ opacity: 0.6, width: 16, height: 16, marginRight: 4 }} />) :
(<SpeakerIcon style={{ opacity: 0.6, width: 16, height: 16, marginRight: 4 }} />)
) : undefined
}
onChange={(event) => {
let newValue = event.target.value as number;
let newSettings = new ChannelRouterSettings().deserialize(settings);
switch (routeType) {
case RouteType.Main:
if (input) {
newSettings.mainInputChannels[channelIndex] = newValue;
} else {
newSettings.mainOutputChannels[channelIndex] = newValue;
}
break;
case RouteType.Aux:
if (input) {
newSettings.auxInputChannels[channelIndex] = newValue;
} else {
newSettings.auxOutputChannels[channelIndex] = newValue;
}
break;
case RouteType.Send:
if (input) {
newSettings.sendInputChannels[channelIndex] = newValue;
} else {
newSettings.sendOutputChannels[channelIndex] = newValue;
}
break;
}
newSettings.channelRouterPresetId = -1; // custom.
newSettings.configured = true;
model.setChannelRouterSettings(newSettings);
}}
>
{MakeChannelMenu(channelCount)}
</Select>
)
}
let Vu = () => {
return (
<div style={{}} >
<div style={{ width: 8, height: 48, background: "black" }}></div>
{/* <VuMeter instanceId={Pedalboard.CHANNEL_ROUTER_MAIN_INSERT_ID} display={"input"} /> */}
</div>
);
}
let ApplyPresetId = (presetId: number | string | null | undefined) => {
if (typeof presetId !== 'number') {
return;
}
for (let preset of channelRouterPresets) {
if (preset.id === presetId) {
let newPreset = Object.assign(new ChannelRouterSettings(), preset.settings);
// mark the presets as inserts.
newPreset.mainInserts.nextInstanceId = Pedalboard.MAIN_INSERT_INSTANCE_BASE;
newPreset.auxInserts.nextInstanceId = Pedalboard.AUX_INSERT_INSTANCE_BASE;
model.setChannelRouterSettings(newPreset);
return;
}
}
}
let CanUsePreset = (preset: ChannelRouterSettingsPreset): boolean => {
let inputChannels = config.inputAudioPorts.length;
let outputChannels = config.outputAudioPorts.length;
return (
(preset.settings.mainInputChannels.every((ch) => ch < inputChannels || ch === -1)) &&
(preset.settings.mainOutputChannels.every((ch) => ch < outputChannels || ch === -1)) &&
(preset.settings.auxInputChannels.every((ch) => ch < inputChannels || ch === -1)) &&
(preset.settings.auxOutputChannels.every((ch) => ch < outputChannels || ch === -1)) &&
(preset.settings.sendInputChannels.every((ch) => ch < inputChannels || ch === -1)) &&
(preset.settings.sendOutputChannels.every((ch) => ch < outputChannels || ch === -1))
);
}
let GeneratePresetMenuItems = (): React.ReactElement[] => {
let items: React.ReactElement[] = [];
for (let preset of channelRouterPresets) {
if (CanUsePreset(preset))
items.push(
<MenuItem key={preset.id} value={preset.id}>
{preset.name}
</MenuItem>
);
}
if (settings.channelRouterPresetId === -1) {
items.push(
(<MenuItem key={-1} value={-1} disabled={true}>
<span style={{ opacity: 0.6 }}>(Custom)</span></MenuItem>)
)
}
return items;
}
let PresetSelect = (width: number | string | undefined) => {
return (
<div style={{ display: "flex", flexDirection: "row", marginTop: 2, alignItems: "center" }}>
<Select variant="standard" style={{ width: width }} value={settings.channelRouterPresetId}
onChange={(event) => {
ApplyPresetId(event.target.value);
}}
>
{GeneratePresetMenuItems()}
</Select>
<IconButtonEx tooltip="Presets" aria-label="more-presets">
<MoreVertIcon style={{ opacity: 0.6 }} onClick={() => { }} />
</IconButtonEx>
</div>
)
}
let LandscapeView = () => {
return (
<div style={{
display: "flex", flexFlow: "row", alignItems: "center", justifyContent: "center",
width: "100%", height: "100%", flexGrow: 1, fontSize: "14px"
}} >
<table style={{ borderCollapse: "collapse" }}>
<colgroup>
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
</colgroup>
<tbody>
{/* Main */}
<tr>
<td rowSpan={1} style={cellLandscapeTitle}>
<Typography variant="body2" color="textSecondary">Main</Typography>
</td>
<td style={cellLeft}>
{ChannelSelect(RouteType.Main, 0, true)}
</td>
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
<td rowSpan={2} style={cellLeft}>
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
Inserts
</Button>
</td>
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
<td style={cellLeft}>
{ChannelSelect(RouteType.Main, 0, false)}
</td>
</tr>
<tr>
<td></td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Main, 1, true))}
</td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Main, 1, false))}
</td>
</tr>
{/* Spacer ---------------- */}
<tr><td colSpan={9} style={{ height: 16 }}></td></tr>
{/* Aux ----------------------*/}
<tr>
<td rowSpan={1} style={cellLandscapeTitle}>
<Typography variant="body2" color="textSecondary">Aux</Typography>
</td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Aux, 0, true))}
</td>
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
<td rowSpan={2} style={cellLeft}>
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
Inserts
</Button>
</td>
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Aux, 0, false))}
</td>
</tr>
<tr>
<td></td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Aux, 1, true))}
</td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Aux, 1, false))}
</td>
</tr>
{/* Spacer ---------------- */}
<tr><td colSpan={9} style={{ height: 16 }}></td></tr>
{/* Send ---------------- */}
<tr>
<td rowSpan={1} style={cellLandscapeTitle}>
<Typography variant="body2" color="textSecondary">Send</Typography>
</td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Send, 0, false))}
</td>
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
<td rowSpan={2} style={cellLeft}>
{/* No button */}
</td>
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Send, 0, true))}
</td>
</tr>
<tr>
<td></td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Send, 1, false))}
</td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Send, 1, true))}
</td>
</tr>
</tbody>
</table>
</div>
);
};
let PortraitView = () => {
return (
<div style={{
display: "flex", flexFlow: "row", alignItems: "center", justifyContent: "center",
width: "100%", flexGrow: 0, fontSize: "14px"
}} >
<table style={{ borderCollapse: "collapse" }}>
<colgroup>
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
<col style={{ width: "auto" }} />
</colgroup>
<tbody>
{/* Main */}
<tr>
<td colSpan={1} style={cellPortraitSectionHead}>
<Typography variant="body2" color="textSecondary" >Main</Typography>
</td>
<td colSpan={4}>
</td>
</tr>
<tr>
<td style={cellPortraitInOut}>
<div style={cellPortraitInOutDiv}>
<Typography variant="body2" color="textSecondary" >In</Typography>
<MicIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
</div>
</td>
<td style={cellLeft}>
{ChannelSelect(RouteType.Main, 0, true, false)}
</td>
<td style={cellLeft}>
{ChannelSelect(RouteType.Main, 1, true, false)}
</td>
<td></td>
</tr>
<tr>
<td></td>
<td colSpan={4}>
<table>
<tbody>
<tr>
<td style={cellPortraitControlStrip}>{Vu()}</td>
<td style={cellPortraitControlStrip}>
<div style={{ marginLeft: 8, marginRight: 8 }}>
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
Inserts
</Button>
</div>
</td>
<td style={cellPortraitControlStrip}>{Vu()}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td style={cellPortraitInOut}>
<div style={cellPortraitInOutDiv}>
<Typography variant="body2" color="textSecondary" >Out</Typography>
<SpeakerIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
</div>
</td>
<td style={cellLeft}>
{ChannelSelect(RouteType.Main, 0, false, false)}
</td>
<td style={cellLeft}>
{ChannelSelect(RouteType.Main, 1, false, false)}
</td>
<td></td>
</tr>
{/* Spacer ---------------- */}
<tr><td colSpan={5} style={{ height: 16 }}></td></tr>
{/* Aux */}
<tr>
<td colSpan={1} style={cellPortraitSectionHead}>
<Typography variant="body2" color="textSecondary" >Aux</Typography>
</td>
<td colSpan={4}></td>
</tr>
<tr>
<td style={cellPortraitInOut}>
<div style={cellPortraitInOutDiv}>
<Typography variant="body2" color="textSecondary" >In</Typography>
<MicIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
</div>
</td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Aux, 0, true, false))}
</td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Aux, 1, true, false))}
</td>
<td></td>
</tr>
<tr>
<td></td>
<td colSpan={4}>
<table>
<tbody>
<tr>
<td style={cellPortraitControlStrip}>{Vu()}</td>
<td style={cellPortraitControlStrip}>
<div style={{ marginLeft: 8, marginRight: 8 }}>
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
Inserts
</Button>
</div>
</td>
<td style={cellPortraitControlStrip}>{Vu()}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td style={cellPortraitInOut}>
<div style={cellPortraitInOutDiv}>
<Typography variant="body2" color="textSecondary" >Out</Typography>
<SpeakerIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
</div>
</td>
<td style={cellLeft}>
{ChannelSelect(RouteType.Aux, 0, false, false)}
</td>
<td style={cellLeft}>
{ChannelSelect(RouteType.Aux, 1, false, false)}
</td>
<td></td>
</tr>
{/* Spacer ---------------- */}
<tr><td colSpan={5} style={{ height: 16 }}></td></tr>
{/* Send */}
<tr>
<td colSpan={1} style={cellPortraitSectionHead}>
<Typography variant="body2" color="textSecondary" >Send</Typography>
</td>
<td colSpan={4}></td>
</tr>
<tr>
<td style={cellPortraitInOut}>
<div style={cellPortraitInOutDiv}>
<Typography variant="body2" color="textSecondary" >Send</Typography>
<SpeakerIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
</div>
</td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Send, 0, false, false))}
</td>
<td style={cellLeft}>
{(ChannelSelect(RouteType.Send, 1, false, false))}
</td>
<td></td>
</tr>
<tr>
<td></td>
<td colSpan={4}>
<table>
<tbody>
<tr>
<td style={cellPortraitControlStrip}>{Vu()}</td>
<td style={cellPortraitControlStrip}>
<div style={{ marginLeft: 8, marginRight: 8, visibility: "hidden" }}>
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
Inserts
</Button>
</div>
</td>
<td style={cellPortraitControlStrip}>{Vu()}</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td style={cellPortraitInOut}>
<div style={cellPortraitInOutDiv}>
<Typography variant="body2" color="textSecondary" >Return</Typography>
<MicIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
</div>
</td>
<td style={cellLeft}>
{ChannelSelect(RouteType.Send, 0, true, false)}
</td>
<td style={cellLeft}>
{ChannelSelect(RouteType.Send, 1, true, false)}
</td>
<td></td>
</tr>
</tbody>
</table>
</div>
);
};
return (
<DialogEx tag="channelRouterSettings"
onClose={handleClose}
aria-labelledby="select-channel_mixer_settings"
open={open}
fullWidth
maxWidth={landscape ? "sm" : "xs"}
onEnterKey={handleClose}
fullScreen={fullScreen}
>
<DialogTitle id="select-channel_mixer_settings" style={{ paddingTop: 0, paddingBottom: 0, marginBottom: 0 }}>
<div style={{
display: "flex", flexFlow: "row", flexWrap: "nowrap", alignItems: "center",
marginTop: 8
}}>
<IconButtonEx
tooltip="Close"
edge="start"
color="inherit"
aria-label="cancel"
style={{ opacity: 0.6 }}
onClick={() => { onClose(); }}
>
<ArrowBackIcon style={{ width: 24, height: 24 }} />
</IconButtonEx>
<Typography noWrap component="div" sx={{}}>
Channel Routing
</Typography>
{
landscape ? (
<>
<div style={{ flexGrow: 1 }}>&nbsp;</div>
<div style={{ flexShrink: 1, maxWidth: 440, display: "relative", marginLeft: 16, marginRight: 16 }}>
{PresetSelect(200)}
</div>
<div style={{ flexGrow: 1 }}>&nbsp;</div>
</>
) : (
<div style={{ flexGrow: 1 }}>&nbsp;</div>
)
}
<IconButtonEx tooltip="Help"
edge="end"
aria-label="help"
onClick={() => { setShowHelp(true); }}
>
<HelpOutlineIcon style={{ width: 24, height: 24, opacity: 0.6 }} />
</IconButtonEx>
</div>
</DialogTitle>
{!landscape && (
<>
<div style={{ marginLeft: 16 + 32 + 8, marginBottom: 8 }}>
{PresetSelect("60%")}
</div>
<Divider />
</>
)}
<DialogContent>
{landscape ?
LandscapeView()
:
PortraitView()
}
</DialogContent>
{
showHelp && (
<ChannelRouterSettingsHelpDialog open={showHelp} onClose={() => setShowHelp(false)} />
)
}
</DialogEx >
);
}
export default ChannelRouterSettingsDialog;
@@ -0,0 +1,153 @@
// Copyright (c) 2022 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import React from 'react';
import DialogEx from './DialogEx';
import DialogContent from '@mui/material/DialogContent';
import Typography from '@mui/material/Typography';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
import DialogTitle from '@mui/material/DialogTitle';
import Toolbar from '@mui/material/Toolbar';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import IconButtonEx from './IconButtonEx';
export interface ChannelRouterSettingsHelpDialogProps {
open: boolean,
onClose: () => void,
};
export interface ChannelRouterSettingsHelpDialogState {
fullScreen: boolean;
};
export default class ChannelRouterSettingsHelpDialog extends ResizeResponsiveComponent<ChannelRouterSettingsHelpDialogProps, ChannelRouterSettingsHelpDialogState> {
refText: React.RefObject<HTMLInputElement | null>;
constructor(props: ChannelRouterSettingsHelpDialogProps) {
super(props);
this.state = {
fullScreen: false
};
this.refText = React.createRef<HTMLInputElement>();
}
mounted: boolean = false;
onWindowSizeChanged(width: number, height: number): void {
this.setState({ fullScreen: height < 200 })
}
componentDidMount() {
super.componentDidMount();
this.mounted = true;
}
componentWillUnmount() {
super.componentWillUnmount();
this.mounted = false;
}
componentDidUpdate() {
}
render() {
let props = this.props;
let { open, onClose } = props;
const handleClose = () => {
onClose();
};
return (
<DialogEx tag="bindingHelp" open={open} fullWidth maxWidth="sm"
onClose={handleClose}
aria-labelledby="mixer-settings-dialog-title"
style={{ userSelect: "none" }}
fullwidth
onEnterKey={() => { }}
>
<DialogTitle id="select-channel_mixer_settings" style={{ paddingTop: 0, paddingBottom: 0, marginBottom: 8 }}>
<Toolbar style={{ padding: 0, margin: 0 }}>
<IconButtonEx
tooltip="Close"
edge="start"
color="inherit"
aria-label="cancel"
style={{ opacity: 0.6 }}
onClick={() => { onClose(); }}
>
<ArrowBackIcon style={{ width: 24, height: 24 }} />
</IconButtonEx>
<Typography noWrap component="div" sx={{ flexGrow: 1 }}>
Channel Routing Dialog Help
</Typography>
</Toolbar>
</DialogTitle>
<DialogContent style={{ minHeight: 96 }}>
<div style={{ fontSize: "0.9em", lineHeight: "18pt" }}>
<p> The Mixer Routing Dialog determines how audio signals are processed and routed between input and output audio channels.
</p>
<p>Guitar effects processing occurs on the <i>Main</i> route only. Plugins in the main PiPedal window are
applied before any insert plugins that have been added to the <i>Main</i> route. Main insert plugins are
applied globally, regardless of the selected preset or effects in the main PiPedal window. You might
want to add an EQ or reverb plugin here, to allow your presets to be globally adjusted to suit the room
in which you are currently performing.
</p>
<p>Plugins in the main PiPedal window are not applied to the <i>Aux</i> route. The Aux route is intended to be
used for
</p>
<ul>
<li>Passing through a dry (unprocessed) guitar signal that can be recorded by a DAW and
re-amped later during mixing.
</li>
<li>Passing through a vocal mic signal from one of the input channels, perhaps with compression, EQ or
other processing performed by plugins in Aux inserts.
</li>
<li>
Passing through a backing track or other external audio signal from one of the input channels, and then either
mixing it with the processed guitar signal, or passing it out on a dedicated output channel.
</li>
</ul>
<p>although you may find other creative uses for the Aux channel as well.</p>
<p>If the Main and Aux routes share output channels, then the results of the Main and Output signals are
summed together on those output channels. For stereo mixing, assign the same left and right output
channels to both the Main and Aux routes. If you have a 2x4 or 4x4 audio interface with two guitar input channels
you can also pass through stereo dry signals by configuring Main and Aux input and output channels appropriately.
</p>
<p>The Send route, in conjunction with the TooB Send plugin allows you to send and return audio signals to an external physical device
from within a PiPedal preset. This allows you to integrate external hardware effects into your PiPedal presets.
Connect a cable from the audio interface output <i>Send</i> channel(s) selected here to the input of your
hardware effect, and a cable from the output of your hardware effect to the audio interface's <i>Return</i> channel
assigned here. Then insert the TooB Send plugin into your PiPedal preset. The input of the
TooB Send plugin will be sent to the external hardware effect, and the output the external hardware
effect will be returned to PiPedal at the output of the TooB Send plugin. PiPedal currently supports
only one Send plugin instance per preset; and the Send and Return channels may not be shared with the Main
or Aux routes.
</p>
</div>
</DialogContent>
</DialogEx>
);
}
}
+187
View File
@@ -0,0 +1,187 @@
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import Units from './Units';
import {UiPlugin, UiControl, PluginType, ControlType, ScalePoint} from './Lv2Plugin';
export const ChannelRouterUiUri = "uri://two-play/pipedal/ppChannelRouter";
export function makeChanelMixerUiPlugin(): UiPlugin {
return new UiPlugin().deserialize({
uri: ChannelRouterUiUri,
name: "Channel Mixer",
brand: "",
label: "",
plugin_type: PluginType.MixerPlugin,
plugin_display_type: "Mixer",
author_name: "",
author_homepage: "",
audio_inputs: 1,
audio_side_chain_inputs: 0,
audio_outputs: 1,
has_midi_input: false,
has_midi_output: false,
description: "",
controls: [
new UiControl().applyProperties({
symbol: "mainIn",
name: "Main In",
index: 0,
is_input: true,
min_value: -60.0,
max_value: 20.0,
default_value: 0.0,
is_bypass: false,
units: Units.db,
controlType: ControlType.Dial,
is_program_controller: false,
custom_units: "",
connection_optional: false,
scale_points: [
new ScalePoint().deserialize({ value: -60, label: "-INF" }),
],
}),
new UiControl().applyProperties({
symbol: "mainOut",
name: "Main Out",
index: 1,
is_input: true,
min_value: -60.0,
max_value: 20.0,
default_value: 0.0,
is_bypass: false,
units: Units.db,
controlType: ControlType.Dial,
is_program_controller: false,
custom_units: "",
connection_optional: false,
scale_points: [
new ScalePoint().deserialize({ value: -60, label: "-INF" }),
],
}),
new UiControl().applyProperties({
symbol: "auxIn",
name: "Aux In",
index: 2,
is_input: true,
min_value: -60.0,
max_value: 20.0,
default_value: 0.0,
is_bypass: false,
units: Units.db,
controlType: ControlType.Dial,
is_program_controller: false,
custom_units: "",
connection_optional: false,
scale_points: [
new ScalePoint().deserialize({ value: -60, label: "-INF" }),
],
}),
new UiControl().applyProperties({
symbol: "auxOut",
name: "Aux Out",
index: 3,
is_input: true,
min_value: -60.0,
max_value: 20.0,
default_value: 0.0,
is_bypass: false,
units: Units.db,
controlType: ControlType.Dial,
is_program_controller: false,
custom_units: "",
connection_optional: false,
scale_points: [
new ScalePoint().deserialize({ value: -60, label: "-INF" }),
],
}),
new UiControl().applyProperties({
symbol: "mainInVu",
name: "In",
index: 4,
is_input: false,
min_value: -60.0,
max_value: 20.0,
default_value: 0.0,
controlType: ControlType.Vu,
is_bypass: false,
is_program_controller: false,
units: Units.db,
custom_units: "",
connection_optional: false,
}),
new UiControl().applyProperties({
symbol: "mainInVu",
name: "In",
index: 5,
is_input: false,
min_value: -60.0,
max_value: 20.0,
default_value: 0.0,
controlType: ControlType.Vu,
is_bypass: false,
is_program_controller: false,
units: Units.db,
custom_units: "",
connection_optional: false,
}),
new UiControl().applyProperties({
symbol: "mainOutVu",
name: "Out",
index: 6,
is_input: false,
min_value: -60.0,
max_value: 20.0,
default_value: 0.0,
controlType: ControlType.Vu,
is_bypass: false,
is_program_controller: false,
units: Units.db,
custom_units: "",
connection_optional: false,
}),
new UiControl().applyProperties({
symbol: "auxOutVu",
name: "Out",
index: 7,
is_input: false,
min_value: -60.0,
max_value: 20.0,
default_value: 0.0,
controlType: ControlType.Vu,
is_bypass: false,
is_program_controller: false,
units: Units.db,
custom_units: "",
connection_optional: false,
}),
],
port_groups: [],
fileProperties: [],
frequencyPlots: [],
is_vst3: false,
}
);
}
+16 -7
View File
@@ -1120,6 +1120,8 @@ export class UiPlugin implements Deserializable<UiPlugin> {
export function makeSplitUiPlugin(): UiPlugin { export function makeSplitUiPlugin(): UiPlugin {
return new UiPlugin().deserialize({ return new UiPlugin().deserialize({
@@ -1138,7 +1140,7 @@ export function makeSplitUiPlugin(): UiPlugin {
has_midi_output: 0, has_midi_output: 0,
description: "", description: "",
controls: [ controls: [
new UiControl().deserialize({ new UiControl().applyProperties({
symbol: "splitType", symbol: "splitType",
name: "Type", name: "Type",
index: 0, index: 0,
@@ -1159,7 +1161,7 @@ export function makeSplitUiPlugin(): UiPlugin {
connection_optional: false, connection_optional: false,
}), }),
new UiControl().deserialize({ new UiControl().applyProperties({
symbol: "select", symbol: "select",
name: "Select", name: "Select",
index: 1, index: 1,
@@ -1182,7 +1184,7 @@ export function makeSplitUiPlugin(): UiPlugin {
}), }),
new UiControl().deserialize({ new UiControl().applyProperties({
symbol: "mix", symbol: "mix",
name: "Mix", name: "Mix",
index: 2, index: 2,
@@ -1195,9 +1197,10 @@ export function makeSplitUiPlugin(): UiPlugin {
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
scale_points: [],
}), }),
new UiControl().deserialize({ new UiControl().applyProperties({
symbol: "panL", symbol: "panL",
name: "Pan Top", name: "Pan Top",
index: 3, index: 3,
@@ -1210,10 +1213,12 @@ export function makeSplitUiPlugin(): UiPlugin {
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
scale_points: [],
}), }),
new UiControl().deserialize({ new UiControl().applyProperties({
symbol: "volL", symbol: "volL",
name: "Vol Top",
index: 4, index: 4,
is_input: true, is_input: true,
min_value: -60.0, min_value: -60.0,
@@ -1224,9 +1229,10 @@ export function makeSplitUiPlugin(): UiPlugin {
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
scale_points: [],
}), }),
new UiControl().deserialize({ new UiControl().applyProperties({
symbol: "panR", symbol: "panR",
name: "Pan Bottom", name: "Pan Bottom",
index: 5, index: 5,
@@ -1239,9 +1245,10 @@ export function makeSplitUiPlugin(): UiPlugin {
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
scale_points: [],
}), }),
new UiControl().deserialize({ new UiControl().applyProperties({
symbol: "volR", symbol: "volR",
name: "Vol Bottom", name: "Vol Bottom",
index: 6, index: 6,
@@ -1254,6 +1261,8 @@ export function makeSplitUiPlugin(): UiPlugin {
is_program_controller: false, is_program_controller: false,
custom_units: "", custom_units: "",
connection_optional: false, connection_optional: false,
scale_points: [],
}) })
], ],
port_groups: [], port_groups: [],
+43 -1
View File
@@ -357,13 +357,36 @@ export class PedalboardSplitItem extends PedalboardItem {
} }
let TWO_POWER_52 = 4503599627370496; // Maximum Javascript integer value.
let TWO_POWER_48 = 281474976710656; // Number of possible instance IDs for Main Pedalboards.
let MAX_INSTANCE_ID = TWO_POWER_48;
export enum InstanceType {
MainPedalboard,
MainInsert,
AuxInsert
}
export class Pedalboard implements Deserializable<Pedalboard> { export class Pedalboard implements Deserializable<Pedalboard> {
static readonly CHANNEL_ROUTER_CONTROLS_INSTANCE_ID = -4; // Reserved Instance ID for Router Main Inserts.
static readonly CHANNEL_ROUTER_MAIN_INSERT_ID = -5; // Reserved Instance ID for Router Main Inserts.
static readonly CHANNEL_ROUTER_AUX_INSERT_ID = -6; // Reserved Instance ID for Router Aux inserts.
static readonly MAIN_INSERT_INSTANCE_BASE = TWO_POWER_52-2*MAX_INSTANCE_ID;
static readonly AUX_INSERT_INSTANCE_BASE = TWO_POWER_52-1*MAX_INSTANCE_ID;
static readonly START_CONTROL = -2; // synthetic PedalboardItem for input volume. static readonly START_CONTROL = -2; // synthetic PedalboardItem for input volume.
static readonly END_CONTROL = -3; // synthetic PedalboardItem for output volume. static readonly END_CONTROL = -3; // synthetic PedalboardItem for output volume.
static readonly MAIN_INSERT_START_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + -2 + MAX_INSTANCE_ID;
static readonly MAIN_INSERT_END_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + -3 + MAX_INSTANCE_ID;
static readonly AUX_INSERT_START_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + -2 + MAX_INSTANCE_ID
static readonly AUX_INSERT_END_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + -3 + MAX_INSTANCE_ID;
static readonly START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start"; static readonly START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start";
static readonly END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End"; static readonly END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End";
@@ -372,7 +395,7 @@ export class Pedalboard implements Deserializable<Pedalboard> {
this.input_volume_db = input.input_volume_db; this.input_volume_db = input.input_volume_db;
this.output_volume_db = input.output_volume_db; this.output_volume_db = input.output_volume_db;
this.items = PedalboardItem.deserializeArray(input.items); this.items = PedalboardItem.deserializeArray(input.items);
this.nextInstanceId = input.nextInstanceId ?? -1; this.nextInstanceId = input.nextInstanceId ?? 0;
this.snapshots = input.snapshots ? Snapshot.deserializeArray(input.snapshots): []; this.snapshots = input.snapshots ? Snapshot.deserializeArray(input.snapshots): [];
this.selectedSnapshot = input.selectedSnapshot; this.selectedSnapshot = input.selectedSnapshot;
this.pathProperties = input.pathProperties; this.pathProperties = input.pathProperties;
@@ -380,6 +403,25 @@ export class Pedalboard implements Deserializable<Pedalboard> {
return this; return this;
} }
static getInstanceTypeFromInstanceId(instanceId: number): InstanceType {
if (instanceId >= Pedalboard.MAIN_INSERT_INSTANCE_BASE) {
return InstanceType.MainInsert;
}
if (instanceId >= Pedalboard.AUX_INSERT_INSTANCE_BASE) {
return InstanceType.AuxInsert;
}
return InstanceType.MainPedalboard;
}
getInstanceType(): InstanceType {
if (this.nextInstanceId >= Pedalboard.MAIN_INSERT_INSTANCE_BASE) {
return InstanceType.MainInsert;
}
if (this.nextInstanceId >= Pedalboard.AUX_INSERT_INSTANCE_BASE) {
return InstanceType.AuxInsert;
}
return InstanceType.MainPedalboard;
}
clone(): Pedalboard { clone(): Pedalboard {
return new Pedalboard().deserialize(this); return new Pedalboard().deserialize(this);
} }
+61 -32
View File
@@ -48,6 +48,7 @@ import AudioFileMetadata from './AudioFileMetadata';
import { pathFileName } from './FileUtils'; import { pathFileName } from './FileUtils';
import { AlsaSequencerConfiguration, AlsaSequencerPortSelection } from './AlsaSequencer'; import { AlsaSequencerConfiguration, AlsaSequencerPortSelection } from './AlsaSequencer';
import { getDefaultModGuiPreference } from './ModGuiHost'; import { getDefaultModGuiPreference } from './ModGuiHost';
import ChannelRouterSettings from './ChannelRouterSettings';
import Tone3000DownloadProgress from './Tone3000DownloadProgress'; import Tone3000DownloadProgress from './Tone3000DownloadProgress';
export enum State { export enum State {
@@ -433,9 +434,9 @@ interface MonitorPortOutputBody {
} }
interface ChannelSelectionChangedBody { interface ChannelRouterSettingsChangedBody {
clientId: number; clientId: number;
jackChannelSelection: JackChannelSelection; channelRouterSettings: ChannelRouterSettings;
} }
interface RenamePresetBody { interface RenamePresetBody {
clientId: number; clientId: number;
@@ -533,6 +534,9 @@ export class PiPedalModel //implements PiPedalModel
jackServerSettings: ObservableProperty<JackServerSettings> jackServerSettings: ObservableProperty<JackServerSettings>
= new ObservableProperty<JackServerSettings>(new JackServerSettings()); = new ObservableProperty<JackServerSettings>(new JackServerSettings());
channelRouterControlValues: ObservableProperty<ControlValue[]> = new ObservableProperty<ControlValue[]>([]);
channelRouterSettings: ObservableProperty<ChannelRouterSettings> = new ObservableProperty<ChannelRouterSettings>(new ChannelRouterSettings());
wifiConfigSettings: ObservableProperty<WifiConfigSettings> = new ObservableProperty<WifiConfigSettings>(new WifiConfigSettings()); wifiConfigSettings: ObservableProperty<WifiConfigSettings> = new ObservableProperty<WifiConfigSettings>(new WifiConfigSettings());
wifiDirectConfigSettings: ObservableProperty<WifiDirectConfigSettings> = new ObservableProperty<WifiDirectConfigSettings>(new WifiDirectConfigSettings()); wifiDirectConfigSettings: ObservableProperty<WifiDirectConfigSettings> = new ObservableProperty<WifiDirectConfigSettings>(new WifiDirectConfigSettings());
governorSettings: ObservableProperty<GovernorSettings> = new ObservableProperty<GovernorSettings>(new GovernorSettings()); governorSettings: ObservableProperty<GovernorSettings> = new ObservableProperty<GovernorSettings>(new GovernorSettings());
@@ -817,10 +821,11 @@ export class PiPedalModel //implements PiPedalModel
} else if (message === "onShowStatusMonitorChanged") { } else if (message === "onShowStatusMonitorChanged") {
let value = body as boolean; let value = body as boolean;
this.showStatusMonitor.set(value); this.showStatusMonitor.set(value);
} else if (message === "onChannelSelectionChanged") { } else if (message === "onChannelRouterSettingsChanged") {
let channelSelectionBody = body as ChannelSelectionChangedBody; let channelRouterSettingChangedBody = body as ChannelRouterSettingsChangedBody;
let channelSelection = new JackChannelSelection().deserialize(channelSelectionBody.jackChannelSelection); let channelRouterSettings = new ChannelRouterSettings().deserialize(
this.jackSettings.set(channelSelection); channelRouterSettingChangedBody.channelRouterSettings);
this.channelRouterSettings.set(channelRouterSettings);
} else if (message === "onSnapshotModified") { } else if (message === "onSnapshotModified") {
let { snapshotIndex, modified } = (body as { snapshotIndex: number, modified: boolean }); let { snapshotIndex, modified } = (body as { snapshotIndex: number, modified: boolean });
let snapshots = this.pedalboard.get().snapshots; let snapshots = this.pedalboard.get().snapshots;
@@ -1385,6 +1390,11 @@ export class PiPedalModel //implements PiPedalModel
await this.getWebSocket().request<any>("getJackServerSettings") await this.getWebSocket().request<any>("getJackServerSettings")
) )
); );
this.channelRouterSettings.set(
new ChannelRouterSettings().deserialize(
await this.getWebSocket().request<any>("getChannelRouterSettings")
)
)
this.jackConfiguration.set(new JackConfiguration().deserialize( this.jackConfiguration.set(new JackConfiguration().deserialize(
await this.getWebSocket().request<JackConfiguration>("getJackConfiguration") await this.getWebSocket().request<JackConfiguration>("getJackConfiguration")
)); ));
@@ -1778,35 +1788,52 @@ export class PiPedalModel //implements PiPedalModel
private lastControlMessageWasSentbyMe = false; private lastControlMessageWasSentbyMe = false;
private _setChannelRouterControlValue(key: string, value: number, notifyServer: boolean) : void {
let channelRouterSettings = this.channelRouterSettings.get();
let changed = channelRouterSettings.setControlValue(key, value);
if (changed)
{
this.channelRouterControlValues.set(this.channelRouterSettings.get().controlValues);
if (notifyServer) {
this.lastControlMessageWasSentbyMe = true;
this._setServerControl("setControl", Pedalboard.CHANNEL_ROUTER_CONTROLS_INSTANCE_ID, key, value);
}
}
}
private _setPedalboardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void { private _setPedalboardControlValue(instanceId: number, key: string, value: number, notifyServer: boolean): void {
let pedalboard = this.pedalboard.get(); let pedalboard = this.pedalboard.get();
if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready."); if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
let newPedalboard = pedalboard.clone();
if (instanceId === Pedalboard.START_CONTROL && key === "volume_db") { if (instanceId == Pedalboard.CHANNEL_ROUTER_CONTROLS_INSTANCE_ID) {
this._setInputVolume(value, notifyServer); this._setChannelRouterControlValue(key,value,notifyServer);
return; return;
} else if (instanceId === Pedalboard.END_CONTROL) { } else {
this._setOutputVolume(value, notifyServer); let changed: boolean;
return; let newPedalboard = pedalboard.clone();
}
let item = newPedalboard.getItem(instanceId);
let changed = item.setControlValue(key, value);
if (changed) { if (instanceId === Pedalboard.START_CONTROL && key === "volume_db") {
if (notifyServer) { this._setInputVolume(value, notifyServer);
this.lastControlMessageWasSentbyMe = true; return;
this._setServerControl("setControl", instanceId, key, value); } else if (instanceId === Pedalboard.END_CONTROL) {
this._setOutputVolume(value, notifyServer);
return;
} }
this.setModelPedalboard(newPedalboard); let item = newPedalboard.getItem(instanceId);
for (let i = 0; i < this._controlValueChangeItems.length; ++i) { changed = item.setControlValue(key, value);
let item = this._controlValueChangeItems[i]; if (changed) {
if (instanceId === item.instanceId) { if (notifyServer) {
item.onValueChanged(key, value); this.lastControlMessageWasSentbyMe = true;
this._setServerControl("setControl", instanceId, key, value);
}
this.setModelPedalboard(newPedalboard);
for (let i = 0; i < this._controlValueChangeItems.length; ++i) {
let item = this._controlValueChangeItems[i];
if (instanceId === item.instanceId) {
item.onValueChanged(key, value);
}
} }
} }
} }
} }
private _setVst3PedalboardControlValue(instanceId: number, key: string, value: number, state: string, notifyServer: boolean): void { private _setVst3PedalboardControlValue(instanceId: number, key: string, value: number, state: string, notifyServer: boolean): void {
let pedalboard = this.pedalboard.get(); let pedalboard = this.pedalboard.get();
@@ -2426,12 +2453,6 @@ export class PiPedalModel //implements PiPedalModel
} }
setJackSettings(jackSettings: JackChannelSelection): void {
this.expectDisconnect(ReconnectReason.LoadingSettings);
this.webSocket?.send("setJackSettings", jackSettings);
}
monitorPortSubscriptions: MonitorPortHandleImpl[] = []; monitorPortSubscriptions: MonitorPortHandleImpl[] = [];
monitorPort(instanceId: number, key: string, updateRateSeconds: number, onUpdated: (value: number) => void): MonitorPortHandle { monitorPort(instanceId: number, key: string, updateRateSeconds: number, onUpdated: (value: number) => void): MonitorPortHandle {
@@ -3420,7 +3441,6 @@ export class PiPedalModel //implements PiPedalModel
if (this.webSocket) { if (this.webSocket) {
this.webSocket.send("setFavorites", newFavorites); this.webSocket.send("setFavorites", newFavorites);
} }
// stub: update server.
} }
@@ -3790,6 +3810,15 @@ export class PiPedalModel //implements PiPedalModel
} }
this.removeInvalidSidechains(pedalboard); this.removeInvalidSidechains(pedalboard);
} }
setChannelRouterSettings(settings: ChannelRouterSettings) {
this.channelRouterSettings.set(settings);
this.channelRouterControlValues.set(settings.controlValues);
if (this.webSocket) {
this.webSocket.send("setChannelRouterSettings", settings);
}
}
}; };
let instance: PiPedalModel | undefined = undefined; let instance: PiPedalModel | undefined = undefined;
@@ -1,4 +1,4 @@
// Copyright (c) 2022 Robin Davies // Copyright (c) 2026 Robin Davies
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy of // Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in // this software and associated documentation files (the "Software"), to deal in
+94 -55
View File
@@ -20,11 +20,13 @@
import React, { SyntheticEvent, Component } from 'react'; import React, { SyntheticEvent, Component } from 'react';
import Switch from "@mui/material/Switch"; import Switch from "@mui/material/Switch";
import ChannelRouterSettings from './ChannelRouterSettings';
import ChannelRouterSettingsDialog from './ChannelRouterSettingsDialog';
import OkCancelDialog from './OkCancelDialog'; import OkCancelDialog from './OkCancelDialog';
import RadioSelectDialog from './RadioSelectDialog'; import RadioSelectDialog from './RadioSelectDialog';
import IconButtonEx from './IconButtonEx'; import IconButtonEx from './IconButtonEx';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import {isDarkMode} from './DarkMode'; import { isDarkMode } from './DarkMode';
import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory, State } from './PiPedalModel';
import { ColorTheme } from './DarkMode'; import { ColorTheme } from './DarkMode';
import ButtonBase from "@mui/material/ButtonBase"; import ButtonBase from "@mui/material/ButtonBase";
@@ -75,7 +77,8 @@ interface SettingsDialogState {
showStatusMonitor: boolean; showStatusMonitor: boolean;
showStatusMonitorDialog: boolean; showStatusMonitorDialog: boolean;
jackConfiguration: JackConfiguration; jackConfiguration: JackConfiguration;
jackSettings: JackChannelSelection; jackSettings: JackChannelSelection | null;
channelRouterSettings: ChannelRouterSettings | null;
jackServerSettings: JackServerSettings; jackServerSettings: JackServerSettings;
alsaSequencerConfiguration: AlsaSequencerConfiguration; alsaSequencerConfiguration: AlsaSequencerConfiguration;
keepScreenOn: boolean; keepScreenOn: boolean;
@@ -98,6 +101,7 @@ interface SettingsDialogState {
showMidiSelectDialog: boolean; showMidiSelectDialog: boolean;
showThemeSelectDialog: boolean; showThemeSelectDialog: boolean;
showJackServerSettingsDialog: boolean; showJackServerSettingsDialog: boolean;
showChannelRouterSettingsDialog: boolean;
shuttingDown: boolean; shuttingDown: boolean;
restarting: boolean; restarting: boolean;
isAndroidHosted: boolean; isAndroidHosted: boolean;
@@ -190,6 +194,7 @@ const SettingsDialog = withStyles(
showStatusMonitorDialog: false, showStatusMonitorDialog: false,
jackServerSettings: this.model.jackServerSettings.get(), jackServerSettings: this.model.jackServerSettings.get(),
channelRouterSettings: this.model.channelRouterSettings.get(),
jackConfiguration: this.model.jackConfiguration.get(), jackConfiguration: this.model.jackConfiguration.get(),
jackStatus: undefined, jackStatus: undefined,
jackSettings: this.model.jackSettings.get(), jackSettings: this.model.jackSettings.get(),
@@ -210,6 +215,7 @@ const SettingsDialog = withStyles(
showMidiSelectDialog: false, showMidiSelectDialog: false,
showThemeSelectDialog: false, showThemeSelectDialog: false,
showJackServerSettingsDialog: false, showJackServerSettingsDialog: false,
showChannelRouterSettingsDialog: false,
shuttingDown: false, shuttingDown: false,
restarting: false, restarting: false,
showShutdownOkDialog: false, showShutdownOkDialog: false,
@@ -222,6 +228,7 @@ const SettingsDialog = withStyles(
this.handleAlsaSequencerConfigurationChanged = this.handleAlsaSequencerConfigurationChanged.bind(this); this.handleAlsaSequencerConfigurationChanged = this.handleAlsaSequencerConfigurationChanged.bind(this);
this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this); this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this);
this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this); this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this);
this.handleChannelRouterSettingsChanged = this.handleChannelRouterSettingsChanged.bind(this);
this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this); this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this);
this.handleWifiDirectConfigSettingsChanged = this.handleWifiDirectConfigSettingsChanged.bind(this); this.handleWifiDirectConfigSettingsChanged = this.handleWifiDirectConfigSettingsChanged.bind(this);
this.handleGovernorSettingsChanged = this.handleGovernorSettingsChanged.bind(this); this.handleGovernorSettingsChanged = this.handleGovernorSettingsChanged.bind(this);
@@ -323,6 +330,12 @@ const SettingsDialog = withStyles(
continueDisabled: !this.model.jackServerSettings.get().valid continueDisabled: !this.model.jackServerSettings.get().valid
}); });
} }
handleChannelRouterSettingsChanged(): void {
this.setState({
channelRouterSettings: this.model.channelRouterSettings.get(),
continueDisabled: !this.model.channelRouterSettings.get().configured
});
}
handleJackConfigurationChanged(): void { handleJackConfigurationChanged(): void {
this.setState({ this.setState({
@@ -365,6 +378,7 @@ const SettingsDialog = withStyles(
this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged); this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged);
this.model.alsaSequencerConfiguration.addOnChangedHandler(this.handleAlsaSequencerConfigurationChanged); this.model.alsaSequencerConfiguration.addOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged); this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged);
this.model.channelRouterSettings.addOnChangedHandler(this.handleChannelRouterSettingsChanged);
this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged); this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged);
this.model.wifiDirectConfigSettings.addOnChangedHandler(this.handleWifiDirectConfigSettingsChanged); this.model.wifiDirectConfigSettings.addOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
this.model.governorSettings.addOnChangedHandler(this.handleGovernorSettingsChanged); this.model.governorSettings.addOnChangedHandler(this.handleGovernorSettingsChanged);
@@ -387,6 +401,7 @@ const SettingsDialog = withStyles(
this.handleJackSettingsChanged(); this.handleJackSettingsChanged();
this.handleShowStatusMonitorChanged(); this.handleShowStatusMonitorChanged();
this.handleJackServerSettingsChanged(); this.handleJackServerSettingsChanged();
this.handleChannelRouterSettingsChanged();
this.handleWifiConfigSettingsChanged(); this.handleWifiConfigSettingsChanged();
this.handleWifiDirectConfigSettingsChanged(); this.handleWifiDirectConfigSettingsChanged();
this.setState({ hasWifiDevice: this.model.hasWifiDevice.get() }); this.setState({ hasWifiDevice: this.model.hasWifiDevice.get() });
@@ -405,6 +420,7 @@ const SettingsDialog = withStyles(
this.model.alsaSequencerConfiguration.removeOnChangedHandler(this.handleAlsaSequencerConfigurationChanged); this.model.alsaSequencerConfiguration.removeOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged); this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged);
this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged); this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged);
this.model.channelRouterSettings.removeOnChangedHandler(this.handleChannelRouterSettingsChanged);
this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged); this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged);
this.model.wifiDirectConfigSettings.removeOnChangedHandler(this.handleWifiDirectConfigSettingsChanged); this.model.wifiDirectConfigSettings.removeOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
this.model.governorSettings.removeOnChangedHandler(this.handleGovernorSettingsChanged); this.model.governorSettings.removeOnChangedHandler(this.handleGovernorSettingsChanged);
@@ -503,28 +519,10 @@ const SettingsDialog = withStyles(
} }
handleSelectChannelsDialogResult(channels: string[] | null): void { handleSelectChannelsDialogResult(channels: string[] | null): void {
if (channels) { this.setState({
let newSelection = this.state.jackSettings.clone(); showInputSelectDialog: false,
if (this.state.showInputSelectDialog) { showOutputSelectDialog: false
newSelection.inputAudioPorts = channels; });
} else {
newSelection.outputAudioPorts = channels;
}
this.setState({
jackSettings: newSelection,
showInputSelectDialog: false,
showOutputSelectDialog: false
});
this.model.setJackSettings(newSelection);
} else {
this.setState({
showInputSelectDialog: false,
showOutputSelectDialog: false
});
}
} }
midiSummary(): string { midiSummary(): string {
@@ -595,10 +593,16 @@ const SettingsDialog = withStyles(
const classes = withStyles.getClasses(this.props); const classes = withStyles.getClasses(this.props);
let isConfigValid = this.state.jackConfiguration.isValid; let isConfigValid = this.state.jackConfiguration.isValid;
let selectedChannels: string[] = this.state.showInputSelectDialog ? this.state.jackSettings.inputAudioPorts : this.state.jackSettings.outputAudioPorts; let selectedChannels: string[] =
this.state.jackSettings === null ? [] :
this.state.showInputSelectDialog ? this.state.jackSettings.inputAudioPorts : this.state.jackSettings.outputAudioPorts;
let disableShutdown = this.state.shuttingDown || this.state.restarting; let disableShutdown = this.state.shuttingDown || this.state.restarting;
let canKeepScreenOn = this.model.canKeepScreenOn; let canKeepScreenOn = this.model.canKeepScreenOn;
let hasAudioConfig = isConfigValid
&& this.state.jackConfiguration.inputAudioPorts.length >= 1
&& this.state.jackConfiguration.outputAudioPorts.length >= 1;
return ( return (
<DialogEx tag="settings" fullScreen open={this.props.open} <DialogEx tag="settings" fullScreen open={this.props.open}
@@ -663,8 +667,8 @@ const SettingsDialog = withStyles(
{(!isConfigValid) ? {(!isConfigValid) ?
( (
<div className={classes.cpuStatusColor} style={{ paddingLeft: 48, position: "relative", top: -12 }}> <div className={classes.cpuStatusColor} style={{ paddingLeft: 48, position: "relative", top: -12 }}>
<Typography display="block" variant="caption" color="textSecondary">Status: <Typography display="block" variant="caption" color="textSecondary">Status:
<span style={{ color: isDarkMode() ? "#F88" : "#F00" }}>Not configured.</span></Typography> <span style={{ color: isDarkMode() ? "#F88" : "#F00" }}>Not configured.</span></Typography>
{(!this.props.onboarding) && ( {(!this.props.onboarding) && (
<Typography display="block" variant="caption" color="inherit">Governor: </Typography> <Typography display="block" variant="caption" color="inherit">Governor: </Typography>
)} )}
@@ -693,47 +697,82 @@ const SettingsDialog = withStyles(
</Typography> </Typography>
<ButtonBase className={classes.setting} onClick={() => this.handleJackServerSettings()} <ButtonBase className={classes.setting} onClick={() => this.handleJackServerSettings()}
> >
<SelectHoverBackground selected={false} showHover={true} /> <SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}> <div style={{ width: "100%" }}>
<Typography display="block" variant="body2" noWrap>Audio device</Typography> <Typography display="block" variant="body2" noWrap>Audio device</Typography>
<Typography display="block" variant="caption" noWrap color="textSecondary">{this.state.jackServerSettings.getSummaryText()}</Typography> <Typography display="block" variant="caption" noWrap color="textSecondary">{this.state.jackServerSettings.getSummaryText()}</Typography>
</div> </div>
</ButtonBase> </ButtonBase>
<JackServerSettingsDialog {this.state.showChannelRouterSettingsDialog && (
open={this.state.showJackServerSettingsDialog} <ChannelRouterSettingsDialog
jackServerSettings={this.state.jackServerSettings} open={this.state.showChannelRouterSettingsDialog}
onClose={() => this.setState({ showJackServerSettingsDialog: false })} onClose={() => this.setState({ showChannelRouterSettingsDialog: false })}
onApply={(jackServerSettings) => { />
)}
{this.state.showJackServerSettingsDialog && (
<JackServerSettingsDialog
open={this.state.showJackServerSettingsDialog}
jackServerSettings={this.state.jackServerSettings}
onClose={() => this.setState({ showJackServerSettingsDialog: false })}
onApply={(jackServerSettings) => {
this.setState({
jackServerSettings: jackServerSettings
});
this.model.setJackServerSettings(jackServerSettings);
}}
/>
)}
<ButtonBase className={classes.setting}
onClick={() => {
this.setState({ this.setState({
jackServerSettings: jackServerSettings showChannelRouterSettingsDialog: true
}); });
this.model.setJackServerSettings(jackServerSettings);
}} }}
/> disabled={!hasAudioConfig}
style={{ opacity: (!hasAudioConfig) ? 0.6 : 1.0 }}
<ButtonBase className={classes.setting} onClick={() => this.handleInputSelection()}
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
> >
<SelectHoverBackground selected={false} showHover={true} /> <SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}> <div style={{ width: "100%" }}>
<Typography display="block" variant="body2" noWrap>Input channels</Typography> <Typography display="block" variant="body2" noWrap>Channel Routing</Typography>
<Typography display="block" variant="caption" color="textSecondary" noWrap>{this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}</Typography> <Typography display="block" variant="caption"
</div> color={
</ButtonBase> this.state.channelRouterSettings?.isValid(this.state.jackConfiguration) ?? false
<ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()} ? "textSecondary": "error"} noWrap>{
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1} this.state.channelRouterSettings?.getDescription(this.state.jackConfiguration)??""}</Typography>
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
>
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography display="block" variant="body2" noWrap>Output channels</Typography>
<Typography display="block" variant="caption" color="textSecondary" noWrap>{this.state.jackSettings.getAudioOutputDisplayValue(this.state.jackConfiguration)}</Typography>
</div> </div>
</ButtonBase> </ButtonBase>
{/* Old Input and Output selection
<ButtonBase className={classes.setting} onClick={() => this.handleInputSelection()}
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
>
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography display="block" variant="body2" noWrap>Input channels</Typography>
<Typography display="block" variant="caption" color="textSecondary" noWrap>{
this.state.jackSettings == null ? "" :
this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}</Typography>
</div>
</ButtonBase>
<ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()}
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
>
<SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}>
<Typography display="block" variant="body2" noWrap>Output channels</Typography>
<Typography display="block" variant="caption" color="textSecondary" noWrap>{
this.state.jackSettings == null ? "" :
this.state.jackSettings.getAudioOutputDisplayValue(this.state.jackConfiguration)}</Typography>
</div>
</ButtonBase>
*/}
<Divider /> <Divider />
<div > <div >
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">MIDI</Typography> <Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">MIDI</Typography>
+1 -1
View File
@@ -372,7 +372,7 @@ const ToobParametricEqView =
])} ])}
{panel("", [ {panel("", [
controls.gain controls.gain
])} ])}
{divider()} {divider()}
{panel("Low", {panel("Low",
[controls.low_level, [controls.low_level,