From 59263ee715f7d4f838051f79faa98918c9bc26c4 Mon Sep 17 00:00:00 2001
From: "Robin E. R. Davies"
Date: Tue, 27 Jan 2026 10:10:36 -0500
Subject: [PATCH] Sync
---
PiPedalCommon/src/include/json.hpp | 11 +
PiPedalCommon/src/json.cpp | 6 +-
src/AlsaDriver.cpp | 337 +++++--
src/AlsaDriverTest.cpp | 8 +-
src/AudioDriver.hpp | 24 +-
src/AudioHost.cpp | 116 ++-
src/AudioHost.hpp | 3 +-
src/Banks.hpp | 2 +-
src/CMakeLists.txt | 1 +
src/ChannelRouterSettings.cpp | 151 ++++
src/ChannelRouterSettings.hpp | 107 +++
src/DummyAudioDriver.cpp | 202 ++---
src/IHost.hpp | 4 +-
src/JackConfiguration.hpp | 1 -
src/Lv2Pedalboard.cpp | 4 +-
src/Pedalboard.cpp | 18 +-
src/Pedalboard.hpp | 47 +-
src/PiLatencyMain.cpp | 8 +-
src/PiPedalCommon.hpp | 32 +
src/PiPedalModel.cpp | 52 +-
src/PiPedalModel.hpp | 7 +-
src/PiPedalSocket.cpp | 16 +-
src/PluginHost.cpp | 7 +-
src/PluginHost.hpp | 22 +-
src/RingBufferReader.hpp | 20 +-
src/Storage.cpp | 229 ++++-
src/Storage.hpp | 20 +-
todo.txt | 1 +
vite/src/pipedal/ChannelMixerSettings.tsx | 55 --
.../pipedal/ChannelMixerSettingsDialog.tsx | 524 -----------
vite/src/pipedal/ChannelRouterSettings.tsx | 64 ++
.../pipedal/ChannelRouterSettingsDialog.tsx | 853 ++++++++++++++++++
...sx => ChannelRouterSettingsHelpDialog.tsx} | 21 +-
...Control.tsx => ChannelRouterUiControl.tsx} | 5 +-
vite/src/pipedal/Pedalboard.tsx | 45 +-
vite/src/pipedal/PiPedalModel.tsx | 45 +-
vite/src/pipedal/SettingsDialog.tsx | 36 +-
vite/src/pipedal/ToobParametricEqView.tsx | 2 +-
38 files changed, 2127 insertions(+), 979 deletions(-)
create mode 100644 src/ChannelRouterSettings.cpp
create mode 100644 src/ChannelRouterSettings.hpp
create mode 100644 src/PiPedalCommon.hpp
delete mode 100644 vite/src/pipedal/ChannelMixerSettings.tsx
delete mode 100644 vite/src/pipedal/ChannelMixerSettingsDialog.tsx
create mode 100644 vite/src/pipedal/ChannelRouterSettings.tsx
create mode 100644 vite/src/pipedal/ChannelRouterSettingsDialog.tsx
rename vite/src/pipedal/{ChannelMixerSettingsHelpDialog.tsx => ChannelRouterSettingsHelpDialog.tsx} (80%)
rename vite/src/pipedal/{ChannelMixerUiControl.tsx => ChannelRouterUiControl.tsx} (97%)
diff --git a/PiPedalCommon/src/include/json.hpp b/PiPedalCommon/src/include/json.hpp
index 7792535..48cb13c 100644
--- a/PiPedalCommon/src/include/json.hpp
+++ b/PiPedalCommon/src/include/json.hpp
@@ -57,6 +57,17 @@
} \
;
+
+#define JSON_GETTER_SETTER_REF(name) \
+ const decltype(name##_) &name() const { return name##_; } \
+ decltype(name##_) &name() { return name##_; } \
+ void name(const decltype(name##_) &value) { name##_ = value; }
+
+#define JSON_GETTER_SETTER(name) \
+ decltype(name##_) name() const { return name##_; } \
+ void name(decltype(name##_) value) { name##_ = value; }
+
+
namespace pipedal
{
class JsonException: public std::runtime_error {
diff --git a/PiPedalCommon/src/json.cpp b/PiPedalCommon/src/json.cpp
index d486bf8..ca8e86e 100644
--- a/PiPedalCommon/src/json.cpp
+++ b/PiPedalCommon/src/json.cpp
@@ -562,8 +562,8 @@ void json_reader::skip_array()
void json_reader::skip_object()
{
- int c;
consume('{');
+ int c;
while (true)
{
c = peek();
@@ -571,11 +571,11 @@ void json_reader::skip_object()
if (c == '}')
{
c = get();
- break;
+ return;
}
skip_string(); // name.
consume(':');
- skip_object();
+ skip_property();
if (peek() == ',')
{
consume(',');
diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp
index c86c554..92472d6 100644
--- a/src/AlsaDriver.cpp
+++ b/src/AlsaDriver.cpp
@@ -23,6 +23,7 @@
*/
#include "pch.h"
+#include "PiPedalCommon.hpp"
#include "util.hpp"
#include
#include "Finally.hpp"
@@ -39,6 +40,7 @@
#include "CrashGuard.hpp"
#include
#include
+#include "ChannelRouterSettings.hpp"
#include "CpuUse.hpp"
@@ -328,11 +330,34 @@ namespace pipedal
bool inputSwapped = false;
bool outputSwapped = false;
- std::vector activeCaptureBuffers;
- std::vector activePlaybackBuffers;
+ std::vector> allocatedBuffers;
- std::vector captureBuffers;
- std::vector playbackBuffers;
+ std::set usedOutputChannels;
+ std::vector deviceCaptureBuffers;
+ std::vector devicePlaybackBuffers;
+ float *zeroInputBuffer = nullptr;
+ float *discardOutputBuffer = nullptr;
+ std::vector mainCaptureBuffers;
+ std::vector mainPlaybackBuffers;
+
+ std::vector auxCaptureBuffers;
+ std::vector auxPlaybackBuffers;
+
+ std::vector sendCaptureBuffers;
+ std::vector sendPlaybackBuffers;
+
+ struct OutputBufferMix {
+ size_t outputChannel = 0;
+ std::vector inputBuffers;
+ float *outputBuffer = nullptr;
+ };
+
+ std::vector outputBufferMixes;
+
+
+
+ std::vector deviceCaptureBuffers;
+ std::vector devicePlaybackBuffers;
std::vector rawCaptureBuffer;
std::vector rawPlaybackBuffer;
@@ -742,7 +767,7 @@ namespace pipedal
{
int32_t *p = getCaptureBuffer(rawCaptureBuffer);
- std::vector &buffers = this->captureBuffers;
+ std::vector &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
for (size_t frame = 0; frame < frames; ++frame)
{
@@ -760,7 +785,7 @@ namespace pipedal
{
float *p = getCaptureBuffer(rawCaptureBuffer);
- std::vector &buffers = this->captureBuffers;
+ std::vector &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
for (size_t frame = 0; frame < frames; ++frame)
{
@@ -776,7 +801,7 @@ namespace pipedal
{
int16_t *p = getCaptureBuffer(rawCaptureBuffer);
- std::vector &buffers = this->captureBuffers;
+ std::vector &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr double scale = 1.0f / (std::numeric_limits::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -792,7 +817,7 @@ namespace pipedal
{
int16_t *p = getCaptureBuffer(rawCaptureBuffer);
- std::vector &buffers = this->captureBuffers;
+ std::vector &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -809,7 +834,7 @@ namespace pipedal
{
int32_t *p = getCaptureBuffer(rawCaptureBuffer);
- std::vector &buffers = this->captureBuffers;
+ std::vector &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -825,7 +850,7 @@ namespace pipedal
{
uint8_t *p = getCaptureBuffer(rawCaptureBuffer);
- std::vector &buffers = this->captureBuffers;
+ std::vector &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits::max() + 1LL);
for (size_t frame = 0; frame < frames; ++frame)
@@ -842,7 +867,7 @@ namespace pipedal
{
uint8_t *p = (uint8_t *)rawCaptureBuffer.data();
- std::vector &buffers = this->captureBuffers;
+ std::vector &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits::max() + 1LL);
for (size_t frame = 0; frame < frames; ++frame)
@@ -859,7 +884,7 @@ namespace pipedal
{
int32_t *p = (int32_t *)rawCaptureBuffer.data();
- std::vector &buffers = this->captureBuffers;
+ std::vector &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (0x00FFFFFFL + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -875,7 +900,7 @@ namespace pipedal
{
int32_t *p = (int32_t *)rawCaptureBuffer.data();
- std::vector &buffers = this->captureBuffers;
+ std::vector &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (0x00FFFFFFL + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -891,7 +916,7 @@ namespace pipedal
{
int32_t *p = (int32_t *)rawCaptureBuffer.data();
- std::vector &buffers = this->captureBuffers;
+ std::vector &buffers = this->deviceCaptureBuffers;
int channels = this->captureChannels;
constexpr float scale = 1.0f / (std::numeric_limits::max() + 1L);
for (size_t frame = 0; frame < frames; ++frame)
@@ -907,7 +932,7 @@ namespace pipedal
{
int16_t *p = (int16_t *)rawPlaybackBuffer.data();
- std::vector &buffers = this->playbackBuffers;
+ std::vector &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr float scale = std::numeric_limits::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -927,7 +952,7 @@ namespace pipedal
{
int16_t *p = (int16_t *)rawPlaybackBuffer.data();
- std::vector &buffers = this->playbackBuffers;
+ std::vector &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr float scale = std::numeric_limits::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -947,7 +972,7 @@ namespace pipedal
{
int32_t *p = (int32_t *)rawPlaybackBuffer.data();
- std::vector &buffers = this->playbackBuffers;
+ std::vector &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -969,7 +994,7 @@ namespace pipedal
int32_t *p = (int32_t *)rawPlaybackBuffer.data();
- std::vector &buffers = this->playbackBuffers;
+ std::vector &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = 0x00FFFFFF;
for (size_t frame = 0; frame < frames; ++frame)
@@ -991,7 +1016,7 @@ namespace pipedal
int32_t *p = (int32_t *)rawPlaybackBuffer.data();
- std::vector &buffers = this->playbackBuffers;
+ std::vector &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = 0x00FFFFFF;
for (size_t frame = 0; frame < frames; ++frame)
@@ -1011,7 +1036,7 @@ namespace pipedal
{
int32_t *p = (int32_t *)rawPlaybackBuffer.data();
- std::vector &buffers = this->playbackBuffers;
+ std::vector &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -1031,7 +1056,7 @@ namespace pipedal
{
uint8_t *p = (uint8_t *)rawPlaybackBuffer.data();
- std::vector &buffers = this->playbackBuffers;
+ std::vector &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -1056,7 +1081,7 @@ namespace pipedal
{
uint8_t *p = (uint8_t *)rawPlaybackBuffer.data();
- std::vector &buffers = this->playbackBuffers;
+ std::vector &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
constexpr double scale = std::numeric_limits::max();
for (size_t frame = 0; frame < frames; ++frame)
@@ -1082,7 +1107,7 @@ namespace pipedal
{
float *p = (float *)rawPlaybackBuffer.data();
- std::vector &buffers = this->playbackBuffers;
+ std::vector &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
for (size_t frame = 0; frame < frames; ++frame)
{
@@ -1097,7 +1122,7 @@ namespace pipedal
{
float *p = (float *)rawPlaybackBuffer.data();
- std::vector &buffers = this->playbackBuffers;
+ std::vector &buffers = this->devicePlaybackBuffers;
int channels = this->playbackChannels;
for (size_t frame = 0; frame < frames; ++frame)
{
@@ -1127,9 +1152,9 @@ namespace pipedal
}
}
- JackChannelSelection channelSelection;
+ ChannelSelection channelSelection;
bool open = false;
- virtual void Open(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
+ virtual void Open(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection)
{
terminateAudio_ = false;
if (open)
@@ -1249,7 +1274,7 @@ namespace pipedal
rawCaptureBuffer.resize(captureFrameSize * bufferSize * 2);
memset(rawCaptureBuffer.data(), 0, rawCaptureBuffer.size());
- AllocateBuffers(captureBuffers, captureChannels);
+ AllocateBuffers(deviceCaptureBuffers, captureChannels);
}
virtual std::string GetConfigurationDescription()
@@ -1260,8 +1285,15 @@ namespace pipedal
<< ", " << GetAlsaFormatDescription(this->captureFormat)
<< ", " << this->sampleRate
<< ", " << this->bufferSize << "x" << this->numberOfBuffers
- << ", in: " << this->InputBufferCount() << "/" << this->captureChannels
- << ", out: " << this->OutputBufferCount() << "/" << this->playbackChannels);
+ << ", " << "device in: " << this->DeviceInputBufferCount()
+ << ", " << "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;
}
void PreparePlaybackFunctions(snd_pcm_format_t playbackFormat)
@@ -1321,10 +1353,10 @@ namespace pipedal
rawPlaybackBuffer.resize(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};
@@ -1717,7 +1749,7 @@ namespace pipedal
}
return 0;
}
- void AudioThread()
+ PIPEDAL_NON_INLINE void AudioThread()
{
SetThreadName("alsaDriver");
@@ -1797,6 +1829,19 @@ namespace pipedal
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);
cpuUse.AddSample(ProfileCategory::Driver);
// process.
@@ -1824,9 +1869,9 @@ namespace pipedal
{
this->driverHost->OnAlsaDriverStopped();
// 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)
{
pBuffer[j] = 0;
@@ -1836,7 +1881,7 @@ namespace pipedal
{
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);
}
}
@@ -1849,63 +1894,129 @@ namespace pipedal
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)
+ PIPEDAL_NON_INLINE void AllocateInputChannels(
+ const std::vector &channelSelection,
+ std::vector &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 &channelSelection,
+ std::vector &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;
- virtual void Activate()
+ PIPEDAL_NON_INLINE virtual void Activate()
{
if (activated)
{
throw PiPedalStateException("Already activated.");
}
+
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);
- if (sourceIndex >= captureBuffers.size())
- {
- Lv2Log::error(SS("Invalid audio input port: " << x));
- }
- else
- {
- this->activeCaptureBuffers[ix++] = this->captureBuffers[sourceIndex];
- }
+ deviceCaptureBuffers[i] = AllocateAudioBuffer();
+ }
+ devicePlaybackBuffers.resize(playbackChannels);
+ for (size_t i = 0; i < playbackChannels; ++i)
+ {
+ devicePlaybackBuffers[i] = AllocateAudioBuffer();
}
- 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];
- }
- }
+ AllocateInputChannels(
+ channelSelection.mainInputChannels(),
+ this->mainCaptureBuffers);
+ AllocateOutputChannels(
+ channelSelection.mainOutputChannels(),
+ this->mainPlaybackBuffers
+ );
+ AllocateInputChannels(
+ channelSelection.auxInputChannels(),
+ this->auxCaptureBuffers);
+ AllocateOutputChannels(
+ channelSelection.auxOutputChannels(),
+ this->auxPlaybackBuffers
+ );
+ AllocateInputChannels(
+ channelSelection.sendInputChannels(),
+ this->sendCaptureBuffers);
+ AllocateOutputChannels(
+ channelSelection.sendOutputChannels(),
+ this->sendPlaybackBuffers
+ );
audioThread = std::make_unique([this]()
{ AudioThread(); });
@@ -1941,10 +2052,43 @@ namespace pipedal
this->alsaSequencer = alsaSequencer;
}
- virtual size_t InputBufferCount() const { return activeCaptureBuffers.size(); }
- virtual float *GetInputBuffer(size_t channel) override
+ virtual size_t DeviceInputBufferCount() const 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
@@ -1956,27 +2100,30 @@ namespace pipedal
return this->midiEvents.data();
}
- virtual size_t OutputBufferCount() const { return activePlaybackBuffers.size(); }
- virtual float *GetOutputBuffer(size_t channel) override
+ virtual size_t MainOutputBufferCount() const { return mainPlaybackBuffers.size(); }
+ virtual float *GetMainOutputBuffer(size_t channel) override
{
- return activePlaybackBuffers[channel];
+ return mainPlaybackBuffers[channel];
}
- void FreeBuffers(std::vector &buffer)
- {
- for (size_t i = 0; i < buffer.size(); ++i)
- {
- delete[] buffer[i];
- buffer[i] = 0;
- }
- buffer.clear();
+ float* AllocateAudioBuffer() {
+ std::vector buffer;
+ buffer.resize(this->bufferSize);
+ float*pBuffer = buffer.data();
+ allocatedBuffers.push_back(std::move(buffer));
+ return pBuffer;
}
void DeleteBuffers()
{
- activeCaptureBuffers.clear();
- activePlaybackBuffers.clear();
- FreeBuffers(this->playbackBuffers);
- FreeBuffers(this->captureBuffers);
+ mainCaptureBuffers.clear();
+ mainPlaybackBuffers.clear();
+ auxCaptureBuffers.clear();
+ auxPlaybackBuffers.clear();
+ sendCaptureBuffers.clear();
+ sendPlaybackBuffers.clear();
+ zeroInputBuffer = nullptr;
+ discardOutputBuffer = nullptr;
+ allocatedBuffers.clear();
}
virtual void Close()
{
@@ -2241,7 +2388,7 @@ namespace pipedal
{
value += 1.0f * (c) / (128.0 * 256.0 * 256.0);
}
- this->playbackBuffers[c][i] = value;
+ this->devicePlaybackBuffers[c][i] = value;
}
}
@@ -2257,7 +2404,7 @@ namespace pipedal
for (size_t c = 0; c < captureChannels; ++c)
{
float error =
- this->captureBuffers[c][i] - this->playbackBuffers[c][i];
+ this->deviceCaptureBuffers[c][i] - this->devicePlaybackBuffers[c][i];
assert(std::abs(error) < 4e-5);
}
diff --git a/src/AlsaDriverTest.cpp b/src/AlsaDriverTest.cpp
index 2508570..129fa37 100644
--- a/src/AlsaDriverTest.cpp
+++ b/src/AlsaDriverTest.cpp
@@ -218,16 +218,16 @@ public:
if (testType == TestType::NullTest) return;
- size_t inputs = audioDriver->InputBufferCount();
- size_t outputs = audioDriver->OutputBufferCount();
+ size_t inputs = audioDriver->MainInputBufferCount();
+ size_t outputs = audioDriver->MainOutputBufferCount();
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)
{
- outputBuffers[i] = audioDriver->GetOutputBuffer(i);
+ outputBuffers[i] = audioDriver->GetMainOutputBuffer(i);
}
if (this->testType == TestType::Oscillator)
{
diff --git a/src/AudioDriver.hpp b/src/AudioDriver.hpp
index 3899b05..ea88c28 100644
--- a/src/AudioDriver.hpp
+++ b/src/AudioDriver.hpp
@@ -35,6 +35,8 @@ namespace pipedal {
using ProcessCallback = std::function;
+ class ChannelSelection;
+
class AudioDriverHost {
public:
@@ -58,14 +60,26 @@ namespace pipedal {
virtual size_t GetMidiInputEventCount() = 0;
virtual MidiEvent*GetMidiEvents() = 0;
- virtual size_t InputBufferCount() const = 0;
- virtual float*GetInputBuffer(size_t channel) = 0;
+ virtual size_t DeviceInputBufferCount() const = 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 float*GetOutputBuffer(size_t channe) = 0;
+ virtual size_t AuxInputBufferCount() const = 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 Activate() = 0;
virtual void Deactivate() = 0;
diff --git a/src/AudioHost.cpp b/src/AudioHost.cpp
index a744f84..c1eb2ca 100644
--- a/src/AudioHost.cpp
+++ b/src/AudioHost.cpp
@@ -18,6 +18,7 @@
// 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 "PiPedalCommon.hpp"
#include "AudioHost.hpp"
#include "util.hpp"
#include
@@ -519,12 +520,17 @@ private:
SystemMidiBinding rebootMidiBinding;
SystemMidiBinding shutdownMidiBinding;
- JackChannelSelection channelSelection;
+ ChannelSelection channelSelection;
std::atomic active = false;
std::atomic audioStopped = false;
std::atomic isDummyAudioDriver = false;
std::shared_ptr currentPedalboard;
+ std::shared_ptr currentMainInsertPedalboard;
+ std::shared_ptr currentAuxInsertPedalboard;
+
+
+
std::vector> activePedalboards; // pedalboards that have been sent to the audio queue.
Lv2Pedalboard *realtimeActivePedalboard = nullptr;
@@ -620,9 +626,9 @@ private:
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)
{
ZeroBuffer(out, nframes);
@@ -1173,9 +1179,9 @@ private:
float *inputBuffers[4];
float *outputBuffers[4];
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)
{
buffersValid = false;
@@ -1183,11 +1189,11 @@ private:
}
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)
{
buffersValid = false;
@@ -1195,7 +1201,7 @@ private:
}
outputBuffers[i] = output;
}
- outputBuffers[audioDriver->OutputBufferCount()] = nullptr;
+ outputBuffers[audioDriver->MainOutputBufferCount()] = nullptr;
if (buffersValid)
{
@@ -1670,7 +1676,7 @@ public:
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);
@@ -1692,11 +1698,6 @@ public:
this->audioDriver = std::unique_ptr(CreateAlsaDriver(this));
}
- if (channelSelection.GetInputAudioPorts().size() == 0 || channelSelection.GetOutputAudioPorts().size() == 0)
- {
- return;
- }
-
this->currentSample = 0;
this->underruns = 0;
@@ -1707,7 +1708,7 @@ public:
this->realtimeReader.Reset();
this->realtimeWriter.Reset();
- this->channelSelection = channelSelection;
+ this->channelSelection = channelSelection_;
StartReaderThread();
@@ -1795,6 +1796,7 @@ public:
}
}
+
virtual void SetPluginPreset(uint64_t instanceId, const std::vector &values)
{
std::lock_guard guard(mutex);
@@ -1896,6 +1898,49 @@ public:
pendingSnapshots.clear();
}
+ PIPEDAL_NON_INLINE RealtimePedalboardItemIndex GetRealtimeItemIndex(int64_t instanceId)
+ {
+ Pedalboard::InstanceType instanceType = Pedalboard::GetInstanceTypeFromInstanceId(instanceId);
+ int64_t index = -1;
+ switch (instanceType)
+ {
+ case Pedalboard::InstanceType::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::InstanceType::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::InstanceType::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 &instanceIds)
{
std::lock_guard guard(mutex);
@@ -1917,7 +1962,7 @@ public:
auto effect = this->currentPedalboard->GetEffect(instanceId);
if (effect)
{
- int index = this->currentPedalboard->GetIndexOfInstanceId(instanceIds[i]);
+ RealtimePedalboardItemIndex index = this->GetRealtimeItemIndex(instanceIds[i]);
vuConfig->enabledIndexes.push_back(index);
VuUpdate v;
v.instanceId_ = instanceId;
@@ -1929,20 +1974,41 @@ public:
vuConfig->vuUpdateWorkingData.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;
vuConfig->enabledIndexes.push_back(index);
+
v.instanceId_ = instanceId;
- if (instanceId == Pedalboard::INPUT_VOLUME_ID)
+ size_t nChannels = 0;
+
+ switch (instanceId)
{
- v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfInputAudioChannels() > 1;
- }
- else
- {
- v.isStereoInput_ = v.isStereoOutput_ = this->pHost->GetNumberOfOutputAudioChannels() > 1;
+ case Pedalboard::START_CONTROL_ID:
+ nChannels = this->pHost->GetChannelSelection().mainInputChannels().size();
+ break;
+ case Pedalboard::END_CONTROL_ID:
+ 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->vuUpdateResponseData.push_back(v);
}
diff --git a/src/AudioHost.hpp b/src/AudioHost.hpp
index f582547..d5af186 100644
--- a/src/AudioHost.hpp
+++ b/src/AudioHost.hpp
@@ -32,6 +32,7 @@
#include "Promise.hpp"
#include "json_variant.hpp"
#include "RealtimeMidiEventType.hpp"
+#include "ChannelRouterSettings.hpp"
namespace pipedal
{
@@ -227,7 +228,7 @@ namespace pipedal
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 SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration) = 0;
diff --git a/src/Banks.hpp b/src/Banks.hpp
index 4522426..18951f3 100644
--- a/src/Banks.hpp
+++ b/src/Banks.hpp
@@ -240,7 +240,7 @@ namespace pipedal
{
// zero length? We can never have a zero-length bank.
// Add a default preset and make it the selected preset.
- Pedalboard pedalboard = Pedalboard::MakeDefault();
+ Pedalboard pedalboard = Pedalboard::MakeDefault(Pedalboard::InstanceType::MainPedalboard);
this->addPreset(pedalboard);
newSelection = presets_[0]->instanceId();
}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 97a2ffb..e8f0bae 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -204,6 +204,7 @@ else()
endif()
set (PIPEDAL_SOURCES
+ ChannelRouterSettings.cpp ChannelRouterSettings.hpp
CrashGuard.cpp CrashGuard.hpp
ModTemplateGenerator.cpp ModTemplateGenerator.hpp
WebServerMod.cpp WebServerMod.hpp
diff --git a/src/ChannelRouterSettings.cpp b/src/ChannelRouterSettings.cpp
new file mode 100644
index 0000000..0b9e9be
--- /dev/null
+++ b/src/ChannelRouterSettings.cpp
@@ -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
+
+using namespace pipedal;
+
+static uint64_t countChannels(const std::vector &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&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&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::InstanceType::MainInsert)
+, auxInserts_(Pedalboard::InstanceType::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()
diff --git a/src/ChannelRouterSettings.hpp b/src/ChannelRouterSettings.hpp
new file mode 100644
index 0000000..885b6e9
--- /dev/null
+++ b/src/ChannelRouterSettings.hpp
@@ -0,0 +1,107 @@
+// 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
+#include "json.hpp"
+#include "Pedalboard.hpp"
+#include
+
+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 mainInputChannels_ = {1, 1};
+ std::vector mainOutputChannels_ = {0, 1};
+ Pedalboard mainInserts_;
+
+ std::vector auxInputChannels_ = {-1, -1};
+ std::vector auxOutputChannels_ = {-1, -1};
+ Pedalboard auxInserts_;
+
+ std::vector sendInputChannels_ = {-1, -1};
+ std::vector sendOutputChannels_ = {-1, -1};
+
+ std::vector controlValues_;
+
+ public:
+ using self = ChannelRouterSettings;
+ using ptr = std::shared_ptr;
+
+ 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&mainInputChannels() const { return mainInputChannels_; }
+ const std::vector&mainOutputChannels() const { return mainOutputChannels_; }
+ const std::vector&auxInputChannels() const { return auxInputChannels_; }
+ const std::vector&auxOutputChannels() const { return auxOutputChannels_; }
+ const std::vector&sendInputChannels() const { return sendInputChannels_; }
+ const std::vector&sendOutputChannels() const { return sendOutputChannels_; }
+
+ private:
+ void normalizeChannelSelection();
+
+ std::vector mainInputChannels_;
+ std::vector mainOutputChannels_;
+ std::vector auxInputChannels_;
+ std::vector auxOutputChannels_;
+ std::vector sendInputChannels_;
+ std::vector sendOutputChannels_;
+ };
+
+}
\ No newline at end of file
diff --git a/src/DummyAudioDriver.cpp b/src/DummyAudioDriver.cpp
index dd2301b..0d298d7 100644
--- a/src/DummyAudioDriver.cpp
+++ b/src/DummyAudioDriver.cpp
@@ -1,7 +1,7 @@
/*
* 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
* this software and associated documentation files (the "Software"), to deal in
@@ -23,6 +23,7 @@
*/
#include "pch.h"
+#include "PiPedalCommon.hpp"
#include "util.hpp"
#include
#include
@@ -39,6 +40,7 @@
#include "ss.hpp"
#include "SchedulerPriority.hpp"
#include "CrashGuard.hpp"
+#include "ChannelRouterSettings.hpp"
#include "CpuUse.hpp"
@@ -85,14 +87,19 @@ namespace pipedal
uint32_t captureFrameSize = 0;
- std::vector activeCaptureBuffers;
- std::vector activePlaybackBuffers;
+ std::vector> allocatedBuffers;
+ std::vector mainCaptureBuffers;
+ std::vector mainPlaybackBuffers;
- std::vector captureBuffers;
- std::vector playbackBuffers;
+ std::vector auxCaptureBuffers;
+ std::vector auxPlaybackBuffers;
+
+ std::vector sendCaptureBuffers;
+ std::vector sendPlaybackBuffers;
+
+ std::vector deviceCaptureBuffers;
+ std::vector devicePlaybackBuffers;
- uint8_t *rawCaptureBuffer = nullptr;
- uint8_t *rawPlaybackBuffer = nullptr;
AudioDriverHost *driverHost = nullptr;
uint32_t channels = 2;
@@ -145,7 +152,7 @@ namespace pipedal
std::atomic terminateAudio_ = false;
- void terminateAudio(bool terminate)
+ PIPEDAL_NON_INLINE void terminateAudio(bool terminate)
{
this->terminateAudio_ = terminate;
}
@@ -161,32 +168,28 @@ namespace pipedal
}
private:
- void AllocateBuffers(std::vector &buffers, size_t n)
+ PIPEDAL_NON_INLINE void AllocateBuffers(std::vector &buffers, size_t n)
{
buffers.resize(n);
for (size_t i = 0; i < n; ++i)
{
- buffers[i] = new float[this->bufferSize];
- for (size_t j = 0; j < this->bufferSize; ++j)
- {
- buffers[i][j] = 0;
- }
+ buffers[i] = AllocateAudioBuffer();
}
}
- virtual size_t GetMidiInputEventCount() override
+ PIPEDAL_NON_INLINE virtual size_t GetMidiInputEventCount() override
{
return midiEventCount;
}
- virtual MidiEvent *GetMidiEvents() override
+ PIPEDAL_NON_INLINE virtual MidiEvent *GetMidiEvents() override
{
return this->midiEvents.data();
}
- JackChannelSelection channelSelection;
+ ChannelSelection channelSelection;
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;
if (open)
@@ -208,7 +211,7 @@ namespace pipedal
throw;
}
}
- virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) override
+ PIPEDAL_NON_INLINE virtual void SetAlsaSequencer(AlsaSequencer::ptr alsaSequencer) override
{
this->alsaSequencer = alsaSequencer;
}
@@ -220,23 +223,28 @@ namespace pipedal
<< ", " << "Native float"
<< ", " << this->sampleRate
<< ", " << this->bufferSize << "x" << this->numberOfBuffers
- << ", in: " << this->InputBufferCount() << "/" << this->captureChannels
- << ", out: " << this->OutputBufferCount() << "/" << this->playbackChannels);
+ << ", " << "device in: " << this->DeviceInputBufferCount()
+ << ", " << "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;
}
- void OpenAudio(const JackServerSettings &jackServerSettings, const JackChannelSelection &channelSelection)
+ void OpenAudio(const JackServerSettings &jackServerSettings, const ChannelSelection &channelSelection)
{
int err;
this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers();
this->bufferSize = jackServerSettings.GetBufferSize();
- AllocateBuffers(captureBuffers, channels);
- AllocateBuffers(playbackBuffers, channels);
}
- std::jthread *audioThread;
+ std::unique_ptr audioThread;
bool audioRunning;
bool block = false;
@@ -303,7 +311,7 @@ namespace pipedal
this->driverHost->OnProcess(framesRead);
/// 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;
- 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;
virtual void Activate()
@@ -344,41 +336,17 @@ namespace pipedal
}
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;
- 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 = std::make_unique([this]()
{ AudioThread(); });
}
@@ -393,7 +361,7 @@ namespace pipedal
if (audioThread)
{
this->audioThread->join();
- this->audioThread = 0;
+ this->audioThread = nullptr;
}
Lv2Log::debug("Audio thread joined.");
}
@@ -402,46 +370,64 @@ namespace pipedal
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 float *GetInputBuffer(size_t channel)
+ virtual size_t MainInputBufferCount() const override { return mainCaptureBuffers.size(); }
+ virtual float *GetMainInputBuffer(size_t channel) override
{
- return activeCaptureBuffers[channel];
+ return mainCaptureBuffers[channel];
}
- virtual size_t OutputBufferCount() const { return activePlaybackBuffers.size(); }
- virtual float *GetOutputBuffer(size_t channel)
+ virtual size_t MainOutputBufferCount() const { return mainPlaybackBuffers.size(); }
+ virtual float *GetMainOutputBuffer(size_t channel) override
{
- return activePlaybackBuffers[channel];
+ return mainPlaybackBuffers[channel];
}
- void FreeBuffers(std::vector &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)
- {
- // delete[] buffer[i];
- buffer[i] = 0;
- }
- buffer.clear();
+ return auxCaptureBuffers[channel];
}
- void DeleteBuffers()
+
+ virtual size_t AuxOutputBufferCount() const override { return auxPlaybackBuffers.size(); }
+ virtual float *GetAuxOutputBuffer(size_t channel) override
{
- activeCaptureBuffers.clear();
- activePlaybackBuffers.clear();
- FreeBuffers(this->playbackBuffers);
- FreeBuffers(this->captureBuffers);
- if (rawCaptureBuffer)
- {
- delete[] rawCaptureBuffer;
- rawCaptureBuffer = nullptr;
- }
- if (rawPlaybackBuffer)
- {
- delete[] rawPlaybackBuffer;
- rawPlaybackBuffer = nullptr;
- }
+ return auxPlaybackBuffers[channel];
}
- 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(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)
{
@@ -453,18 +439,18 @@ namespace pipedal
DeleteBuffers();
}
- virtual float CpuUse()
+ PIPEDAL_NON_INLINE virtual float CpuUse()
{
return 0;
}
- virtual float CpuOverhead()
+ PIPEDAL_NON_INLINE virtual float CpuOverhead()
{
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);
}
diff --git a/src/IHost.hpp b/src/IHost.hpp
index ab02649..48c7c52 100644
--- a/src/IHost.hpp
+++ b/src/IHost.hpp
@@ -26,6 +26,7 @@ namespace pipedal {
class Lv2PluginInfo;
class IEffect;
class HostWorkerThread;
+ class ChannelSelection;
class IHost
{
@@ -43,13 +44,12 @@ namespace pipedal {
virtual size_t GetAtomBufferSize() const = 0;
virtual bool HasMidiInputChannel() const = 0;
- virtual int GetNumberOfInputAudioChannels() const = 0;
- virtual int GetNumberOfOutputAudioChannels() const = 0;
virtual std::shared_ptr GetPluginInfo(const std::string &uri) const = 0;
virtual IEffect *CreateEffect(PedalboardItem &pedalboard) = 0;
virtual std::string GetPluginStoragePath() const = 0;
+ virtual const ChannelSelection&GetChannelSelection() const = 0;
};
}
\ No newline at end of file
diff --git a/src/JackConfiguration.hpp b/src/JackConfiguration.hpp
index b76abb0..a6a9bc4 100644
--- a/src/JackConfiguration.hpp
+++ b/src/JackConfiguration.hpp
@@ -124,5 +124,4 @@ namespace pipedal
DECLARE_JSON_MAP(JackChannelSelection);
};
-
} // namespace.
\ No newline at end of file
diff --git a/src/Lv2Pedalboard.cpp b/src/Lv2Pedalboard.cpp
index 4c750c8..34d0055 100644
--- a/src/Lv2Pedalboard.cpp
+++ b/src/Lv2Pedalboard.cpp
@@ -574,7 +574,7 @@ void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp
{
int index = vuConfiguration->enabledIndexes[i];
VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i];
- if (index == Pedalboard::INPUT_VOLUME_ID)
+ if (index == Pedalboard::START_CONTROL_ID)
{
if (this->pedalboardInputBuffers.size() > 1)
{
@@ -588,7 +588,7 @@ void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp
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)
{
diff --git a/src/Pedalboard.cpp b/src/Pedalboard.cpp
index 97d7f0f..5cdc8fb 100644
--- a/src/Pedalboard.cpp
+++ b/src/Pedalboard.cpp
@@ -210,7 +210,7 @@ PedalboardItem Pedalboard::MakeSplit()
-Pedalboard Pedalboard::MakeDefault()
+Pedalboard Pedalboard::MakeDefault(InstanceType instanceType)
{
// copy insanity. but it happens so rarely.
Pedalboard result;
@@ -510,6 +510,22 @@ Snapshot Pedalboard::MakeSnapshotFromCurrentSettings(const Pedalboard &previousP
}
+Pedalboard::Pedalboard(InstanceType instanceType = InstanceType::MainPedalboard)
+{
+ switch (instanceType)
+ {
+ case InstanceType::MainPedalboard:
+ nextInstanceId_ = 0;
+ break;
+ case InstanceType::MainInsert:
+ nextInstanceId_ = MAIN_INSERT_INSTANCE_BASE;;
+ break;
+ case InstanceType::AuxInsert:
+ nextInstanceId_ = AUX_INSERT_INSTANCE_BASE;;
+ break;
+ }
+}
+
JSON_MAP_BEGIN(ControlValue)
JSON_MAP_REFERENCE(ControlValue,key)
diff --git a/src/Pedalboard.hpp b/src/Pedalboard.hpp
index 173b497..9532efb 100644
--- a/src/Pedalboard.hpp
+++ b/src/Pedalboard.hpp
@@ -230,11 +230,52 @@ class Pedalboard {
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:
+
+ enum class InstanceType {
+ 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 InstanceType GetInstanceTypeFromInstanceId(int64_t instanceId) {
+ if (instanceId == START_CONTROL_ID) return InstanceType::MainPedalboard;
+ if (instanceId == END_CONTROL_ID) return InstanceType::MainPedalboard;
+
+ if (instanceId >= MAIN_INSERT_INSTANCE_BASE) {
+ return InstanceType::MainInsert;
+ } else if (instanceId >= AUX_INSERT_INSTANCE_BASE) {
+ return InstanceType::AuxInsert;
+ } else {
+ return InstanceType::MainPedalboard;
+ }
+ }
+ InstanceType GetInstanceType() const {
+ return GetInstanceTypeFromInstanceId(this->nextInstanceId_); // instanceId is irrelevant here.
+ }
+
+
+
+ Pedalboard(InstanceType instanceType = InstanceType::MainPedalboard);
+
// deep copy, breaking shared pointers.
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 SetItemTitle(int64_t pedalItemId, const std::string &title, const std::string&iconColor);
bool SetItemEnabled(int64_t pedalItemId, bool enabled);
@@ -266,7 +307,7 @@ public:
PedalboardItem MakeSplit();
- static Pedalboard MakeDefault();
+ static Pedalboard MakeDefault(Pedalboard::InstanceType instanceType);
};
#undef GETTER_SETTER_REF
diff --git a/src/PiLatencyMain.cpp b/src/PiLatencyMain.cpp
index 01887e5..edc6d4f 100644
--- a/src/PiLatencyMain.cpp
+++ b/src/PiLatencyMain.cpp
@@ -364,16 +364,16 @@ public:
virtual void OnProcess(size_t nFrames)
{
- size_t inputs = audioDriver->InputBufferCount();
- size_t outputs = audioDriver->OutputBufferCount();
+ size_t inputs = audioDriver->MainInputBufferCount();
+ size_t outputs = audioDriver->MainOutputBufferCount();
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)
{
- outputBuffers[i] = audioDriver->GetOutputBuffer(i);
+ outputBuffers[i] = audioDriver->GetMainOutputBuffer(i);
}
for (size_t i = 0; i < nFrames; ++i)
diff --git a/src/PiPedalCommon.hpp b/src/PiPedalCommon.hpp
new file mode 100644
index 0000000..a464e39
--- /dev/null
+++ b/src/PiPedalCommon.hpp
@@ -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
diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp
index 7e9f4ff..f07b645 100644
--- a/src/PiPedalModel.cpp
+++ b/src/PiPedalModel.cpp
@@ -81,7 +81,7 @@ PiPedalModel::PiPedalModel()
this->updater = Updater::Create();
this->updater->Start();
this->currentUpdateStatus = updater->GetCurrentStatus();
- this->pedalboard = Pedalboard::MakeDefault();
+ this->pedalboard = Pedalboard::MakeDefault(Pedalboard::InstanceType::MainPedalboard);
#if JACK_HOST
this->jackServerSettings = this->storage.GetJackServerSettings(); // to get onboarding flag.
this->jackServerSettings.ReadJackDaemonConfiguration();
@@ -216,6 +216,7 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration)
storage.Initialize();
pluginHost.SetPluginStoragePath(storage.GetPluginUploadDirectory());
+ this->channelRouterSettings = storage.GetChannelRouterSettings();
this->systemMidiBindings = storage.GetSystemMidiBindings();
#if JACK_HOST
@@ -298,7 +299,7 @@ void PiPedalModel::Load()
if (CrashGuard::HasCrashed())
{
// 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::InstanceType::MainPedalboard);
}
else
{
@@ -1422,12 +1423,8 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver)
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->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection);
@@ -1528,21 +1525,6 @@ std::vector PiPedalModel::GetAlsaSequencerPorts()
return result;
}
-void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection)
-{
- {
- std::lock_guard lock(mutex); // copy atomically.
- this->storage.SetJackChannelSelection(channelSelection);
-
- this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection);
-
- CancelAudioRetry();
- }
-
- RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications..
-
- this->FireChannelSelectionChanged(clientId);
-}
void PiPedalModel::FireChannelSelectionChanged(int64_t clientId)
{
@@ -1671,7 +1653,7 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions()
for (int i = 0; i < activeVuSubscriptions.size(); ++i)
{
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);
}
@@ -3330,3 +3312,27 @@ int64_t PiPedalModel::CopyPresetsToBank(int64_t bankInstanceId, const std::vecto
return lastAdded;
}
+
+
+ChannelRouterSettings::ptr PiPedalModel::GetChannelRouterSettings()
+{
+ std::lock_guard lock(mutex);
+ return this->channelRouterSettings;
+}
+
+void PiPedalModel::SetChannelRouterSettings(int64_t clientId,ChannelRouterSettings::ptr &settings)
+{
+ {
+ std::lock_guard 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->FireChannelSelectionChanged(clientId);
+
+}
+
+
diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp
index 897f524..17f1cf9 100644
--- a/src/PiPedalModel.hpp
+++ b/src/PiPedalModel.hpp
@@ -40,6 +40,7 @@
#include "Promise.hpp"
#include "AtomConverter.hpp"
#include "FileEntry.hpp"
+#include "ChannelRouterSettings.hpp"
#include
namespace pipedal
@@ -176,6 +177,8 @@ namespace pipedal
AtomConverter atomConverter; // must be AFTER pluginHost!
Pedalboard pedalboard;
+ ChannelRouterSettings::ptr channelRouterSettings;
+
bool previousPedalboardLoaded = false;
Pedalboard previousPedalboard;
Storage storage;
@@ -397,7 +400,6 @@ namespace pipedal
JackConfiguration GetJackConfiguration();
- void SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection);
JackChannelSelection GetJackChannelSelection();
void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration);
@@ -497,6 +499,9 @@ namespace pipedal
std::vector RequestBankPresets(int64_t bankInstanceId);
int64_t ImportPresetsFromBank(int64_t bankInstanceId, const std::vector &presets);
int64_t CopyPresetsToBank(int64_t bankInstanceId, const std::vector &presets);
+
+ void SetChannelRouterSettings(int64_t clientId, ChannelRouterSettings::ptr &settings);
+ ChannelRouterSettings::ptr GetChannelRouterSettings();
};
} // namespace pipedal.
\ No newline at end of file
diff --git a/src/PiPedalSocket.cpp b/src/PiPedalSocket.cpp
index a3bf46b..85d0624 100644
--- a/src/PiPedalSocket.cpp
+++ b/src/PiPedalSocket.cpp
@@ -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
// this software and associated documentation files (the "Software"), to deal in
@@ -1422,12 +1422,6 @@ public:
this->model.AddNotificationSubscription(shared_from_this());
Reply(replyTo, "ehlo", clientId);
}
- else if (message == "setJackSettings")
- {
- JackChannelSelection jackSettings;
- pReader->read(&jackSettings);
- this->model.SetJackChannelSelection(this->clientId, jackSettings);
- }
else if (message == "setShowStatusMonitor")
{
bool showStatusMonitor;
@@ -1847,6 +1841,14 @@ public:
pReader->read(&args);
auto result = this->model.CopyPresetsToBank(args.bankInstanceId_, args.presets_);
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
{
diff --git a/src/PluginHost.cpp b/src/PluginHost.cpp
index 6973a5b..ca1c3c7 100644
--- a/src/PluginHost.cpp
+++ b/src/PluginHost.cpp
@@ -34,6 +34,7 @@
#include "lv2/urid/urid.h"
#include "lv2/ui/ui.h"
#include "lv2/core/lv2.h"
+#include "ChannelRouterSettings.hpp"
// #include "lv2.h"
#include "lv2/atom/atom.h"
@@ -315,15 +316,15 @@ PluginHost::PluginHost()
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();
if (configuration.isValid())
{
- this->numberOfAudioInputChannels = settings.GetInputAudioPorts().size();
- this->numberOfAudioOutputChannels = settings.GetOutputAudioPorts().size();
this->maxBufferSize = configuration.blockLength();
}
+ this->channelSelection = channelSelection;
}
PluginHost::~PluginHost()
diff --git a/src/PluginHost.hpp b/src/PluginHost.hpp
index 9e0abec..5b31745 100644
--- a/src/PluginHost.hpp
+++ b/src/PluginHost.hpp
@@ -43,6 +43,7 @@
#include "PiPedalUI.hpp"
#include "MapPathFeature.hpp"
#include "ModGui.hpp"
+#include "ChannelRouterSettings.hpp"
namespace pipedal
{
@@ -54,6 +55,7 @@ namespace pipedal
class PluginHost;
class JackConfiguration;
class JackChannelSelection;
+ class ChannelRouterSettings;
#ifndef LV2_PROPERTY_GETSET
#define LV2_PROPERTY_GETSET(name) \
@@ -893,8 +895,8 @@ namespace pipedal
size_t maxBufferSize = 1024;
size_t maxAtomBufferSize = 16 * 1024;
bool hasMidiInputChannel;
- int numberOfAudioInputChannels = 1;
- int numberOfAudioOutputChannels = 1;
+ ChannelSelection channelSelection;
+
double sampleRate = 48000;
std::string vst3CachePath;
@@ -955,13 +957,12 @@ namespace pipedal
private:
// IHost implementation.
- virtual void SetMaxAudioBufferSize(size_t size) { maxBufferSize = size; }
- virtual size_t GetMaxAudioBufferSize() const { return maxBufferSize; }
- virtual size_t GetAtomBufferSize() const { return maxAtomBufferSize; }
- virtual bool HasMidiInputChannel() const { return hasMidiInputChannel; }
- virtual int GetNumberOfInputAudioChannels() const { return numberOfAudioInputChannels; }
- virtual int GetNumberOfOutputAudioChannels() const { return numberOfAudioOutputChannels; }
- virtual LV2_Feature *const *GetLv2Features() const { return (LV2_Feature *const *)&(this->lv2Features[0]); }
+ virtual void SetMaxAudioBufferSize(size_t size) override { maxBufferSize = size; }
+ virtual size_t GetMaxAudioBufferSize() const override { return maxBufferSize; }
+ virtual size_t GetAtomBufferSize() const override { return maxAtomBufferSize; }
+ virtual bool HasMidiInputChannel() const override { return hasMidiInputChannel; }
+ virtual LV2_Feature *const *GetLv2Features() const override { return (LV2_Feature *const *)&(this->lv2Features[0]); }
+ virtual const ChannelSelection &GetChannelSelection() const override { return this->channelSelection; }
public:
virtual MapFeature &GetMapFeature() override { return this->mapFeature; }
@@ -1027,7 +1028,8 @@ namespace pipedal
Urids *urids = 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 GetPluginClass(const std::string &uri) const;
bool is_a(const std::string &class_, const std::string &target_class);
diff --git a/src/RingBufferReader.hpp b/src/RingBufferReader.hpp
index c3260fb..46f8c46 100644
--- a/src/RingBufferReader.hpp
+++ b/src/RingBufferReader.hpp
@@ -96,6 +96,24 @@ namespace pipedal
};
+ struct RealtimePedalboardItemIndex {
+ RealtimePedalboardItemIndex()
+ : instanceType(Pedalboard::InstanceType::MainPedalboard), index(-1)
+ {
+ }
+ RealtimePedalboardItemIndex(
+ Pedalboard::InstanceType instanceType,
+ int index)
+ : instanceType(instanceType), index(index)
+ {
+ }
+ RealtimePedalboardItemIndex(const RealtimePedalboardItemIndex& other) = default;
+ RealtimePedalboardItemIndex& operator=(const RealtimePedalboardItemIndex& other) = default;
+
+ Pedalboard::InstanceType instanceType = Pedalboard::InstanceType::MainPedalboard;
+ int index = -1;
+ };
+
struct RealtimeMidiEventRequest
{
RealtimeMidiEventType eventType;
@@ -159,7 +177,7 @@ namespace pipedal
return &vuUpdateResponseData;
}
- std::vector enabledIndexes;
+ std::vector enabledIndexes;
std::vector vuUpdateWorkingData;
std::vector vuUpdateResponseData;
diff --git a/src/Storage.cpp b/src/Storage.cpp
index 81e283b..83a9524 100644
--- a/src/Storage.cpp
+++ b/src/Storage.cpp
@@ -196,11 +196,11 @@ void Storage::SetDataRoot(const std::filesystem::path &path)
this->dataRoot__audio_uploads = dataRoot / "audio_uploads";
}
-const std::filesystem::path &Storage::GetConfigRoot()
+const std::filesystem::path &Storage::GetConfigRoot() const
{
return this->configRoot;
}
-const std::filesystem::path &Storage::GetDataRoot()
+const std::filesystem::path &Storage::GetDataRoot() const
{
return this->dataRoot;
}
@@ -249,7 +249,6 @@ static void removeFileNoThrow(const std::filesystem::path &path)
}
}
-
void Storage::UpgradeFactoryPresets()
{
auto presetsDirectory = this->GetPresetsDirectory();
@@ -387,8 +386,10 @@ void Storage::Initialize()
LoadWifiConfigSettings();
LoadWifiDirectConfigSettings();
+ this->channelRouterSettings = LoadChannelRouterSettings();
LoadUserSettings();
UpgradeFactoryPresets();
+ UpgradeChannelRouterSettings();
}
void Storage::LoadBank(int64_t instanceId)
@@ -687,40 +688,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.
// Find the last '(' character
size_t pos = name.find_last_of('(');
- if (pos == std::string::npos) {
+ if (pos == std::string::npos)
+ {
return name;
}
// Check if everything between '(' and ')' is digits
- size_t len = name.length()-1;
- if (name[len] != ')')
+ size_t len = name.length() - 1;
+ if (name[len] != ')')
{
return name;
}
bool allDigits = true;
- for (size_t i = pos + 1; i < len - 1; ++i) {
- if (name[i] < '0' || name[i] > '9') {
+ for (size_t i = pos + 1; i < len - 1; ++i)
+ {
+ if (name[i] < '0' || name[i] > '9')
+ {
allDigits = false;
break;
}
}
- if (!allDigits) {
+ if (!allDigits)
+ {
return name;
}
// Remove trailing spaces before the '('
- while (pos > 0 && name[pos - 1] == ' ') {
+ while (pos > 0 && name[pos - 1] == ' ')
+ {
--pos;
}
return name.substr(0, pos);
}
-static std::string makeUniqueName(const std::string &name, const std::set&existingNames)
+static std::string makeUniqueName(const std::string &name, const std::set &existingNames)
{
if (!existingNames.contains(name))
{
@@ -729,9 +736,11 @@ static std::string makeUniqueName(const std::string &name, const std::set &presets)
{
- if (bankIndex.selectedBank() == bankInstanceId) {
+ if (bankIndex.selectedBank() == bankInstanceId)
+ {
throw std::runtime_error("Can't import to self.");
}
- std::set presetsSet { presets.begin(), presets.end()};
+ std::set presetsSet{presets.begin(), presets.end()};
auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId);
std::set existingNames;
- for (auto&preset: this->currentBank.presets()) {
+ for (auto &preset : this->currentBank.presets())
+ {
existingNames.insert(preset->preset().name());
}
BankFile bankFile;
- LoadBankFile(indexEntry.name(),&bankFile);
+ LoadBankFile(indexEntry.name(), &bankFile);
int64_t lastPresetId = -1;
- for (auto &presetEntry: bankFile.presets()) {
- if (presetsSet.contains(presetEntry->instanceId())) {
- std::string uniqueName = makeUniqueName(presetEntry->preset().name(),existingNames);
+ for (auto &presetEntry : bankFile.presets())
+ {
+ if (presetsSet.contains(presetEntry->instanceId()))
+ {
+ std::string uniqueName = makeUniqueName(presetEntry->preset().name(), existingNames);
existingNames.insert(uniqueName);
Pedalboard t = presetEntry->preset();
t.name(uniqueName);
@@ -767,31 +780,35 @@ int64_t Storage::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector
}
int64_t Storage::CopyPresetsToBank(int64_t bankInstanceId, const std::vector &presets)
{
- if (bankIndex.selectedBank() == bankInstanceId) {
+ if (bankIndex.selectedBank() == bankInstanceId)
+ {
throw std::runtime_error("Can't copy to self.");
}
auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId);
BankFile bankFile;
- LoadBankFile(indexEntry.name(),&bankFile);
+ LoadBankFile(indexEntry.name(), &bankFile);
- std::set presetsSet { presets.begin(), presets.end()};
+ std::set presetsSet{presets.begin(), presets.end()};
std::set existingNames;
- for (auto&preset: bankFile.presets()) {
+ for (auto &preset : bankFile.presets())
+ {
existingNames.insert(preset->preset().name());
}
- for (auto &presetEntry: this->currentBank.presets()) {
- if (presetsSet.contains(presetEntry->instanceId())) {
- std::string uniqueName = makeUniqueName(presetEntry->preset().name(),existingNames);
+ for (auto &presetEntry : this->currentBank.presets())
+ {
+ if (presetsSet.contains(presetEntry->instanceId()))
+ {
+ std::string uniqueName = makeUniqueName(presetEntry->preset().name(), existingNames);
existingNames.insert(uniqueName);
Pedalboard t = presetEntry->preset();
t.name(uniqueName);
bankFile.addPreset(t);
}
}
- SaveBankFile(indexEntry.name(),bankFile);
+ SaveBankFile(indexEntry.name(), bankFile);
return -1;
}
@@ -801,17 +818,15 @@ std::vector Storage::RequestBankPresets(int64_t bankInstanceId
std::vector result;
BankFile bankFile;
- LoadBankFile(indexEntry.name(),&bankFile);
- for (auto &preset: bankFile.presets()) {
+ LoadBankFile(indexEntry.name(), &bankFile);
+ for (auto &preset : bankFile.presets())
+ {
result.push_back(
- PresetIndexEntry(preset->instanceId(),preset->preset().name())
- );
+ PresetIndexEntry(preset->instanceId(), preset->preset().name()));
}
return result;
}
-
-
void Storage::SetPresetIndex(const PresetIndex &presets)
{
// painful because we must move unique_ptrs.
@@ -885,7 +900,8 @@ Pedalboard Storage::GetPreset(int64_t instanceId) const
int64_t Storage::DeletePresets(const std::vector &presetInstanceIds)
{
int64_t newSelection = currentBank.selectedPreset();
- for (auto presetId: presetInstanceIds) {
+ for (auto presetId : presetInstanceIds)
+ {
newSelection = currentBank.deletePreset(presetId);
}
SaveCurrentBank();
@@ -977,7 +993,7 @@ int64_t Storage::CreateNewPreset()
}
}
newPedalboard.name(name);
- auto t = this->currentBank.addPreset(newPedalboard, -1);
+ auto t = this->currentBank.addPreset(newPedalboard, -1);
SaveCurrentBank();
return t;
}
@@ -997,7 +1013,7 @@ int64_t Storage::CopyPreset(int64_t fromId, int64_t toId)
{
auto &toItem = this->currentBank.getItem(toId);
toItem.preset(fromItem.preset());
- result = toId;
+ result = toId;
}
SaveCurrentBank();
return result;
@@ -2075,21 +2091,21 @@ static bool ensureNoDotDot(const std::filesystem::path &path)
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;
}
- if (strcasecmp(fileName.c_str(),"license.md") == 0)
+ if (strcasecmp(fileName.c_str(), "license.md") == 0)
{
return true;
}
- if (strcasecmp(fileName.c_str(),"readme.txt") == 0)
+ if (strcasecmp(fileName.c_str(), "readme.txt") == 0)
{
return true;
}
- if (strcasecmp(fileName.c_str(),"readme.md") == 0)
+ if (strcasecmp(fileName.c_str(), "readme.md") == 0)
{
return true;
}
@@ -3021,11 +3037,13 @@ std::string Storage::GetTone3000Auth() const
std::filesystem::path Storage::FromAbstractPathString(const std::string &stringPath)
{
- if (stringPath.empty()) {
+ if (stringPath.empty())
+ {
return "";
}
- fs::path path { stringPath};
- if (path.is_absolute()) {
+ fs::path path{stringPath};
+ if (path.is_absolute())
+ {
return path;
}
return GetPluginUploadDirectory() / path;
@@ -3048,6 +3066,125 @@ std::string Storage::FromAbstractPathJson(const std::string &pathJson)
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);
+ return result;
+ } catch (const std::exception &e) {
+ Lv2Log::error("Failed to load Channel Router settings: %s", e.what());
+ return std::make_shared();
+ }
+}
+
+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();
+ if (jackChannelSelection.isValid())
+ {
+ channelRouterSettings->configured(true);
+ channelRouterSettings->mainInputChannels().resize(2);
+ const std::vector& oldInputs = jackChannelSelection.GetInputAudioPorts();
+ std::vector& 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& 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_REFERENCE(UserSettings, governor)
diff --git a/src/Storage.hpp b/src/Storage.hpp
index 21ae5ba..76b4199 100644
--- a/src/Storage.hpp
+++ b/src/Storage.hpp
@@ -32,6 +32,8 @@
#include
#include "FilePropertyDirectoryTree.hpp"
#include "AlsaSequencer.hpp"
+#include "ChannelRouterSettings.hpp"
+
namespace pipedal {
@@ -117,6 +119,15 @@ private:
WifiDirectConfigSettings wifiDirectConfigSettings;
UserSettings userSettings;
+
+ ChannelRouterSettings::ptr channelRouterSettings;
+ ChannelSelection channelSelection;
+
+ void UpgradeChannelRouterSettings();
+ std::filesystem::path GetChannelRouterSettingsPath() const;
+ void SaveChannelRouterSettings(ChannelRouterSettings::ptr settings);
+ ChannelRouterSettings::ptr LoadChannelRouterSettings();
+
public:
Storage();
void Initialize();
@@ -124,8 +135,8 @@ public:
void SetDataRoot(const std::filesystem::path& path);
void SetConfigRoot(const std::filesystem::path& path);
- const std::filesystem::path&GetConfigRoot();
- const std::filesystem::path&GetDataRoot();
+ const std::filesystem::path&GetConfigRoot() const;
+ const std::filesystem::path&GetDataRoot() const;
const std::filesystem::path &GetPluginUploadDirectory() const;
@@ -206,6 +217,11 @@ public:
void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration);
AlsaSequencerConfiguration GetAlsaSequencerConfiguration() const;
+ void SetChannelRouterSettings(ChannelRouterSettings::ptr settings);
+ ChannelRouterSettings::ptr GetChannelRouterSettings();
+
+ const ChannelSelection& GetChannelSelection() const;
+
//std::string MapPropertyFileName(Lv2PluginInfo*pluginInfo, const std::string&path);
diff --git a/todo.txt b/todo.txt
index 8423926..d3b331d 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,3 +1,4 @@
+A way to force input to mono?
ls
diff --git a/vite/src/pipedal/ChannelMixerSettings.tsx b/vite/src/pipedal/ChannelMixerSettings.tsx
deleted file mode 100644
index 983131f..0000000
--- a/vite/src/pipedal/ChannelMixerSettings.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-
-import { ControlValue } from "../pipedal/Pedalboard.tsx";
-
-
-export class ChannelMixerStrip {
- inputVu: number = -100.0;
- inputVolume: number = 0;
- outputVu: number = -100.0;
- outputVolume: number = 0;
-};
-
-export default class ChannelMixerSettings {
- configured: boolean = false;
-
- guitarInputChannels: number[] = [1,1];
-
- guitarOutputChannels: number[] = [0,1];
-
- guitarMixerSettings: ChannelMixerStrip = new ChannelMixerStrip();
-
-
- auxInputChannels: number[] = [0,0];
- auxOutputChannels: number[] = [-1,-1];
-
- auxMixerSettings: ChannelMixerStrip = new ChannelMixerStrip();
-
- controls: ControlValue[] = [];
- // Inserts...
-
- getControlValue(symbol: string): number | null {
- for (let control of this.controls) {
- if (control.key === symbol) {
- return control.value;
- }
- }
- return null;
- }
- setControlValue(symbol: string, value: number): boolean {
- for (let control of this.controls) {
- if (control.key === symbol) {
- if (control.value === value) {
- return false;
- }
- control.value = value;
- this.controls = this.controls.slice(); // trigger observers.
- return true;
- }
- }
- let newValue = new ControlValue(symbol,value);
- this.controls.push(newValue);
- this.controls = this.controls.slice(); // trigger observers.
- return true;
- }
-
-}
\ No newline at end of file
diff --git a/vite/src/pipedal/ChannelMixerSettingsDialog.tsx b/vite/src/pipedal/ChannelMixerSettingsDialog.tsx
deleted file mode 100644
index 2d34295..0000000
--- a/vite/src/pipedal/ChannelMixerSettingsDialog.tsx
+++ /dev/null
@@ -1,524 +0,0 @@
-// 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 VuMeter from './VuMeter';
-import Toolbar from '@mui/material/Toolbar';
-import ArrowBackIcon from '@mui/icons-material/ArrowBack';
-import IconButtonEx from './IconButtonEx';
-import Button from '@mui/material/Button';
-import useWindowSize from "./UseWindowSize";
-import DialIcon from './svg/fx_dial.svg?react';
-import Select from '@mui/material/Select';
-import MenuItem from '@mui/material/MenuItem';
-import ChannelMixerSettingsHelpDialog from './ChannelMixerSettingsHelpDialog';
-import PluginControl from './PluginControl';
-
-import DialogTitle from '@mui/material/DialogTitle';
-import DialogContent from '@mui/material/DialogContent';
-import Typography from '@mui/material/Typography';
-import ChannelMixerSettings from './ChannelMixerSettings';
-import { makeChanelMixerUiPlugin, CHANNEL_MIXER_INSTANCE_ID } from './ChannelMixerUiControl';
-import { ControlValue } from './Pedalboard';
-
-import DialogEx from './DialogEx';
-
-import { PiPedalModelFactory } from './PiPedalModel';
-
-export interface ChannelMixerSettingsDialogProps {
- open: boolean;
- onClose: () => void;
-}
-
-let channelMixerUiPlugin = makeChanelMixerUiPlugin();
-
-enum RouteType {
- Main,
- Aux
-}
-let cellSectionHead: React.CSSProperties = {
- border: "0px",
- paddingTop: 4,
- paddingBottom: 4,
- paddingLeft: 0,
- paddingRight: 12,
- 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: 4,
- margin: "0px",
- textAlign: "left",
- verticalAlign: "middle"
-};
-let cellLeftIndent: React.CSSProperties = {
- border: "0px",
- paddingTop: 4,
- paddingBottom: 4,
- paddingLeft: 12,
- paddingRight: 4,
- margin: "0px",
- textAlign: "right",
- verticalAlign: "middle"
-};
-let cellLeftH: React.CSSProperties = {
- border: "0px",
- paddingTop: 4,
- paddingBottom: 4,
- paddingLeft: 4,
- paddingRight: 4,
- margin: "0px",
- textAlign: "left",
- verticalAlign: "middle"
-};
-
-
-function MakeChannelMenu(channelCount: number): React.ReactElement[] {
- let items: React.ReactElement[] = [(None )]
- if (channelCount === 0) {
- return items;
- }
- if (channelCount === 2) {
- items.push((Left ));
- items.push((Right ));
- return items;
- }
- for (let i = 0; i < channelCount; ++i) {
- items.push((Ch {i + 1} ));
- }
- return items;
-}
-
-
-
-function ChannelMixerSettingsDialog(props: ChannelMixerSettingsDialogProps) {
- //const classes = useStyles();
- const { open, onClose } = props;
- let model = PiPedalModelFactory.getInstance();
- let config = model.jackConfiguration.get();
-
-
- const [settings, setSettings] = useState(model.channelMixerSettings.get());
- const [controlValues,setControlValues] = useState(model.channelMixerControlValues.get());
- const [showHelp, setShowHelp] = useState(false);
-
- if (settings) {
- }
- if (controlValues) {
-
- }
-
-
- React.useEffect(() => {
- if (open) {
- let handleSettingsChanged = () => {
- setSettings(model.channelMixerSettings.get());
- };
- model.channelMixerSettings.addOnChangedHandler(handleSettingsChanged);
- let handleControlsChanged = () => {
- setControlValues(model.channelMixerControlValues.get());
- };
- model.channelMixerControlValues.addOnChangedHandler(handleControlsChanged);
- return () => {
- model.channelMixerSettings.removeOnChangedHandler(handleSettingsChanged);
- model.channelMixerControlValues.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 Dial = (symbol: string) => {
- let instanceId = CHANNEL_MIXER_INSTANCE_ID;
- let control = channelMixerUiPlugin.getControl(symbol);
- let value = settings.getControlValue(symbol);
- if (value === null) {
- value = control?.default_value ?? 0;
- }
-
- return ( {
- model.previewPedalboardValue(instanceId, symbol, value);
- }}
- onChange={(value: number) => {
- model.setPedalboardControl(instanceId, symbol, value);
- }}
- requestIMEEdit={() => { }}
- value={value}
- />);
-
- return ;
- }
- let ChannelSelect = (routeType: RouteType, channelIndex: number, input: boolean) => {
- let channelCount = input ? config.inputAudioPorts.length : config.outputAudioPorts.length;
-
- let value: number;
-
- switch (routeType) {
- case RouteType.Main:
- value = input ? settings.guitarInputChannels[channelIndex] : settings.guitarOutputChannels[channelIndex];
- break;
- case RouteType.Aux:
- value = input ? settings.auxInputChannels[channelIndex] : settings.auxOutputChannels[channelIndex];
- break;
- }
- if (value >= channelCount) {
- value = -1;
- }
- return (
- {
- let newValue = event.target.value as number;
- let newSettings = Object.assign(new ChannelMixerSettings(), settings);
- switch (routeType) {
- case RouteType.Main:
- if (input) {
- newSettings.guitarInputChannels[channelIndex] = newValue;
- } else {
- newSettings.guitarOutputChannels[channelIndex] = newValue;
- }
- break;
- case RouteType.Aux:
- if (input) {
- newSettings.auxInputChannels[channelIndex] = newValue;
- } else {
- newSettings.auxOutputChannels[channelIndex] = newValue;
- }
- break;
- }
- model.channelMixerSettings.set(newSettings);
- }}
- >
- {MakeChannelMenu(channelCount)}
-
- )
- }
- let Vu = () => {
- return (
-
-
-
- );
- }
-
- let LandscapeView = () => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- In
-
-
-
-
-
- Out
-
-
-
- {/* Main */}
-
-
- Main
-
-
- {ChannelSelect(RouteType.Main, 0, true)}
-
- {Dial("mainIn")}
- {Vu()}
-
-
- Inserts
-
-
-
- {Dial("mainOut")}
- {Vu()}
-
- {ChannelSelect(RouteType.Main, 0, false)}
-
-
-
-
-
- {(ChannelSelect(RouteType.Main, 1, true))}
-
-
- {(ChannelSelect(RouteType.Main, 1, false))}
-
-
-
- {/* Spacer */}
-
-
- {/* Aux */}
-
-
- Aux
-
-
- {(ChannelSelect(RouteType.Aux, 0, true))}
-
- {Dial("auxIn")}
- {Vu()}
-
-
- Inserts
-
-
-
- {Dial("auxOut")}
- {Vu()}
-
- {(ChannelSelect(RouteType.Aux, 0, false))}
-
-
-
-
-
- {(ChannelSelect(RouteType.Aux, 1, true))}
-
-
- {(ChannelSelect(RouteType.Aux, 1, false))}
-
-
-
-
-
-
- );
- };
- let PortraitView = () => {
- return (
-
-
-
-
-
-
-
-
-
- {/* Main */}
-
-
- Main
-
-
-
-
- In
-
-
- {ChannelSelect(RouteType.Main, 0, true)}
-
-
- {ChannelSelect(RouteType.Main, 1, true)}
-
-
-
-
-
-
-
-
-
- {Dial("mainIn")}
- {Vu()}
-
-
-
- Inserts
-
-
-
-
- {Dial("mainOut")}
- {Vu()}
-
-
-
-
-
-
-
-
- Out
-
-
- {ChannelSelect(RouteType.Main, 0, false)}
-
-
- {ChannelSelect(RouteType.Main, 1, false)}
-
-
-
-
- {/* Spacer */}
-
-
- {/* Aux */}
-
-
- Aux
-
-
-
-
- In
-
-
- {(ChannelSelect(RouteType.Aux, 0, true))}
-
-
- {(ChannelSelect(RouteType.Aux, 1, true))}
-
-
-
-
-
-
-
-
-
- {Dial("auxIn")}
- {Vu()}
-
-
- Inserts
-
-
-
- {Dial("auxOut")}
- {Vu()}
-
-
-
-
-
-
-
-
- Out
-
-
- {ChannelSelect(RouteType.Aux, 0, false)}
-
-
- {ChannelSelect(RouteType.Aux, 1, false)}
-
-
-
-
-
-
- );
- };
-
- return (
-
-
-
- { onClose(); }}
- >
-
-
-
-
- Channel Routing
-
-
- { setShowHelp(true); }}
- >
-
-
-
-
-
-
-
- {landscape ?
- LandscapeView()
- :
- PortraitView()
- }
-
- {showHelp && (
- setShowHelp(false)} />
- )}
-
- );
-}
-
-export default ChannelMixerSettingsDialog;
diff --git a/vite/src/pipedal/ChannelRouterSettings.tsx b/vite/src/pipedal/ChannelRouterSettings.tsx
new file mode 100644
index 0000000..bea7ef7
--- /dev/null
+++ b/vite/src/pipedal/ChannelRouterSettings.tsx
@@ -0,0 +1,64 @@
+
+import { Pedalboard, ControlValue } from "./Pedalboard.tsx";
+
+
+
+
+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;
+ }
+
+}
\ No newline at end of file
diff --git a/vite/src/pipedal/ChannelRouterSettingsDialog.tsx b/vite/src/pipedal/ChannelRouterSettingsDialog.tsx
new file mode 100644
index 0000000..5fbb414
--- /dev/null
+++ b/vite/src/pipedal/ChannelRouterSettingsDialog.tsx
@@ -0,0 +1,853 @@
+// 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';
+
+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[] = [(None )]
+ if (channelCount === 0) {
+ return items;
+ }
+ if (channelCount === 2) {
+ items.push((Left ));
+ items.push((Right ));
+ return items;
+ }
+ for (let i = 0; i < channelCount; ++i) {
+ items.push((Ch {i + 1} ));
+ }
+ 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(model.channelRouterSettings.get());
+ const [controlValues, setControlValues] = useState(model.channelRouterControlValues.get());
+ const [showHelp, setShowHelp] = useState(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;
+
+ 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 (
+ ) :
+ ( )
+ ) : undefined
+ }
+ onChange={(event) => {
+ let newValue = event.target.value as number;
+ let newSettings = Object.assign(new ChannelRouterSettings(), 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)}
+
+ )
+ }
+ let Vu = () => {
+ return (
+
+ );
+ }
+
+ let ApplyPresetId = (presetId: number | string | null | undefined) => {
+ if (typeof presetId !== 'number') {
+ return;
+ }
+ for (let preset of channelRouterPresets) {
+ if (preset.id === presetId) {
+ model.setChannelRouterSettings(preset.settings);
+ 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(
+
+ {preset.name}
+
+ );
+ }
+ if (settings.channelRouterPresetId === -1) {
+ items.push(
+ (
+ (Custom) )
+ )
+ }
+
+ return items;
+ }
+
+ let PresetSelect = (width: number | string | undefined) => {
+ return (
+
+ {
+ ApplyPresetId(event.target.value);
+ }}
+ >
+ {GeneratePresetMenuItems()}
+
+
+ { }} />
+
+
+ )
+ }
+
+
+ let LandscapeView = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Main */}
+
+
+ Main
+
+
+ {ChannelSelect(RouteType.Main, 0, true)}
+
+ {Vu()}
+
+
+ Inserts
+
+
+
+ {Vu()}
+
+ {ChannelSelect(RouteType.Main, 0, false)}
+
+
+
+
+
+ {(ChannelSelect(RouteType.Main, 1, true))}
+
+
+ {(ChannelSelect(RouteType.Main, 1, false))}
+
+
+
+ {/* Spacer ---------------- */}
+
+
+ {/* Aux ----------------------*/}
+
+
+ Aux
+
+
+ {(ChannelSelect(RouteType.Aux, 0, true))}
+
+ {Vu()}
+
+
+ Inserts
+
+
+
+ {Vu()}
+
+ {(ChannelSelect(RouteType.Aux, 0, false))}
+
+
+
+
+
+ {(ChannelSelect(RouteType.Aux, 1, true))}
+
+
+ {(ChannelSelect(RouteType.Aux, 1, false))}
+
+
+
+ {/* Spacer ---------------- */}
+
+
+ {/* Send ---------------- */}
+
+
+ Send
+
+
+ {(ChannelSelect(RouteType.Send, 0, false))}
+
+ {Vu()}
+
+ {/* No button */}
+
+ {Vu()}
+
+ {(ChannelSelect(RouteType.Send, 0, true))}
+
+
+
+
+
+ {(ChannelSelect(RouteType.Send, 1, false))}
+
+
+ {(ChannelSelect(RouteType.Send, 1, true))}
+
+
+
+
+
+
+ );
+ };
+ let PortraitView = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+ {/* Main */}
+
+
+ Main
+
+
+
+
+
+
+
+
+ In
+
+
+
+
+ {ChannelSelect(RouteType.Main, 0, true, false)}
+
+
+ {ChannelSelect(RouteType.Main, 1, true, false)}
+
+
+
+
+
+
+
+
+
+ {Vu()}
+
+
+
+ Inserts
+
+
+
+
+ {Vu()}
+
+
+
+
+
+
+
+
+
+ Out
+
+
+
+
+
+
+ {ChannelSelect(RouteType.Main, 0, false, false)}
+
+
+ {ChannelSelect(RouteType.Main, 1, false, false)}
+
+
+
+
+ {/* Spacer ---------------- */}
+
+ {/* Aux */}
+
+
+ Aux
+
+
+
+
+
+
+ In
+
+
+
+
+ {(ChannelSelect(RouteType.Aux, 0, true, false))}
+
+
+ {(ChannelSelect(RouteType.Aux, 1, true, false))}
+
+
+
+
+
+
+
+
+
+ {Vu()}
+
+
+
+ Inserts
+
+
+
+
+ {Vu()}
+
+
+
+
+
+
+
+
+
+ Out
+
+
+
+
+ {ChannelSelect(RouteType.Aux, 0, false, false)}
+
+
+ {ChannelSelect(RouteType.Aux, 1, false, false)}
+
+
+
+
+ {/* Spacer ---------------- */}
+
+
+ {/* Send */}
+
+
+ Send
+
+
+
+
+
+
+ Send
+
+
+
+
+ {(ChannelSelect(RouteType.Send, 0, false, false))}
+
+
+ {(ChannelSelect(RouteType.Send, 1, false, false))}
+
+
+
+
+
+
+
+
+
+ {Vu()}
+
+
+
+ Inserts
+
+
+
+
+ {Vu()}
+
+
+
+
+
+
+
+
+
+ Return
+
+
+
+
+ {ChannelSelect(RouteType.Send, 0, true, false)}
+
+
+ {ChannelSelect(RouteType.Send, 1, false, false)}
+
+
+
+
+
+
+
+ );
+ };
+
+ return (
+
+
+
+
{ onClose(); }}
+ >
+
+
+
+
+ Channel Routing
+
+ {
+ landscape ? (
+ <>
+
+
+ {PresetSelect(200)}
+
+
+ >
+
+ ) : (
+
+ )
+ }
+
+
{ setShowHelp(true); }}
+ >
+
+
+
+
+
+ {!landscape && (
+ <>
+
+ {PresetSelect("60%")}
+
+
+ >
+ )}
+
+
+ {landscape ?
+ LandscapeView()
+ :
+ PortraitView()
+ }
+
+ {
+ showHelp && (
+ setShowHelp(false)} />
+ )
+ }
+
+ );
+}
+
+export default ChannelRouterSettingsDialog;
diff --git a/vite/src/pipedal/ChannelMixerSettingsHelpDialog.tsx b/vite/src/pipedal/ChannelRouterSettingsHelpDialog.tsx
similarity index 80%
rename from vite/src/pipedal/ChannelMixerSettingsHelpDialog.tsx
rename to vite/src/pipedal/ChannelRouterSettingsHelpDialog.tsx
index 345737b..f2d98c2 100644
--- a/vite/src/pipedal/ChannelMixerSettingsHelpDialog.tsx
+++ b/vite/src/pipedal/ChannelRouterSettingsHelpDialog.tsx
@@ -18,33 +18,30 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import React from 'react';
-import Button from '@mui/material/Button';
import DialogEx from './DialogEx';
-import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import Typography from '@mui/material/Typography';
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
-import Divider from '@mui/material/Divider';
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 ChannelMixerSettingsHelpDialogProps {
+export interface ChannelRouterSettingsHelpDialogProps {
open: boolean,
onClose: () => void,
};
-export interface ChannelMixerSettingsHelpDialogState {
+export interface ChannelRouterSettingsHelpDialogState {
fullScreen: boolean;
};
-export default class ChannelMixerSettingsHelpDialog extends ResizeResponsiveComponent {
+export default class ChannelRouterSettingsHelpDialog extends ResizeResponsiveComponent {
refText: React.RefObject;
- constructor(props: ChannelMixerSettingsHelpDialogProps) {
+ constructor(props: ChannelRouterSettingsHelpDialogProps) {
super(props);
this.state = {
fullScreen: false
@@ -137,6 +134,16 @@ export default class ChannelMixerSettingsHelpDialog extends ResizeResponsiveComp
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.
+ 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 Send 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 Return 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.
+
diff --git a/vite/src/pipedal/ChannelMixerUiControl.tsx b/vite/src/pipedal/ChannelRouterUiControl.tsx
similarity index 97%
rename from vite/src/pipedal/ChannelMixerUiControl.tsx
rename to vite/src/pipedal/ChannelRouterUiControl.tsx
index 731a3f3..186e891 100644
--- a/vite/src/pipedal/ChannelMixerUiControl.tsx
+++ b/vite/src/pipedal/ChannelRouterUiControl.tsx
@@ -19,13 +19,12 @@
import Units from './Units';
import {UiPlugin, UiControl, PluginType, ControlType, ScalePoint} from './Lv2Plugin';
-export const ChannelMixerUiUri = "uri://two-play/pipedal/ppChannelMixer";
-export const CHANNEL_MIXER_INSTANCE_ID = -4;
+export const ChannelRouterUiUri = "uri://two-play/pipedal/ppChannelRouter";
export function makeChanelMixerUiPlugin(): UiPlugin {
return new UiPlugin().deserialize({
- uri: ChannelMixerUiUri,
+ uri: ChannelRouterUiUri,
name: "Channel Mixer",
brand: "",
label: "",
diff --git a/vite/src/pipedal/Pedalboard.tsx b/vite/src/pipedal/Pedalboard.tsx
index 31ae610..c96f0d0 100644
--- a/vite/src/pipedal/Pedalboard.tsx
+++ b/vite/src/pipedal/Pedalboard.tsx
@@ -357,14 +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 {
+ 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 END_CONTROL = -3; // synthetic PedalboardItem for output volume.
- static readonly CHANNEL_MIXER_INSTANCE_ID = -4; // Synthetic instance ID for the channel routing mixer.
+ static readonly MAIN_INSERT_START_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2;
+ static readonly MAIN_INSERT_END_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3;
+ static readonly AUX_INSERT_START_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2;
+ static readonly AUX_INSERT_END_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3;
+
+
static readonly START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start";
static readonly END_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#End";
@@ -373,7 +395,7 @@ export class Pedalboard implements Deserializable {
this.input_volume_db = input.input_volume_db;
this.output_volume_db = input.output_volume_db;
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.selectedSnapshot = input.selectedSnapshot;
this.pathProperties = input.pathProperties;
@@ -381,6 +403,25 @@ export class Pedalboard implements Deserializable {
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 {
return new Pedalboard().deserialize(this);
}
diff --git a/vite/src/pipedal/PiPedalModel.tsx b/vite/src/pipedal/PiPedalModel.tsx
index 083603e..569a2e3 100644
--- a/vite/src/pipedal/PiPedalModel.tsx
+++ b/vite/src/pipedal/PiPedalModel.tsx
@@ -46,7 +46,7 @@ import AudioFileMetadata from './AudioFileMetadata';
import { pathFileName } from './FileUtils';
import { AlsaSequencerConfiguration, AlsaSequencerPortSelection } from './AlsaSequencer';
import { getDefaultModGuiPreference } from './ModGuiHost';
-import ChannelMixerSettings from './ChannelMixerSettings';
+import ChannelRouterSettings from './ChannelRouterSettings';
export enum State {
Loading,
@@ -530,13 +530,9 @@ export class PiPedalModel //implements PiPedalModel
jackServerSettings: ObservableProperty
= new ObservableProperty(new JackServerSettings());
- channelMixerControlValues: ObservableProperty = new ObservableProperty([]);
- channelMixerSettings: ObservableProperty = new ObservableProperty(new ChannelMixerSettings());
+ channelRouterControlValues: ObservableProperty = new ObservableProperty([]);
+ channelRouterSettings: ObservableProperty = new ObservableProperty(new ChannelRouterSettings());
- setChannelMixerSettings(settings: ChannelMixerSettings) {
- this.channelMixerSettings.set(settings);
- this.channelMixerControlValues.set(settings.controls);
- }
wifiConfigSettings: ObservableProperty = new ObservableProperty(new WifiConfigSettings());
wifiDirectConfigSettings: ObservableProperty = new ObservableProperty(new WifiDirectConfigSettings());
governorSettings: ObservableProperty = new ObservableProperty(new GovernorSettings());
@@ -1255,6 +1251,11 @@ export class PiPedalModel //implements PiPedalModel
await this.getWebSocket().request("getJackServerSettings")
)
);
+ this.channelRouterSettings.set(
+ new ChannelRouterSettings().deserialize(
+ await this.getWebSocket().request("getChannelRouterSettings")
+ )
+ )
this.jackConfiguration.set(new JackConfiguration().deserialize(
await this.getWebSocket().request("getJackConfiguration")
));
@@ -1651,15 +1652,15 @@ export class PiPedalModel //implements PiPedalModel
private lastControlMessageWasSentbyMe = false;
- private _setChannelMixerControlValue(key: string, value: number, notifyServer: boolean) : void {
- let channelMixerSettings = this.channelMixerSettings.get();
- let changed = channelMixerSettings.setControlValue(key, value);
+ private _setChannelRouterControlValue(key: string, value: number, notifyServer: boolean) : void {
+ let channelRouterSettings = this.channelRouterSettings.get();
+ let changed = channelRouterSettings.setControlValue(key, value);
if (changed)
{
- this.channelMixerControlValues.set(this.channelMixerSettings.get().controls);
+ this.channelRouterControlValues.set(this.channelRouterSettings.get().controlValues);
if (notifyServer) {
this.lastControlMessageWasSentbyMe = true;
- this._setServerControl("setControl", Pedalboard.CHANNEL_MIXER_INSTANCE_ID, key, value);
+ this._setServerControl("setControl", Pedalboard.CHANNEL_ROUTER_CONTROLS_INSTANCE_ID, key, value);
}
}
}
@@ -1667,8 +1668,8 @@ export class PiPedalModel //implements PiPedalModel
let pedalboard = this.pedalboard.get();
if (pedalboard === undefined) throw new PiPedalStateError("Pedalboard not ready.");
- if (instanceId == Pedalboard.CHANNEL_MIXER_INSTANCE_ID) {
- this._setChannelMixerControlValue(key,value,notifyServer);
+ if (instanceId == Pedalboard.CHANNEL_ROUTER_CONTROLS_INSTANCE_ID) {
+ this._setChannelRouterControlValue(key,value,notifyServer);
return;
} else {
let changed: boolean;
@@ -2316,12 +2317,6 @@ export class PiPedalModel //implements PiPedalModel
}
- setJackSettings(jackSettings: JackChannelSelection): void {
- this.expectDisconnect(ReconnectReason.LoadingSettings);
- this.webSocket?.send("setJackSettings", jackSettings);
- }
-
-
monitorPortSubscriptions: MonitorPortHandleImpl[] = [];
monitorPort(instanceId: number, key: string, updateRateSeconds: number, onUpdated: (value: number) => void): MonitorPortHandle {
@@ -3310,7 +3305,6 @@ export class PiPedalModel //implements PiPedalModel
if (this.webSocket) {
this.webSocket.send("setFavorites", newFavorites);
}
- // stub: update server.
}
@@ -3680,6 +3674,15 @@ export class PiPedalModel //implements PiPedalModel
}
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;
diff --git a/vite/src/pipedal/SettingsDialog.tsx b/vite/src/pipedal/SettingsDialog.tsx
index 7a47b71..dc579ee 100644
--- a/vite/src/pipedal/SettingsDialog.tsx
+++ b/vite/src/pipedal/SettingsDialog.tsx
@@ -20,8 +20,8 @@
import React, { SyntheticEvent, Component } from 'react';
import Switch from "@mui/material/Switch";
-import ChannelMixerSettings from './ChannelMixerSettings';
-import ChannelMixerSettingsDialog from './ChannelMixerSettingsDialog';
+import ChannelRouterSettings from './ChannelRouterSettings';
+import ChannelRouterSettingsDialog from './ChannelRouterSettingsDialog';
import OkCancelDialog from './OkCancelDialog';
import RadioSelectDialog from './RadioSelectDialog';
import IconButtonEx from './IconButtonEx';
@@ -78,7 +78,7 @@ interface SettingsDialogState {
showStatusMonitorDialog: boolean;
jackConfiguration: JackConfiguration;
jackSettings: JackChannelSelection | null;
- channelMixerSettings: ChannelMixerSettings | null;
+ channelRouterSettings: ChannelRouterSettings | null;
jackServerSettings: JackServerSettings;
alsaSequencerConfiguration: AlsaSequencerConfiguration;
keepScreenOn: boolean;
@@ -101,7 +101,7 @@ interface SettingsDialogState {
showMidiSelectDialog: boolean;
showThemeSelectDialog: boolean;
showJackServerSettingsDialog: boolean;
- showChannelMixerSettingsDialog: boolean;
+ showChannelRouterSettingsDialog: boolean;
shuttingDown: boolean;
restarting: boolean;
isAndroidHosted: boolean;
@@ -194,7 +194,7 @@ const SettingsDialog = withStyles(
showStatusMonitorDialog: false,
jackServerSettings: this.model.jackServerSettings.get(),
- channelMixerSettings: this.model.channelMixerSettings.get(),
+ channelRouterSettings: this.model.channelRouterSettings.get(),
jackConfiguration: this.model.jackConfiguration.get(),
jackStatus: undefined,
jackSettings: this.model.jackSettings.get(),
@@ -215,7 +215,7 @@ const SettingsDialog = withStyles(
showMidiSelectDialog: false,
showThemeSelectDialog: false,
showJackServerSettingsDialog: false,
- showChannelMixerSettingsDialog: false,
+ showChannelRouterSettingsDialog: false,
shuttingDown: false,
restarting: false,
showShutdownOkDialog: false,
@@ -228,7 +228,7 @@ const SettingsDialog = withStyles(
this.handleAlsaSequencerConfigurationChanged = this.handleAlsaSequencerConfigurationChanged.bind(this);
this.handleJackSettingsChanged = this.handleJackSettingsChanged.bind(this);
this.handleJackServerSettingsChanged = this.handleJackServerSettingsChanged.bind(this);
- this.handleChannelMixerSettingsChanged = this.handleChannelMixerSettingsChanged.bind(this);
+ this.handleChannelRouterSettingsChanged = this.handleChannelRouterSettingsChanged.bind(this);
this.handleWifiConfigSettingsChanged = this.handleWifiConfigSettingsChanged.bind(this);
this.handleWifiDirectConfigSettingsChanged = this.handleWifiDirectConfigSettingsChanged.bind(this);
this.handleGovernorSettingsChanged = this.handleGovernorSettingsChanged.bind(this);
@@ -330,10 +330,10 @@ const SettingsDialog = withStyles(
continueDisabled: !this.model.jackServerSettings.get().valid
});
}
- handleChannelMixerSettingsChanged(): void {
+ handleChannelRouterSettingsChanged(): void {
this.setState({
- channelMixerSettings: this.model.channelMixerSettings.get(),
- continueDisabled: !this.model.channelMixerSettings.get().configured
+ channelRouterSettings: this.model.channelRouterSettings.get(),
+ continueDisabled: !this.model.channelRouterSettings.get().configured
});
}
@@ -378,7 +378,7 @@ const SettingsDialog = withStyles(
this.model.jackConfiguration.addOnChangedHandler(this.handleJackConfigurationChanged);
this.model.alsaSequencerConfiguration.addOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
this.model.jackServerSettings.addOnChangedHandler(this.handleJackServerSettingsChanged);
- this.model.channelMixerSettings.addOnChangedHandler(this.handleChannelMixerSettingsChanged);
+ this.model.channelRouterSettings.addOnChangedHandler(this.handleChannelRouterSettingsChanged);
this.model.wifiConfigSettings.addOnChangedHandler(this.handleWifiConfigSettingsChanged);
this.model.wifiDirectConfigSettings.addOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
this.model.governorSettings.addOnChangedHandler(this.handleGovernorSettingsChanged);
@@ -401,7 +401,7 @@ const SettingsDialog = withStyles(
this.handleJackSettingsChanged();
this.handleShowStatusMonitorChanged();
this.handleJackServerSettingsChanged();
- this.handleChannelMixerSettingsChanged();
+ this.handleChannelRouterSettingsChanged();
this.handleWifiConfigSettingsChanged();
this.handleWifiDirectConfigSettingsChanged();
this.setState({ hasWifiDevice: this.model.hasWifiDevice.get() });
@@ -420,7 +420,7 @@ const SettingsDialog = withStyles(
this.model.alsaSequencerConfiguration.removeOnChangedHandler(this.handleAlsaSequencerConfigurationChanged);
this.model.jackSettings.removeOnChangedHandler(this.handleJackSettingsChanged);
this.model.jackServerSettings.removeOnChangedHandler(this.handleJackServerSettingsChanged);
- this.model.channelMixerSettings.removeOnChangedHandler(this.handleChannelMixerSettingsChanged);
+ this.model.channelRouterSettings.removeOnChangedHandler(this.handleChannelRouterSettingsChanged);
this.model.wifiConfigSettings.removeOnChangedHandler(this.handleWifiConfigSettingsChanged);
this.model.wifiDirectConfigSettings.removeOnChangedHandler(this.handleWifiDirectConfigSettingsChanged);
this.model.governorSettings.removeOnChangedHandler(this.handleGovernorSettingsChanged);
@@ -720,10 +720,10 @@ const SettingsDialog = withStyles(
{this.state.jackServerSettings.getSummaryText()}
- {this.state.showChannelMixerSettingsDialog && (
- this.setState({ showChannelMixerSettingsDialog: false })}
+ {this.state.showChannelRouterSettingsDialog && (
+ this.setState({ showChannelRouterSettingsDialog: false })}
/>
)}
{this.state.showJackServerSettingsDialog && (
@@ -744,7 +744,7 @@ const SettingsDialog = withStyles(
{
this.setState({
- showChannelMixerSettingsDialog: true
+ showChannelRouterSettingsDialog: true
});
}}
disabled={!hasAudioConfig}
diff --git a/vite/src/pipedal/ToobParametricEqView.tsx b/vite/src/pipedal/ToobParametricEqView.tsx
index 7bced53..8a6bb16 100644
--- a/vite/src/pipedal/ToobParametricEqView.tsx
+++ b/vite/src/pipedal/ToobParametricEqView.tsx
@@ -372,7 +372,7 @@ const ToobParametricEqView =
])}
{panel("", [
controls.gain
- ])}
+ ])}
{divider()}
{panel("Low",
[controls.low_level,