Sync
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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(',');
|
||||
|
||||
+242
-95
@@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
#include "pch.h"
|
||||
#include "PiPedalCommon.hpp"
|
||||
#include "util.hpp"
|
||||
#include <cmath>
|
||||
#include "Finally.hpp"
|
||||
@@ -39,6 +40,7 @@
|
||||
#include "CrashGuard.hpp"
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include "ChannelRouterSettings.hpp"
|
||||
|
||||
#include "CpuUse.hpp"
|
||||
|
||||
@@ -328,11 +330,34 @@ namespace pipedal
|
||||
bool inputSwapped = false;
|
||||
bool outputSwapped = false;
|
||||
|
||||
std::vector<float *> activeCaptureBuffers;
|
||||
std::vector<float *> activePlaybackBuffers;
|
||||
std::vector<std::vector<float>> allocatedBuffers;
|
||||
|
||||
std::vector<float *> captureBuffers;
|
||||
std::vector<float *> playbackBuffers;
|
||||
std::set<int64_t> usedOutputChannels;
|
||||
std::vector<float*> deviceCaptureBuffers;
|
||||
std::vector<float*> devicePlaybackBuffers;
|
||||
float *zeroInputBuffer = nullptr;
|
||||
float *discardOutputBuffer = nullptr;
|
||||
std::vector<float *> mainCaptureBuffers;
|
||||
std::vector<float *> mainPlaybackBuffers;
|
||||
|
||||
std::vector<float *> auxCaptureBuffers;
|
||||
std::vector<float *> auxPlaybackBuffers;
|
||||
|
||||
std::vector<float *> sendCaptureBuffers;
|
||||
std::vector<float *> sendPlaybackBuffers;
|
||||
|
||||
struct OutputBufferMix {
|
||||
size_t outputChannel = 0;
|
||||
std::vector<float*> inputBuffers;
|
||||
float *outputBuffer = nullptr;
|
||||
};
|
||||
|
||||
std::vector<OutputBufferMix> outputBufferMixes;
|
||||
|
||||
|
||||
|
||||
std::vector<float *> deviceCaptureBuffers;
|
||||
std::vector<float *> devicePlaybackBuffers;
|
||||
|
||||
std::vector<uint8_t> rawCaptureBuffer;
|
||||
std::vector<uint8_t> rawPlaybackBuffer;
|
||||
@@ -742,7 +767,7 @@ namespace pipedal
|
||||
{
|
||||
int32_t *p = getCaptureBuffer<int32_t>(rawCaptureBuffer);
|
||||
|
||||
std::vector<float *> &buffers = this->captureBuffers;
|
||||
std::vector<float *> &buffers = this->deviceCaptureBuffers;
|
||||
int channels = this->captureChannels;
|
||||
for (size_t frame = 0; frame < frames; ++frame)
|
||||
{
|
||||
@@ -760,7 +785,7 @@ namespace pipedal
|
||||
{
|
||||
float *p = getCaptureBuffer<float>(rawCaptureBuffer);
|
||||
|
||||
std::vector<float *> &buffers = this->captureBuffers;
|
||||
std::vector<float *> &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<int16_t>(rawCaptureBuffer);
|
||||
|
||||
std::vector<float *> &buffers = this->captureBuffers;
|
||||
std::vector<float *> &buffers = this->deviceCaptureBuffers;
|
||||
int channels = this->captureChannels;
|
||||
constexpr double scale = 1.0f / (std::numeric_limits<int16_t>::max() + 1L);
|
||||
for (size_t frame = 0; frame < frames; ++frame)
|
||||
@@ -792,7 +817,7 @@ namespace pipedal
|
||||
{
|
||||
int16_t *p = getCaptureBuffer<int16_t>(rawCaptureBuffer);
|
||||
|
||||
std::vector<float *> &buffers = this->captureBuffers;
|
||||
std::vector<float *> &buffers = this->deviceCaptureBuffers;
|
||||
int channels = this->captureChannels;
|
||||
constexpr float scale = 1.0f / (std::numeric_limits<int16_t>::max() + 1L);
|
||||
for (size_t frame = 0; frame < frames; ++frame)
|
||||
@@ -809,7 +834,7 @@ namespace pipedal
|
||||
{
|
||||
int32_t *p = getCaptureBuffer<int32_t>(rawCaptureBuffer);
|
||||
|
||||
std::vector<float *> &buffers = this->captureBuffers;
|
||||
std::vector<float *> &buffers = this->deviceCaptureBuffers;
|
||||
int channels = this->captureChannels;
|
||||
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::max() + 1L);
|
||||
for (size_t frame = 0; frame < frames; ++frame)
|
||||
@@ -825,7 +850,7 @@ namespace pipedal
|
||||
{
|
||||
uint8_t *p = getCaptureBuffer<uint8_t>(rawCaptureBuffer);
|
||||
|
||||
std::vector<float *> &buffers = this->captureBuffers;
|
||||
std::vector<float *> &buffers = this->deviceCaptureBuffers;
|
||||
int channels = this->captureChannels;
|
||||
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::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<float *> &buffers = this->captureBuffers;
|
||||
std::vector<float *> &buffers = this->deviceCaptureBuffers;
|
||||
int channels = this->captureChannels;
|
||||
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::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<float *> &buffers = this->captureBuffers;
|
||||
std::vector<float *> &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<float *> &buffers = this->captureBuffers;
|
||||
std::vector<float *> &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<float *> &buffers = this->captureBuffers;
|
||||
std::vector<float *> &buffers = this->deviceCaptureBuffers;
|
||||
int channels = this->captureChannels;
|
||||
constexpr float scale = 1.0f / (std::numeric_limits<int32_t>::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<float *> &buffers = this->playbackBuffers;
|
||||
std::vector<float *> &buffers = this->devicePlaybackBuffers;
|
||||
int channels = this->playbackChannels;
|
||||
constexpr float scale = std::numeric_limits<int16_t>::max();
|
||||
for (size_t frame = 0; frame < frames; ++frame)
|
||||
@@ -927,7 +952,7 @@ namespace pipedal
|
||||
{
|
||||
int16_t *p = (int16_t *)rawPlaybackBuffer.data();
|
||||
|
||||
std::vector<float *> &buffers = this->playbackBuffers;
|
||||
std::vector<float *> &buffers = this->devicePlaybackBuffers;
|
||||
int channels = this->playbackChannels;
|
||||
constexpr float scale = std::numeric_limits<int16_t>::max();
|
||||
for (size_t frame = 0; frame < frames; ++frame)
|
||||
@@ -947,7 +972,7 @@ namespace pipedal
|
||||
{
|
||||
int32_t *p = (int32_t *)rawPlaybackBuffer.data();
|
||||
|
||||
std::vector<float *> &buffers = this->playbackBuffers;
|
||||
std::vector<float *> &buffers = this->devicePlaybackBuffers;
|
||||
int channels = this->playbackChannels;
|
||||
constexpr double scale = std::numeric_limits<int32_t>::max();
|
||||
for (size_t frame = 0; frame < frames; ++frame)
|
||||
@@ -969,7 +994,7 @@ namespace pipedal
|
||||
|
||||
int32_t *p = (int32_t *)rawPlaybackBuffer.data();
|
||||
|
||||
std::vector<float *> &buffers = this->playbackBuffers;
|
||||
std::vector<float *> &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<float *> &buffers = this->playbackBuffers;
|
||||
std::vector<float *> &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<float *> &buffers = this->playbackBuffers;
|
||||
std::vector<float *> &buffers = this->devicePlaybackBuffers;
|
||||
int channels = this->playbackChannels;
|
||||
constexpr double scale = std::numeric_limits<int32_t>::max();
|
||||
for (size_t frame = 0; frame < frames; ++frame)
|
||||
@@ -1031,7 +1056,7 @@ namespace pipedal
|
||||
{
|
||||
uint8_t *p = (uint8_t *)rawPlaybackBuffer.data();
|
||||
|
||||
std::vector<float *> &buffers = this->playbackBuffers;
|
||||
std::vector<float *> &buffers = this->devicePlaybackBuffers;
|
||||
int channels = this->playbackChannels;
|
||||
constexpr double scale = std::numeric_limits<int32_t>::max();
|
||||
for (size_t frame = 0; frame < frames; ++frame)
|
||||
@@ -1056,7 +1081,7 @@ namespace pipedal
|
||||
{
|
||||
uint8_t *p = (uint8_t *)rawPlaybackBuffer.data();
|
||||
|
||||
std::vector<float *> &buffers = this->playbackBuffers;
|
||||
std::vector<float *> &buffers = this->devicePlaybackBuffers;
|
||||
int channels = this->playbackChannels;
|
||||
constexpr double scale = std::numeric_limits<int32_t>::max();
|
||||
for (size_t frame = 0; frame < frames; ++frame)
|
||||
@@ -1082,7 +1107,7 @@ namespace pipedal
|
||||
{
|
||||
float *p = (float *)rawPlaybackBuffer.data();
|
||||
|
||||
std::vector<float *> &buffers = this->playbackBuffers;
|
||||
std::vector<float *> &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<float *> &buffers = this->playbackBuffers;
|
||||
std::vector<float *> &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<int64_t> &channelSelection,
|
||||
std::vector<float*> &channelBuffers
|
||||
) {
|
||||
size_t nChannels = channelSelection.size();
|
||||
channelBuffers.resize(nChannels);
|
||||
|
||||
for (size_t i = 0; i < nChannels; ++i)
|
||||
{
|
||||
throw std::invalid_argument("Bad port name.");
|
||||
int64_t deviceChannel = channelSelection[i];
|
||||
if (deviceChannel == -1 || deviceChannel >= captureChannels)
|
||||
{
|
||||
channelBuffers[i] = zeroInputBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
channelBuffers[i] = deviceCaptureBuffers[deviceChannel];
|
||||
}
|
||||
}
|
||||
}
|
||||
PIPEDAL_NON_INLINE void AddMixBuffer(int64_t outputChannel, float *mixBuffer)
|
||||
{
|
||||
for (auto&bufferMix: outputBufferMixes)
|
||||
{
|
||||
if (bufferMix.outputChannel == outputChannel)
|
||||
{
|
||||
bufferMix.inputBuffers.push_back(mixBuffer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
OutputBufferMix newMix;
|
||||
newMix.outputChannel = outputChannel;
|
||||
newMix.outputBuffer = devicePlaybackBuffers[outputChannel];
|
||||
newMix.inputBuffers.push_back(mixBuffer);
|
||||
outputBufferMixes.push_back(std::move(newMix));
|
||||
}
|
||||
PIPEDAL_NON_INLINE void AllocateOutputChannels(
|
||||
const std::vector<int64_t> &channelSelection,
|
||||
std::vector<float*> &channelBuffers
|
||||
) {
|
||||
size_t nChannels = channelSelection.size();
|
||||
|
||||
channelBuffers.resize(nChannels);
|
||||
for (size_t i = 0; i < nChannels; ++i)
|
||||
{
|
||||
int64_t deviceChannel = channelSelection[i];
|
||||
if (deviceChannel == -1 || deviceChannel >= playbackChannels)
|
||||
{
|
||||
if (discardOutputBuffer == nullptr)
|
||||
{
|
||||
discardOutputBuffer = AllocateAudioBuffer();
|
||||
}
|
||||
channelBuffers[i] = discardOutputBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!usedOutputChannels.contains(deviceChannel))
|
||||
{
|
||||
usedOutputChannels.insert(deviceChannel);
|
||||
channelBuffers[i] = deviceCaptureBuffers[deviceChannel];
|
||||
|
||||
} else {
|
||||
// Used once already. We need to mix down.
|
||||
float *mixBuffer = AllocateAudioBuffer();
|
||||
channelBuffers[i] = mixBuffer;
|
||||
AddMixBuffer(deviceChannel, mixBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool activated = false;
|
||||
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<std::jthread>([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<float *> &buffer)
|
||||
{
|
||||
for (size_t i = 0; i < buffer.size(); ++i)
|
||||
{
|
||||
delete[] buffer[i];
|
||||
buffer[i] = 0;
|
||||
}
|
||||
buffer.clear();
|
||||
float* AllocateAudioBuffer() {
|
||||
std::vector<float> 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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+19
-5
@@ -35,6 +35,8 @@ namespace pipedal {
|
||||
|
||||
using ProcessCallback = std::function<void (size_t)>;
|
||||
|
||||
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;
|
||||
|
||||
+91
-25
@@ -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 <lv2/atom/atom.h>
|
||||
@@ -519,12 +520,17 @@ private:
|
||||
SystemMidiBinding rebootMidiBinding;
|
||||
SystemMidiBinding shutdownMidiBinding;
|
||||
|
||||
JackChannelSelection channelSelection;
|
||||
ChannelSelection channelSelection;
|
||||
std::atomic<bool> active = false;
|
||||
std::atomic<bool> audioStopped = false;
|
||||
std::atomic<bool> isDummyAudioDriver = false;
|
||||
|
||||
std::shared_ptr<Lv2Pedalboard> currentPedalboard;
|
||||
std::shared_ptr<Lv2Pedalboard> currentMainInsertPedalboard;
|
||||
std::shared_ptr<Lv2Pedalboard> currentAuxInsertPedalboard;
|
||||
|
||||
|
||||
|
||||
std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue.
|
||||
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<AudioDriver>(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<ControlValue> &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<int64_t> &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);
|
||||
}
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) 2026 Robin Davies
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
#include "ChannelRouterSettings.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
static uint64_t countChannels(const std::vector<int64_t> &channels)
|
||||
{
|
||||
if (channels.size() != 2)
|
||||
{
|
||||
throw std::runtime_error("Invalid Channel Router channel configuration.");
|
||||
}
|
||||
if (channels[0] == -1)
|
||||
{
|
||||
if (channels[1] == -1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (channels[1] == -1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (channels[0] == channels[1])
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t ChannelRouterSettings::numberOfAudioInputChannels() const
|
||||
{
|
||||
if (!configured_)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
return countChannels(mainInputChannels_);
|
||||
}
|
||||
uint64_t ChannelRouterSettings::numberOfAudioOutputChannels() const
|
||||
{
|
||||
if (!configured_)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
return countChannels(mainOutputChannels_);
|
||||
}
|
||||
|
||||
ChannelSelection::ChannelSelection(ChannelRouterSettings&settings)
|
||||
: mainInputChannels_(settings.mainInputChannels())
|
||||
, mainOutputChannels_(settings.mainOutputChannels())
|
||||
, auxInputChannels_(settings.auxInputChannels())
|
||||
, auxOutputChannels_(settings.auxOutputChannels())
|
||||
, sendInputChannels_(settings.sendInputChannels())
|
||||
, sendOutputChannels_(settings.sendOutputChannels())
|
||||
{
|
||||
normalizeChannelSelection();
|
||||
}
|
||||
|
||||
static void normalizeInputChannels(std::vector<int64_t>&channels) {
|
||||
if (channels.size() == 2) {
|
||||
if (channels[0] == -1 && channels[1] == -1)
|
||||
{
|
||||
channels.resize(0);
|
||||
} else if (channels[0] == channels[1]) {
|
||||
channels.resize(1);
|
||||
} else if (channels[1] == -1) {
|
||||
channels.resize(1);} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void normalizeOutputChannels(std::vector<int64_t>&channels) {
|
||||
normalizeInputChannels(channels);
|
||||
}
|
||||
|
||||
void ChannelSelection::normalizeChannelSelection() {
|
||||
normalizeInputChannels(mainInputChannels_);
|
||||
normalizeOutputChannels(mainOutputChannels_);
|
||||
normalizeInputChannels(auxInputChannels_);
|
||||
normalizeOutputChannels(auxOutputChannels_);
|
||||
normalizeInputChannels(sendInputChannels_);
|
||||
normalizeOutputChannels(sendOutputChannels_);
|
||||
|
||||
// If either aux inputs or outputs are zero, don't do ANY aux processing.
|
||||
if (auxInputChannels_.size() == 0)
|
||||
{
|
||||
auxOutputChannels_.resize(0);
|
||||
} else if (auxOutputChannels_.size() == 0) {
|
||||
auxInputChannels_.resize(0);
|
||||
}
|
||||
// If either main inputs or outputs are empty, add send/receive dummy buffers. (Lv2 plugins shouldn't have to deal with this)
|
||||
if (mainInputChannels_.size() == 0)
|
||||
{
|
||||
mainInputChannels_.resize(1);
|
||||
mainInputChannels_[0] = -1;
|
||||
}
|
||||
if (mainOutputChannels_.size() == 0) {
|
||||
mainInputChannels_.resize(1);
|
||||
mainOutputChannels_[0] = -1;
|
||||
}
|
||||
// Send buffers are what they are. Let the send plugin deal with it.
|
||||
}
|
||||
|
||||
|
||||
ChannelRouterSettings::ChannelRouterSettings()
|
||||
: mainInserts_(Pedalboard::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()
|
||||
@@ -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 <vector>
|
||||
#include "json.hpp"
|
||||
#include "Pedalboard.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
class ChannelRouterSettings
|
||||
{
|
||||
protected:
|
||||
static constexpr int64_t CHANNEL_ROUTER_MAIN_INSERT_ID = -4; // Reserved Instance ID for Router Main Inserts.
|
||||
static constexpr int64_t CHANNEL_ROUTER_AUX_INSERT_ID = -5; // Reserved Instance ID for Router Aux inserts.
|
||||
|
||||
bool configured_ = false;
|
||||
int64_t channelRouterPresetId_ = -1;
|
||||
|
||||
std::vector<int64_t> mainInputChannels_ = {1, 1};
|
||||
std::vector<int64_t> mainOutputChannels_ = {0, 1};
|
||||
Pedalboard mainInserts_;
|
||||
|
||||
std::vector<int64_t> auxInputChannels_ = {-1, -1};
|
||||
std::vector<int64_t> auxOutputChannels_ = {-1, -1};
|
||||
Pedalboard auxInserts_;
|
||||
|
||||
std::vector<int64_t> sendInputChannels_ = {-1, -1};
|
||||
std::vector<int64_t> sendOutputChannels_ = {-1, -1};
|
||||
|
||||
std::vector<ControlValue> controlValues_;
|
||||
|
||||
public:
|
||||
using self = ChannelRouterSettings;
|
||||
using ptr = std::shared_ptr<self>;
|
||||
|
||||
ChannelRouterSettings();
|
||||
|
||||
uint64_t numberOfAudioInputChannels() const;
|
||||
uint64_t numberOfAudioOutputChannels() const;
|
||||
|
||||
JSON_GETTER_SETTER(configured)
|
||||
JSON_GETTER_SETTER(channelRouterPresetId)
|
||||
JSON_GETTER_SETTER_REF(mainInputChannels)
|
||||
JSON_GETTER_SETTER_REF(mainOutputChannels)
|
||||
JSON_GETTER_SETTER_REF(mainInserts)
|
||||
JSON_GETTER_SETTER_REF(auxInputChannels)
|
||||
JSON_GETTER_SETTER_REF(auxOutputChannels)
|
||||
JSON_GETTER_SETTER_REF(auxInserts)
|
||||
JSON_GETTER_SETTER_REF(sendInputChannels)
|
||||
JSON_GETTER_SETTER_REF(sendOutputChannels)
|
||||
JSON_GETTER_SETTER_REF(controlValues)
|
||||
|
||||
|
||||
DECLARE_JSON_MAP(ChannelRouterSettings);
|
||||
|
||||
};
|
||||
|
||||
// just the channel selecttions.
|
||||
class ChannelSelection {
|
||||
public:
|
||||
ChannelSelection() = default;
|
||||
ChannelSelection(ChannelRouterSettings&settings);
|
||||
ChannelSelection(const ChannelSelection&other) = default;
|
||||
ChannelSelection(ChannelSelection&&other) = default;
|
||||
ChannelSelection& operator=(const ChannelSelection&other) = default;
|
||||
ChannelSelection& operator=(ChannelSelection&&other) = default;
|
||||
~ChannelSelection() = default;
|
||||
|
||||
const std::vector<int64_t>&mainInputChannels() const { return mainInputChannels_; }
|
||||
const std::vector<int64_t>&mainOutputChannels() const { return mainOutputChannels_; }
|
||||
const std::vector<int64_t>&auxInputChannels() const { return auxInputChannels_; }
|
||||
const std::vector<int64_t>&auxOutputChannels() const { return auxOutputChannels_; }
|
||||
const std::vector<int64_t>&sendInputChannels() const { return sendInputChannels_; }
|
||||
const std::vector<int64_t>&sendOutputChannels() const { return sendOutputChannels_; }
|
||||
|
||||
private:
|
||||
void normalizeChannelSelection();
|
||||
|
||||
std::vector<int64_t> mainInputChannels_;
|
||||
std::vector<int64_t> mainOutputChannels_;
|
||||
std::vector<int64_t> auxInputChannels_;
|
||||
std::vector<int64_t> auxOutputChannels_;
|
||||
std::vector<int64_t> sendInputChannels_;
|
||||
std::vector<int64_t> sendOutputChannels_;
|
||||
};
|
||||
|
||||
}
|
||||
+94
-108
@@ -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 <bit>
|
||||
#include <memory>
|
||||
@@ -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<float *> activeCaptureBuffers;
|
||||
std::vector<float *> activePlaybackBuffers;
|
||||
std::vector<std::vector<float>> allocatedBuffers;
|
||||
std::vector<float *> mainCaptureBuffers;
|
||||
std::vector<float *> mainPlaybackBuffers;
|
||||
|
||||
std::vector<float *> captureBuffers;
|
||||
std::vector<float *> playbackBuffers;
|
||||
std::vector<float *> auxCaptureBuffers;
|
||||
std::vector<float *> auxPlaybackBuffers;
|
||||
|
||||
std::vector<float*> sendCaptureBuffers;
|
||||
std::vector<float*> sendPlaybackBuffers;
|
||||
|
||||
std::vector<float *> deviceCaptureBuffers;
|
||||
std::vector<float *> devicePlaybackBuffers;
|
||||
|
||||
uint8_t *rawCaptureBuffer = nullptr;
|
||||
uint8_t *rawPlaybackBuffer = nullptr;
|
||||
|
||||
AudioDriverHost *driverHost = nullptr;
|
||||
uint32_t channels = 2;
|
||||
@@ -145,7 +152,7 @@ namespace pipedal
|
||||
|
||||
std::atomic<bool> 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<float *> &buffers, size_t n)
|
||||
PIPEDAL_NON_INLINE void AllocateBuffers(std::vector<float *> &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<std::jthread> 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<std::jthread>([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<float *> &buffer)
|
||||
virtual size_t AuxInputBufferCount() const override { return auxCaptureBuffers.size(); }
|
||||
virtual float *GetAuxInputBuffer(size_t channel) override
|
||||
{
|
||||
for (size_t i = 0; i < buffer.size(); ++i)
|
||||
{
|
||||
// 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<float>(bufferSize));
|
||||
return allocatedBuffers.back().data();
|
||||
}
|
||||
PIPEDAL_NON_INLINE void DeleteBuffers()
|
||||
{
|
||||
mainCaptureBuffers.clear();
|
||||
mainPlaybackBuffers.clear();
|
||||
auxCaptureBuffers.clear();
|
||||
auxPlaybackBuffers.clear();
|
||||
sendCaptureBuffers.clear();
|
||||
sendPlaybackBuffers.clear();
|
||||
allocatedBuffers.clear();
|
||||
}
|
||||
PIPEDAL_NON_INLINE virtual void Close()
|
||||
{
|
||||
if (!open)
|
||||
{
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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<Lv2PluginInfo> 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;
|
||||
|
||||
};
|
||||
}
|
||||
@@ -124,5 +124,4 @@ namespace pipedal
|
||||
DECLARE_JSON_MAP(JackChannelSelection);
|
||||
};
|
||||
|
||||
|
||||
} // namespace.
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+17
-1
@@ -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)
|
||||
|
||||
+44
-3
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
+29
-23
@@ -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<AlsaSequencerPortSelection> PiPedalModel::GetAlsaSequencerPorts()
|
||||
return result;
|
||||
}
|
||||
|
||||
void PiPedalModel::SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex); // copy atomically.
|
||||
this->storage.SetJackChannelSelection(channelSelection);
|
||||
|
||||
this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection);
|
||||
|
||||
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<std::recursive_mutex> lock(mutex);
|
||||
return this->channelRouterSettings;
|
||||
}
|
||||
|
||||
void PiPedalModel::SetChannelRouterSettings(int64_t clientId,ChannelRouterSettings::ptr &settings)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
this->channelRouterSettings = settings;
|
||||
this->storage.SetChannelRouterSettings(settings);
|
||||
this->pluginHost.OnConfigurationChanged(jackConfiguration, *settings);
|
||||
CancelAudioRetry();
|
||||
}
|
||||
RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications..
|
||||
|
||||
this->FireChannelSelectionChanged(clientId);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "Promise.hpp"
|
||||
#include "AtomConverter.hpp"
|
||||
#include "FileEntry.hpp"
|
||||
#include "ChannelRouterSettings.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
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<PresetIndexEntry> RequestBankPresets(int64_t bankInstanceId);
|
||||
int64_t ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
|
||||
int64_t CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets);
|
||||
|
||||
void SetChannelRouterSettings(int64_t clientId, ChannelRouterSettings::ptr &settings);
|
||||
ChannelRouterSettings::ptr GetChannelRouterSettings();
|
||||
};
|
||||
|
||||
} // namespace pipedal.
|
||||
@@ -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
|
||||
{
|
||||
|
||||
+4
-3
@@ -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()
|
||||
|
||||
+12
-10
@@ -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<Lv2PluginClass> GetPluginClass(const std::string &uri) const;
|
||||
bool is_a(const std::string &class_, const std::string &target_class);
|
||||
|
||||
@@ -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<int> enabledIndexes;
|
||||
std::vector<RealtimePedalboardItemIndex> enabledIndexes;
|
||||
std::vector<VuUpdate> vuUpdateWorkingData;
|
||||
std::vector<VuUpdate> vuUpdateResponseData;
|
||||
|
||||
|
||||
+183
-46
@@ -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<std::string>&existingNames)
|
||||
static std::string makeUniqueName(const std::string &name, const std::set<std::string> &existingNames)
|
||||
{
|
||||
if (!existingNames.contains(name))
|
||||
{
|
||||
@@ -729,9 +736,11 @@ static std::string makeUniqueName(const std::string &name, const std::set<std::s
|
||||
std::string baseName = stripNumericSuffix(name);
|
||||
|
||||
size_t i = 2;
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
std::string newName = SS(baseName << " (" << i << ")");
|
||||
if (!existingNames.contains(newName)) {
|
||||
if (!existingNames.contains(newName))
|
||||
{
|
||||
return newName;
|
||||
}
|
||||
++i;
|
||||
@@ -739,23 +748,27 @@ static std::string makeUniqueName(const std::string &name, const std::set<std::s
|
||||
}
|
||||
int64_t Storage::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
|
||||
{
|
||||
if (bankIndex.selectedBank() == bankInstanceId) {
|
||||
if (bankIndex.selectedBank() == bankInstanceId)
|
||||
{
|
||||
throw std::runtime_error("Can't import to self.");
|
||||
}
|
||||
std::set<int64_t> presetsSet { presets.begin(), presets.end()};
|
||||
std::set<int64_t> presetsSet{presets.begin(), presets.end()};
|
||||
auto indexEntry = this->bankIndex.getBankIndexEntry(bankInstanceId);
|
||||
|
||||
std::set<std::string> 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<int64_t> &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<int64_t> presetsSet { presets.begin(), presets.end()};
|
||||
std::set<int64_t> presetsSet{presets.begin(), presets.end()};
|
||||
|
||||
std::set<std::string> 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<PresetIndexEntry> Storage::RequestBankPresets(int64_t bankInstanceId
|
||||
|
||||
std::vector<PresetIndexEntry> 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<int64_t> &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<ChannelRouterSettings>();
|
||||
}
|
||||
}
|
||||
|
||||
static int64_t GetUpgradedPortIndex(const std::string &portName)
|
||||
{
|
||||
auto nPos = portName.find_last_of('_');
|
||||
if (nPos != std::string::npos)
|
||||
{
|
||||
std::string channelStr = portName.substr(nPos + 1);
|
||||
try
|
||||
{
|
||||
int64_t channelIndex = std::stoll(channelStr);
|
||||
return channelIndex;
|
||||
} catch (const std::exception &) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
void Storage::UpgradeChannelRouterSettings()
|
||||
{
|
||||
if (channelRouterSettings == nullptr)
|
||||
{
|
||||
channelRouterSettings = std::make_shared<ChannelRouterSettings>();
|
||||
if (jackChannelSelection.isValid())
|
||||
{
|
||||
channelRouterSettings->configured(true);
|
||||
channelRouterSettings->mainInputChannels().resize(2);
|
||||
const std::vector<std::string>& oldInputs = jackChannelSelection.GetInputAudioPorts();
|
||||
std::vector<int64_t>& newInputs = channelRouterSettings->mainInputChannels();
|
||||
|
||||
if (oldInputs.size() ==1)
|
||||
{
|
||||
int64_t leftIndex = GetUpgradedPortIndex(oldInputs[0]);
|
||||
newInputs.resize(2);
|
||||
newInputs[0] = leftIndex;
|
||||
newInputs[1] = leftIndex;
|
||||
} else if (oldInputs.size() == 2) {
|
||||
newInputs.resize(2);
|
||||
for (size_t i = 0; i < std::min(oldInputs.size(), newInputs.size()); ++i)
|
||||
{
|
||||
int64_t portIndex = GetUpgradedPortIndex(oldInputs[i]);
|
||||
newInputs[i] = portIndex;
|
||||
}
|
||||
}
|
||||
const std::vector<std::string>& oldOutputs = jackChannelSelection.GetOutputAudioPorts();
|
||||
auto &newMainOutputs = channelRouterSettings->mainOutputChannels();
|
||||
if (oldOutputs.size() == 1) {
|
||||
int64_t leftIndex = GetUpgradedPortIndex(oldOutputs[0]);
|
||||
newMainOutputs.resize(2);
|
||||
newMainOutputs[0] = leftIndex;
|
||||
newMainOutputs[1] = leftIndex;
|
||||
} else if (oldOutputs.size() == 2) {
|
||||
newMainOutputs.resize(2);
|
||||
for (size_t i = 0; i < std::min(oldOutputs.size(), newMainOutputs.size()); ++i)
|
||||
{
|
||||
int64_t portIndex = GetUpgradedPortIndex(oldOutputs[i]);
|
||||
newMainOutputs[i] = portIndex;
|
||||
}
|
||||
}
|
||||
|
||||
auto &newAuxInputs = channelRouterSettings->auxInputChannels();
|
||||
newAuxInputs.resize(2);
|
||||
newAuxInputs[0] = -1;
|
||||
newAuxInputs[1] = -1;
|
||||
auto &newAuxOutputs = channelRouterSettings->auxOutputChannels();
|
||||
newAuxOutputs.resize(2);
|
||||
newAuxOutputs[0] = -1;
|
||||
newAuxOutputs[1] = -1;
|
||||
SaveChannelRouterSettings(channelRouterSettings);
|
||||
}
|
||||
channelSelection = ChannelSelection(*channelRouterSettings);
|
||||
}
|
||||
}
|
||||
|
||||
const ChannelSelection& Storage::GetChannelSelection() const {
|
||||
return channelSelection;
|
||||
}
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(UserSettings)
|
||||
JSON_MAP_REFERENCE(UserSettings, governor)
|
||||
|
||||
+18
-2
@@ -32,6 +32,8 @@
|
||||
#include <map>
|
||||
#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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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[] = [(<MenuItem key={-1} value={-1}>None</MenuItem>)]
|
||||
if (channelCount === 0) {
|
||||
return items;
|
||||
}
|
||||
if (channelCount === 2) {
|
||||
items.push((<MenuItem key={0} value={0}>Left</MenuItem>));
|
||||
items.push((<MenuItem key={1} value={1}>Right</MenuItem>));
|
||||
return items;
|
||||
}
|
||||
for (let i = 0; i < channelCount; ++i) {
|
||||
items.push((<MenuItem key={i} value={i}>Ch {i + 1}</MenuItem>));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ChannelMixerSettingsDialog(props: ChannelMixerSettingsDialogProps) {
|
||||
//const classes = useStyles();
|
||||
const { open, onClose } = props;
|
||||
let model = PiPedalModelFactory.getInstance();
|
||||
let config = model.jackConfiguration.get();
|
||||
|
||||
|
||||
const [settings, setSettings] = useState<ChannelMixerSettings>(model.channelMixerSettings.get());
|
||||
const [controlValues,setControlValues] = useState<ControlValue[]>(model.channelMixerControlValues.get());
|
||||
const [showHelp, setShowHelp] = useState<boolean>(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 (<PluginControl
|
||||
instanceId={instanceId}
|
||||
uiControl={control}
|
||||
onPreviewChange={(value: number) => {
|
||||
model.previewPedalboardValue(instanceId, symbol, value);
|
||||
}}
|
||||
onChange={(value: number) => {
|
||||
model.setPedalboardControl(instanceId, symbol, value);
|
||||
}}
|
||||
requestIMEEdit={() => { }}
|
||||
value={value}
|
||||
/>);
|
||||
|
||||
return <DialIcon width={32} height={32} style={{ verticalAlign: "middle", fill: "white" }} />;
|
||||
}
|
||||
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 (
|
||||
<Select variant="standard" style={{ width: 80 }} value={value}
|
||||
onChange={(event) => {
|
||||
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)}
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
let Vu = () => {
|
||||
return (
|
||||
<div style={{ }} >
|
||||
<VuMeter instanceId={CHANNEL_MIXER_INSTANCE_ID} display={"input"} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let LandscapeView = () => {
|
||||
return (
|
||||
<div style={{ width: "100%", height: "100%", flexGrow: 1, fontSize: "14px" }} >
|
||||
<table style={{ borderCollapse: "collapse", width: "100%" }}>
|
||||
<colgroup>
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th style={cellLeftH}><Typography variant="caption" >In</Typography></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th style={cellLeftH}><Typography variant="caption" >Out</Typography></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{/* Main */}
|
||||
<tr>
|
||||
<td rowSpan={1} style={cellLeft}>
|
||||
<Typography variant="body2" color="textSecondary">Main</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, true)}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Dial("mainIn")}</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td rowSpan={2} style={cellLeft}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</td>
|
||||
|
||||
<td rowSpan={2} style={cellLeft}>{Dial("mainOut")}</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, false)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Main, 1, true))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Main, 1, false))}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer */}
|
||||
<tr><td colSpan={9} style={{ height: 16 }}></td></tr>
|
||||
|
||||
{/* Aux */}
|
||||
<tr>
|
||||
<td rowSpan={1} style={cellLeft}>
|
||||
<Typography variant="body2" color="textSecondary">Aux</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, true))}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Dial("auxIn")}</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td rowSpan={2} style={cellLeft}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</td>
|
||||
|
||||
<td rowSpan={2} style={cellLeft}>{Dial("auxOut")}</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, false))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, true))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, false))}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
let PortraitView = () => {
|
||||
return (
|
||||
<div style={{ width: "100%", height: "100%", flexGrow: 1, fontSize: "14px" }} >
|
||||
<table style={{ borderCollapse: "collapse", width: "100%" }}>
|
||||
<colgroup>
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "100%" }} />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{/* Main */}
|
||||
<tr>
|
||||
<td colSpan={4} style={cellSectionHead}>
|
||||
<Typography variant="body2" color="textSecondary" >Main</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellLeftIndent}>
|
||||
<Typography variant="body2" color="textSecondary" >In</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, true)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 1, true)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colSpan={3}>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={cellPortraitControlStrip}>{Dial("mainIn")}</td>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
<td style={cellPortraitControlStrip}>
|
||||
<div style={{ marginLeft: 8, marginRight: 8 }}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style={cellPortraitControlStrip}>{Dial("mainOut")}</td>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellLeftIndent}>
|
||||
<Typography variant="body2" color="textSecondary" >Out</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 1, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer */}
|
||||
<tr><td colSpan={4} style={{ height: 16 }}></td></tr>
|
||||
|
||||
{/* Aux */}
|
||||
<tr>
|
||||
<td colSpan={4} style={cellSectionHead}>
|
||||
<Typography variant="body2" color="textSecondary" >Aux</Typography>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellLeftIndent}>
|
||||
<Typography variant="body2" color="textSecondary" >In</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, true))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, true))}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colSpan={3}>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={cellPortraitControlStrip}>{Dial("auxIn")}</td>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
<td style={cellPortraitControlStrip}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</td>
|
||||
|
||||
<td style={cellPortraitControlStrip}>{Dial("auxOut")}</td>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellLeftIndent}>
|
||||
<Typography variant="body2" color="textSecondary" >Out</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Aux, 0, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Aux, 1, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogEx tag="channelMixerSettings" onClose={handleClose} aria-labelledby="select-channel_mixer_settings"
|
||||
open={open}
|
||||
fullWidth maxWidth="sm"
|
||||
onEnterKey={handleClose}
|
||||
fullScreen={fullScreen}
|
||||
style={{ maxWidth: 1200 }}
|
||||
>
|
||||
<DialogTitle id="select-channel_mixer_settings" style={{ paddingTop: 0, paddingBottom: 0, marginBottom: 8 }}>
|
||||
<Toolbar style={{ padding: 0, margin: 0 }}>
|
||||
<IconButtonEx
|
||||
tooltip="Close"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="cancel"
|
||||
style={{ opacity: 0.6 }}
|
||||
onClick={() => { onClose(); }}
|
||||
>
|
||||
<ArrowBackIcon style={{ width: 24, height: 24 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
<Typography noWrap component="div" sx={{ flexGrow: 1 }}>
|
||||
Channel Routing
|
||||
</Typography>
|
||||
|
||||
<IconButtonEx tooltip="Help"
|
||||
edge="end"
|
||||
aria-label="help"
|
||||
onClick={() => { setShowHelp(true); }}
|
||||
>
|
||||
<HelpOutlineIcon style={{ width: 24, height: 24, opacity: 0.6 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
|
||||
</Toolbar>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{landscape ?
|
||||
LandscapeView()
|
||||
:
|
||||
PortraitView()
|
||||
}
|
||||
</DialogContent>
|
||||
{showHelp && (
|
||||
<ChannelMixerSettingsHelpDialog open={showHelp} onClose={() => setShowHelp(false)} />
|
||||
)}
|
||||
</DialogEx>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelMixerSettingsDialog;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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[] = [(<MenuItem key={-1} value={-1}>None</MenuItem>)]
|
||||
if (channelCount === 0) {
|
||||
return items;
|
||||
}
|
||||
if (channelCount === 2) {
|
||||
items.push((<MenuItem key={0} value={0}>Left</MenuItem>));
|
||||
items.push((<MenuItem key={1} value={1}>Right</MenuItem>));
|
||||
return items;
|
||||
}
|
||||
for (let i = 0; i < channelCount; ++i) {
|
||||
items.push((<MenuItem key={i} value={i}>Ch {i + 1}</MenuItem>));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
|
||||
//const classes = useStyles();
|
||||
const { open, onClose } = props;
|
||||
let model = PiPedalModelFactory.getInstance();
|
||||
let config = model.jackConfiguration.get();
|
||||
|
||||
|
||||
const [settings, setSettings] = useState<ChannelRouterSettings>(model.channelRouterSettings.get());
|
||||
const [controlValues, setControlValues] = useState<ControlValue[]>(model.channelRouterControlValues.get());
|
||||
const [showHelp, setShowHelp] = useState<boolean>(false);
|
||||
|
||||
if (settings) {
|
||||
}
|
||||
if (controlValues) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
let handleSettingsChanged = () => {
|
||||
setSettings(model.channelRouterSettings.get());
|
||||
};
|
||||
model.channelRouterSettings.addOnChangedHandler(handleSettingsChanged);
|
||||
let handleControlsChanged = () => {
|
||||
setControlValues(model.channelRouterControlValues.get());
|
||||
};
|
||||
model.channelRouterControlValues.addOnChangedHandler(handleControlsChanged);
|
||||
return () => {
|
||||
model.channelRouterSettings.removeOnChangedHandler(handleSettingsChanged);
|
||||
model.channelRouterControlValues.removeOnChangedHandler(handleControlsChanged);
|
||||
}
|
||||
} else {
|
||||
return () => { };
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleClose = (): void => {
|
||||
onClose();
|
||||
};
|
||||
let [windowSize] = useWindowSize();
|
||||
let landscape = windowSize.width > windowSize.height;
|
||||
let fullScreen = windowSize.width < 450 || windowSize.height < 600;
|
||||
|
||||
let ChannelSelect = (routeType: RouteType, channelIndex: number, input: boolean, icon: boolean = true) => {
|
||||
let channelCount = input ? config.inputAudioPorts.length : config.outputAudioPorts.length;
|
||||
|
||||
let value: number;
|
||||
|
||||
switch (routeType) {
|
||||
case RouteType.Main:
|
||||
value = input ? settings.mainInputChannels[channelIndex] : settings.mainOutputChannels[channelIndex];
|
||||
break;
|
||||
case RouteType.Aux:
|
||||
value = input ? settings.auxInputChannels[channelIndex] : settings.auxOutputChannels[channelIndex];
|
||||
break;
|
||||
case RouteType.Send:
|
||||
value = input ? settings.sendInputChannels[channelIndex] : settings.sendOutputChannels[channelIndex];
|
||||
break;
|
||||
}
|
||||
if (value >= channelCount) {
|
||||
value = -1;
|
||||
}
|
||||
return (
|
||||
<Select variant="standard" style={{ width: 90 }} value={value}
|
||||
startAdornment={
|
||||
icon ? (
|
||||
input ? (<MicIcon style={{ opacity: 0.6, width: 16, height: 16, marginRight: 4 }} />) :
|
||||
(<SpeakerIcon style={{ opacity: 0.6, width: 16, height: 16, marginRight: 4 }} />)
|
||||
) : undefined
|
||||
}
|
||||
onChange={(event) => {
|
||||
let newValue = event.target.value as number;
|
||||
let newSettings = 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)}
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
let Vu = () => {
|
||||
return (
|
||||
<div style={{}} >
|
||||
<div style={{ width: 8, height: 48, background: "black" }}></div>
|
||||
{/* <VuMeter instanceId={Pedalboard.CHANNEL_ROUTER_MAIN_INSERT_ID} display={"input"} /> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let ApplyPresetId = (presetId: number | string | null | undefined) => {
|
||||
if (typeof presetId !== 'number') {
|
||||
return;
|
||||
}
|
||||
for (let preset of channelRouterPresets) {
|
||||
if (preset.id === presetId) {
|
||||
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(
|
||||
<MenuItem key={preset.id} value={preset.id}>
|
||||
{preset.name}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
if (settings.channelRouterPresetId === -1) {
|
||||
items.push(
|
||||
(<MenuItem key={-1} value={-1} disabled={true}>
|
||||
<span style={{ opacity: 0.6 }}>(Custom)</span></MenuItem>)
|
||||
)
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
let PresetSelect = (width: number | string | undefined) => {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "row", marginTop: 2, alignItems: "center" }}>
|
||||
<Select variant="standard" style={{ width: width }} value={settings.channelRouterPresetId}
|
||||
onChange={(event) => {
|
||||
ApplyPresetId(event.target.value);
|
||||
}}
|
||||
>
|
||||
{GeneratePresetMenuItems()}
|
||||
</Select>
|
||||
<IconButtonEx tooltip="Presets" aria-label="more-presets">
|
||||
<MoreVertIcon style={{ opacity: 0.6 }} onClick={() => { }} />
|
||||
</IconButtonEx>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
let LandscapeView = () => {
|
||||
return (
|
||||
<div style={{
|
||||
display: "flex", flexFlow: "row", alignItems: "center", justifyContent: "center",
|
||||
width: "100%", height: "100%", flexGrow: 1, fontSize: "14px"
|
||||
}} >
|
||||
<table style={{ borderCollapse: "collapse" }}>
|
||||
<colgroup>
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{/* Main */}
|
||||
<tr>
|
||||
<td rowSpan={1} style={cellLandscapeTitle}>
|
||||
<Typography variant="body2" color="textSecondary">Main</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, true)}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td rowSpan={2} style={cellLeft}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</td>
|
||||
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, false)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Main, 1, true))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Main, 1, false))}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer ---------------- */}
|
||||
<tr><td colSpan={9} style={{ height: 16 }}></td></tr>
|
||||
|
||||
{/* Aux ----------------------*/}
|
||||
<tr>
|
||||
<td rowSpan={1} style={cellLandscapeTitle}>
|
||||
<Typography variant="body2" color="textSecondary">Aux</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, true))}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td rowSpan={2} style={cellLeft}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</td>
|
||||
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, false))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, true))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, false))}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer ---------------- */}
|
||||
<tr><td colSpan={9} style={{ height: 16 }}></td></tr>
|
||||
|
||||
{/* Send ---------------- */}
|
||||
<tr>
|
||||
<td rowSpan={1} style={cellLandscapeTitle}>
|
||||
<Typography variant="body2" color="textSecondary">Send</Typography>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 0, false))}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td rowSpan={2} style={cellLeft}>
|
||||
{/* No button */}
|
||||
</td>
|
||||
<td rowSpan={2} style={cellLeft}>{Vu()}</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 0, true))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 1, false))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 1, true))}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
let PortraitView = () => {
|
||||
return (
|
||||
<div style={{
|
||||
display: "flex", flexFlow: "row", alignItems: "center", justifyContent: "center",
|
||||
width: "100%", flexGrow: 0, fontSize: "14px"
|
||||
}} >
|
||||
<table style={{ borderCollapse: "collapse" }}>
|
||||
<colgroup>
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
<col style={{ width: "auto" }} />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{/* Main */}
|
||||
<tr>
|
||||
<td colSpan={1} style={cellPortraitSectionHead}>
|
||||
<Typography variant="body2" color="textSecondary" >Main</Typography>
|
||||
</td>
|
||||
<td colSpan={4}>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >In</Typography>
|
||||
<MicIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 0, true, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 1, true, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colSpan={4}>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
<td style={cellPortraitControlStrip}>
|
||||
<div style={{ marginLeft: 8, marginRight: 8 }}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >Out</Typography>
|
||||
<SpeakerIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style={cellLeft}>
|
||||
|
||||
{ChannelSelect(RouteType.Main, 0, false, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Main, 1, false, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer ---------------- */}
|
||||
<tr><td colSpan={5} style={{ height: 16 }}></td></tr>
|
||||
{/* Aux */}
|
||||
<tr>
|
||||
<td colSpan={1} style={cellPortraitSectionHead}>
|
||||
<Typography variant="body2" color="textSecondary" >Aux</Typography>
|
||||
</td>
|
||||
<td colSpan={4}></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >In</Typography>
|
||||
<MicIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 0, true, false))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Aux, 1, true, false))}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colSpan={4}>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
<td style={cellPortraitControlStrip}>
|
||||
<div style={{ marginLeft: 8, marginRight: 8 }}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >Out</Typography>
|
||||
<SpeakerIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Aux, 0, false, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Aux, 1, false, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
{/* Spacer ---------------- */}
|
||||
<tr><td colSpan={5} style={{ height: 16 }}></td></tr>
|
||||
|
||||
{/* Send */}
|
||||
<tr>
|
||||
<td colSpan={1} style={cellPortraitSectionHead}>
|
||||
<Typography variant="body2" color="textSecondary" >Send</Typography>
|
||||
</td>
|
||||
<td colSpan={4}></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >Send</Typography>
|
||||
<SpeakerIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 0, false, false))}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{(ChannelSelect(RouteType.Send, 1, false, false))}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td colSpan={4}>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
<td style={cellPortraitControlStrip}>
|
||||
<div style={{ marginLeft: 8, marginRight: 8, visibility: "hidden" }}>
|
||||
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}>
|
||||
Inserts
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td style={cellPortraitControlStrip}>{Vu()}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={cellPortraitInOut}>
|
||||
<div style={cellPortraitInOutDiv}>
|
||||
<Typography variant="body2" color="textSecondary" >Return</Typography>
|
||||
<MicIcon style={{ verticalAlign: "middle", width: 16, height: 16, opacity: 0.6 }} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Send, 0, true, false)}
|
||||
</td>
|
||||
<td style={cellLeft}>
|
||||
{ChannelSelect(RouteType.Send, 1, false, false)}
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogEx tag="channelRouterSettings"
|
||||
onClose={handleClose}
|
||||
aria-labelledby="select-channel_mixer_settings"
|
||||
open={open}
|
||||
fullWidth
|
||||
maxWidth={landscape ? "sm" : "xs"}
|
||||
onEnterKey={handleClose}
|
||||
fullScreen={fullScreen}
|
||||
>
|
||||
<DialogTitle id="select-channel_mixer_settings" style={{ paddingTop: 0, paddingBottom: 0, marginBottom: 0 }}>
|
||||
<div style={{
|
||||
display: "flex", flexFlow: "row", flexWrap: "nowrap", alignItems: "center",
|
||||
marginTop: 8
|
||||
|
||||
}}>
|
||||
<IconButtonEx
|
||||
tooltip="Close"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="cancel"
|
||||
style={{ opacity: 0.6 }}
|
||||
onClick={() => { onClose(); }}
|
||||
>
|
||||
<ArrowBackIcon style={{ width: 24, height: 24 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
<Typography noWrap component="div" sx={{}}>
|
||||
Channel Routing
|
||||
</Typography>
|
||||
{
|
||||
landscape ? (
|
||||
<>
|
||||
<div style={{ flexGrow: 1 }}> </div>
|
||||
<div style={{ flexShrink: 1, maxWidth: 440, display: "relative", marginLeft: 16, marginRight: 16 }}>
|
||||
{PresetSelect(200)}
|
||||
</div>
|
||||
<div style={{ flexGrow: 1 }}> </div>
|
||||
</>
|
||||
|
||||
) : (
|
||||
<div style={{ flexGrow: 1 }}> </div>
|
||||
)
|
||||
}
|
||||
|
||||
<IconButtonEx tooltip="Help"
|
||||
edge="end"
|
||||
aria-label="help"
|
||||
onClick={() => { setShowHelp(true); }}
|
||||
>
|
||||
<HelpOutlineIcon style={{ width: 24, height: 24, opacity: 0.6 }} />
|
||||
</IconButtonEx>
|
||||
|
||||
</div>
|
||||
</DialogTitle>
|
||||
{!landscape && (
|
||||
<>
|
||||
<div style={{ marginLeft: 16 + 32 + 8, marginBottom: 8 }}>
|
||||
{PresetSelect("60%")}
|
||||
</div>
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
<DialogContent>
|
||||
{landscape ?
|
||||
LandscapeView()
|
||||
:
|
||||
PortraitView()
|
||||
}
|
||||
</DialogContent>
|
||||
{
|
||||
showHelp && (
|
||||
<ChannelRouterSettingsHelpDialog open={showHelp} onClose={() => setShowHelp(false)} />
|
||||
)
|
||||
}
|
||||
</DialogEx >
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelRouterSettingsDialog;
|
||||
+14
-7
@@ -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<ChannelMixerSettingsHelpDialogProps, ChannelMixerSettingsHelpDialogState> {
|
||||
export default class ChannelRouterSettingsHelpDialog extends ResizeResponsiveComponent<ChannelRouterSettingsHelpDialogProps, ChannelRouterSettingsHelpDialogState> {
|
||||
|
||||
refText: React.RefObject<HTMLInputElement | null>;
|
||||
|
||||
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.
|
||||
</p>
|
||||
<p>The Send route, in conjunction with the TooB Send plugin allows you to send and return audio signals to an external physical device
|
||||
from within a PiPedal preset. This allows you to integrate external hardware effects into your PiPedal presets.
|
||||
Connect a cable from the audio interface output <i>Send</i> channel(s) selected here to the input of your
|
||||
hardware effect, and a cable from the output of your hardware effect to the audio interface's <i>Return</i> channel
|
||||
assigned here. Then insert the TooB Send plugin into your PiPedal preset. The input of the
|
||||
TooB Send plugin will be sent to the external hardware effect, and the output the external hardware
|
||||
effect will be returned to PiPedal at the output of the TooB Send plugin. PiPedal currently supports
|
||||
only one Send plugin instance per preset; and the Send and Return channels may not be shared with the Main
|
||||
or Aux routes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</DialogContent>
|
||||
+2
-3
@@ -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: "",
|
||||
@@ -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<Pedalboard> {
|
||||
|
||||
static readonly CHANNEL_ROUTER_CONTROLS_INSTANCE_ID = -4; // Reserved Instance ID for Router Main Inserts.
|
||||
static readonly CHANNEL_ROUTER_MAIN_INSERT_ID = -5; // Reserved Instance ID for Router Main Inserts.
|
||||
static readonly CHANNEL_ROUTER_AUX_INSERT_ID = -6; // Reserved Instance ID for Router Aux inserts.
|
||||
|
||||
static readonly MAIN_INSERT_INSTANCE_BASE = TWO_POWER_52-2*MAX_INSTANCE_ID;
|
||||
static readonly AUX_INSERT_INSTANCE_BASE = TWO_POWER_52-1*MAX_INSTANCE_ID;
|
||||
|
||||
static readonly START_CONTROL = -2; // synthetic PedalboardItem for input volume.
|
||||
static readonly 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<Pedalboard> {
|
||||
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<Pedalboard> {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<JackServerSettings>
|
||||
= new ObservableProperty<JackServerSettings>(new JackServerSettings());
|
||||
|
||||
channelMixerControlValues: ObservableProperty<ControlValue[]> = new ObservableProperty<ControlValue[]>([]);
|
||||
channelMixerSettings: ObservableProperty<ChannelMixerSettings> = new ObservableProperty<ChannelMixerSettings>(new ChannelMixerSettings());
|
||||
channelRouterControlValues: ObservableProperty<ControlValue[]> = new ObservableProperty<ControlValue[]>([]);
|
||||
channelRouterSettings: ObservableProperty<ChannelRouterSettings> = new ObservableProperty<ChannelRouterSettings>(new ChannelRouterSettings());
|
||||
|
||||
setChannelMixerSettings(settings: ChannelMixerSettings) {
|
||||
this.channelMixerSettings.set(settings);
|
||||
this.channelMixerControlValues.set(settings.controls);
|
||||
}
|
||||
wifiConfigSettings: ObservableProperty<WifiConfigSettings> = new ObservableProperty<WifiConfigSettings>(new WifiConfigSettings());
|
||||
wifiDirectConfigSettings: ObservableProperty<WifiDirectConfigSettings> = new ObservableProperty<WifiDirectConfigSettings>(new WifiDirectConfigSettings());
|
||||
governorSettings: ObservableProperty<GovernorSettings> = new ObservableProperty<GovernorSettings>(new GovernorSettings());
|
||||
@@ -1255,6 +1251,11 @@ export class PiPedalModel //implements PiPedalModel
|
||||
await this.getWebSocket().request<any>("getJackServerSettings")
|
||||
)
|
||||
);
|
||||
this.channelRouterSettings.set(
|
||||
new ChannelRouterSettings().deserialize(
|
||||
await this.getWebSocket().request<any>("getChannelRouterSettings")
|
||||
)
|
||||
)
|
||||
this.jackConfiguration.set(new JackConfiguration().deserialize(
|
||||
await this.getWebSocket().request<JackConfiguration>("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;
|
||||
|
||||
@@ -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(
|
||||
<Typography display="block" variant="caption" noWrap color="textSecondary">{this.state.jackServerSettings.getSummaryText()}</Typography>
|
||||
</div>
|
||||
</ButtonBase>
|
||||
{this.state.showChannelMixerSettingsDialog && (
|
||||
<ChannelMixerSettingsDialog
|
||||
open={this.state.showChannelMixerSettingsDialog}
|
||||
onClose={() => this.setState({ showChannelMixerSettingsDialog: false })}
|
||||
{this.state.showChannelRouterSettingsDialog && (
|
||||
<ChannelRouterSettingsDialog
|
||||
open={this.state.showChannelRouterSettingsDialog}
|
||||
onClose={() => this.setState({ showChannelRouterSettingsDialog: false })}
|
||||
/>
|
||||
)}
|
||||
{this.state.showJackServerSettingsDialog && (
|
||||
@@ -744,7 +744,7 @@ const SettingsDialog = withStyles(
|
||||
<ButtonBase className={classes.setting}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
showChannelMixerSettingsDialog: true
|
||||
showChannelRouterSettingsDialog: true
|
||||
});
|
||||
}}
|
||||
disabled={!hasAudioConfig}
|
||||
|
||||
@@ -372,7 +372,7 @@ const ToobParametricEqView =
|
||||
])}
|
||||
{panel("", [
|
||||
controls.gain
|
||||
])}
|
||||
])}
|
||||
{divider()}
|
||||
{panel("Low",
|
||||
[controls.low_level,
|
||||
|
||||
Reference in New Issue
Block a user