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

This commit is contained in:
Robin E.R. Davies
2026-02-10 20:18:09 -05:00
48 changed files with 2959 additions and 595 deletions
+237 -95
View File
@@ -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,29 @@ 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<uint8_t> rawCaptureBuffer;
std::vector<uint8_t> rawPlaybackBuffer;
@@ -742,7 +762,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 +780,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 +796,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 +812,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 +829,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 +845,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 +862,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 +879,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 +895,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 +911,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 +927,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 +947,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 +967,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 +989,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 +1011,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 +1031,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 +1051,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 +1076,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 +1102,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 +1117,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 +1147,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 +1269,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 +1280,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 +1348,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 +1744,7 @@ namespace pipedal
}
return 0;
}
void AudioThread()
PIPEDAL_NON_INLINE void AudioThread()
{
SetThreadName("alsaDriver");
@@ -1797,6 +1824,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 +1864,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 +1876,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 +1889,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 +2047,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 +2095,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 +2383,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 +2399,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);
}
+2 -1
View File
@@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2025 Robin E. R. Davies
* Copyright (c) 2026 Robin E. R. Davies
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -24,6 +24,7 @@
#pragma once
#include <lilv/lilv.h>
#include "AudioDriver.hpp"
#include "JackServerSettings.hpp"
+113 -98
View File
@@ -1,18 +1,18 @@
/*
* MIT License
*
*
* Copyright (c) 2022 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
* 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
@@ -27,28 +27,34 @@
#include <cmath>
#include <mutex>
#include <iostream>
#include <algorithm>
#include "AlsaDriver.hpp"
#include "JackDriver.hpp"
#include "ChannelRouterSettings.hpp"
using namespace pipedal;
using namespace std;
class AlsaTester: private AudioDriverHost {
class AlsaTester : private AudioDriverHost
{
public:
enum class TestType { Oscillator, LatencyMonitor, NullTest};
enum class TestType
{
Oscillator,
LatencyMonitor,
NullTest
};
private:
AudioDriver *audioDriver = nullptr;
TestType testType;
public:
AlsaTester(TestType testType)
: testType(testType)
: testType(testType)
{
// audioDriver = CreateAlsaDriver(this);
// audioDriver = CreateJackDriver(this);
}
~AlsaTester()
{
@@ -57,47 +63,47 @@ public:
delete[] outputBuffers;
}
bool useJack = false;
void initializeChannelSelection(JackConfiguration &jackConfiguration, ChannelSelection &channelSelection)
{
size_t nInputs = std::max(jackConfiguration.inputAudioPorts().size(),size_t(2));
size_t nOutputs = std::max(jackConfiguration.outputAudioPorts().size(),size_t(2));
auto &inputSelections = channelSelection.mainInputChannels();
auto &outputSelections = channelSelection.mainOutputChannels();
inputSelections.resize(nInputs);
for (size_t i = 0; i < nInputs; ++i)
{
inputSelections[i] = i;
}
outputSelections.resize(nOutputs);
for (size_t i = 0; i < nOutputs; ++i)
{
outputSelections[i] = i;
}
}
void Test()
{
AlsaFormatEncodeDecodeTest(this);
JackServerSettings serverSettings("hw:M2","hw:M2",48000,32,3);
JackServerSettings serverSettings("hw:M2", "hw:M2", 48000, 32, 3);
JackConfiguration jackConfiguration;
if (useJack)
{
jackConfiguration.JackInitialize();
} else {
jackConfiguration.AlsaInitialize(serverSettings);
}
jackConfiguration.AlsaInitialize(serverSettings);
JackChannelSelection channelSelection(
jackConfiguration.inputAudioPorts(),
jackConfiguration.outputAudioPorts(),
jackConfiguration.inputMidiDevices());
ChannelSelection channelSelection;
#if JACK_HOST
if (useJack)
{
audioDriver = CreateJackDriver(this);
} else {
audioDriver = CreateAlsaDriver(this);
}
#else
audioDriver = CreateAlsaDriver(this);
#endif
initializeChannelSelection(jackConfiguration, channelSelection);
oscillator.Init(440,jackConfiguration.sampleRate());
oscillator.Init(440, jackConfiguration.sampleRate());
latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings,channelSelection);
audioDriver->Open(serverSettings, channelSelection);
inputBuffers = new float*[channelSelection.GetInputAudioPorts().size()];
outputBuffers = new float*[channelSelection.GetOutputAudioPorts().size()];
inputBuffers = new float *[channelSelection.mainInputChannels().size()];
outputBuffers = new float *[channelSelection.mainOutputChannels().size()];
audioDriver->Activate();
@@ -107,7 +113,7 @@ public:
if (testType == TestType::LatencyMonitor)
{
auto latency = this->latencyMonitor.GetLatency();
double ms = 1000.0*latency/jackConfiguration.sampleRate();
double ms = 1000.0 * latency / jackConfiguration.sampleRate();
cout << "Latency: " << latency << " samples " << ms << "ms" << " xruns: " << GetXruns() << " Cpu: " << audioDriver->CpuUse() << "%" << endl;
}
@@ -115,37 +121,40 @@ public:
audioDriver->Deactivate();
audioDriver->Close();
}
float**inputBuffers = nullptr;
float**outputBuffers = nullptr;
float **inputBuffers = nullptr;
float **outputBuffers = nullptr;
class Oscillator {
class Oscillator
{
private:
double dx = 0;
double x = 0;
double dx2 = 0;
double x2 = 0;
public:
public:
void Init(float frequency, size_t sampleRate)
{
dx = frequency*3.141592736*2/sampleRate;
dx2 = 0.5*3.141592736*2/sampleRate;
dx = frequency * 3.141592736 * 2 / sampleRate;
dx2 = 0.5 * 3.141592736 * 2 / sampleRate;
}
float Next() {
float Next()
{
float result = (float)std::cos(x);
float env = (float)std::cos(x2);
x += dx;
x2 += dx2;
return result*env;
return result * env;
}
};
class LatencyMonitor {
enum class State {
class LatencyMonitor
{
enum class State
{
Idle,
Waiting,
};
@@ -156,91 +165,99 @@ public:
size_t current_latency = 0;
size_t latency = 0;
std::mutex sync;
public:
void Init(uint64_t sampleRate)
{
idle_samples = (uint64_t)sampleRate*2;
waiting_samples = (uint64_t)sampleRate*2;
idle_samples = (uint64_t)sampleRate * 2;
waiting_samples = (uint64_t)sampleRate * 2;
state = State::Idle;
t = idle_samples;
latency = 0;
}
size_t GetLatency() {
std::lock_guard lock { sync};
size_t GetLatency()
{
std::lock_guard lock{sync};
return latency;
}
float Next(float input)
{
switch(state)
switch (state)
{
default:
case State::Idle:
default:
case State::Idle:
{
if (t-- == 0)
{
if (t-- == 0) {
state = State::Waiting;
current_latency = 0;
}
return 0.01;
state = State::Waiting;
current_latency = 0;
}
break;
case State::Waiting:
return 0.01;
}
break;
case State::Waiting:
{
if (std::abs(input) > 0.1 || current_latency > 500)
{
if (std::abs(input) > 0.1 || current_latency > 500) {
{
std::lock_guard lock { sync};
latency = current_latency;
}
state = State::Idle;
t = idle_samples;
} else {
++current_latency;
{
std::lock_guard lock{sync};
latency = current_latency;
}
return current_latency < 100 ? 0.25 : 0.0;
state = State::Idle;
t = idle_samples;
}
break;
else
{
++current_latency;
}
return current_latency < 100 ? 0.25 : 0.0;
}
break;
}
}
};
Oscillator oscillator;
LatencyMonitor latencyMonitor;
virtual void OnAudioTerminated() {
virtual void OnAudioTerminated()
{
}
virtual void OnAlsaDriverStopped() {
virtual void OnAlsaDriverStopped()
{
}
virtual void OnProcess(size_t nFrames) {
if (testType == TestType::NullTest) return;
virtual void OnProcess(size_t nFrames)
{
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)
{
for (size_t i = 0; i < nFrames; ++i)
{
float v = oscillator.Next()*0.25f;
float v = oscillator.Next() * 0.25f;
for (size_t c = 0; c < outputs; ++c)
{
outputBuffers[c][i] = v;
}
}
}
else {
else
{
for (size_t i = 0; i < nFrames; ++i)
{
float v = latencyMonitor.Next(inputBuffers[0][i]);
@@ -249,36 +266,34 @@ public:
outputBuffers[c][i] = v;
}
}
}
}
std::mutex sync;
uint64_t xruns;
uint64_t GetXruns() {
lock_guard lock { sync};
uint64_t GetXruns()
{
lock_guard lock{sync};
return xruns;
}
virtual void OnUnderrun() {
lock_guard lock { sync };
virtual void OnUnderrun()
{
lock_guard lock{sync};
++xruns;
}
};
TEST_CASE("alsa_test", "[alsa_test]")
{
TEST_CASE( "alsa_test", "[alsa_test]" ) {
AlsaTester alsaDriver(AlsaTester::TestType::Oscillator);
alsaDriver.Test();
}
TEST_CASE( "alsa_midi_test", "[alsa_midi_test]" ) {
TEST_CASE("alsa_midi_test", "[alsa_midi_test]")
{
AlsaTester alsaDriver(AlsaTester::TestType::Oscillator);
MidiDecoderTest();
}
+19 -5
View File
@@ -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
View File
@@ -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::PedalboardType instanceType = Pedalboard::GetInstanceTypeFromInstanceId(instanceId);
int64_t index = -1;
switch (instanceType)
{
case Pedalboard::PedalboardType::MainPedalboard:
if (this->currentPedalboard) {
if (instanceId == Pedalboard::START_CONTROL_ID) {
index = Pedalboard::START_CONTROL_ID;
} else if (instanceId == Pedalboard::END_CONTROL_ID) {
index = Pedalboard::END_CONTROL_ID;
} else {
index = this->currentPedalboard->GetIndexOfInstanceId(instanceId);
}
}
break;
case Pedalboard::PedalboardType::MainInsert:
if (this->currentMainInsertPedalboard) {
if (instanceId == Pedalboard::MAIN_INSERT_START_CONTROL_ID) {
index = Pedalboard::MAIN_INSERT_START_CONTROL_ID;
} else if (instanceId == Pedalboard::MAIN_INSERT_END_CONTROL_ID) {
index = Pedalboard::END_CONTROL_ID;
} else {
index = this->currentMainInsertPedalboard->GetIndexOfInstanceId(instanceId);
}
}
break;
case Pedalboard::PedalboardType::AuxInsert:
if (this->currentAuxInsertPedalboard) {
if (instanceId == Pedalboard::AUX_INSERT_START_CONTROL_ID) {
index = Pedalboard::START_CONTROL_ID;
} else if (instanceId == Pedalboard::AUX_INSERT_END_CONTROL_ID) {
index = Pedalboard::END_CONTROL_ID;
} else {
index = this->currentAuxInsertPedalboard->GetIndexOfInstanceId(instanceId);
}
}
break;
}
RealtimePedalboardItemIndex result { instanceType, index};
return result;
}
virtual void SetVuSubscriptions(const std::vector<int64_t> &instanceIds)
{
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
View File
@@ -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
View File
@@ -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::PedalboardType::MainPedalboard);
this->addPreset(pedalboard);
newSelection = presets_[0]->instanceId();
}
+10 -3
View File
@@ -204,6 +204,7 @@ else()
endif()
set (PIPEDAL_SOURCES
ChannelRouterSettings.cpp ChannelRouterSettings.hpp
Curl.cpp Curl.hpp
Tone3000DownloadType.cpp Tone3000DownloadType.hpp
Tone3000DownloadProgress.cpp Tone3000DownloadProgress.hpp
@@ -358,8 +359,9 @@ target_compile_definitions(libpipedald PUBLIC "_REENTRANT")
target_include_directories(libpipedald PUBLIC ${PIPEDAL_INCLUDES})
target_link_libraries(libpipedald
PUBLIC
PUBLIC
PiPedalCommon
PRIVATE
SQLiteCpp
${PipeWire_LIBRARIES}
)
@@ -768,6 +770,7 @@ add_executable(pipedal_latency_test
target_link_libraries(pipedal_latency_test PRIVATE pthread asound PiPedalCommon)
target_include_directories(pipedal_latency_test PRIVATE ${PIPEDAL_INCLUDES})
add_executable(pipedal_alsa_info
alsaCheck.cpp alsaCheck.hpp
@@ -865,10 +868,14 @@ add_custom_target (
)
install (TARGETS pipedalconfig pipedal_kconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
EXPORT pipedalTargets)
EXPORT pipedalTargets
)
install (TARGETS pipedald pipedaladmind pipedal_update DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
EXPORT pipedalSbinTargets)
EXPORT pipedalSbinTargets
)
+151
View File
@@ -0,0 +1,151 @@
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "ChannelRouterSettings.hpp"
#include <stdexcept>
using namespace pipedal;
static uint64_t countChannels(const std::vector<int64_t> &channels)
{
if (channels.size() != 2)
{
throw std::runtime_error("Invalid Channel Router channel configuration.");
}
if (channels[0] == -1)
{
if (channels[1] == -1)
{
return 0;
}
else
{
return 1;
}
}
else
{
if (channels[1] == -1)
{
return 1;
}
if (channels[0] == channels[1])
{
return 1;
}
return 2;
}
}
uint64_t ChannelRouterSettings::numberOfAudioInputChannels() const
{
if (!configured_)
{
return 2;
}
return countChannels(mainInputChannels_);
}
uint64_t ChannelRouterSettings::numberOfAudioOutputChannels() const
{
if (!configured_)
{
return 2;
}
return countChannels(mainOutputChannels_);
}
ChannelSelection::ChannelSelection(ChannelRouterSettings&settings)
: mainInputChannels_(settings.mainInputChannels())
, mainOutputChannels_(settings.mainOutputChannels())
, auxInputChannels_(settings.auxInputChannels())
, auxOutputChannels_(settings.auxOutputChannels())
, sendInputChannels_(settings.sendInputChannels())
, sendOutputChannels_(settings.sendOutputChannels())
{
normalizeChannelSelection();
}
static void normalizeInputChannels(std::vector<int64_t>&channels) {
if (channels.size() == 2) {
if (channels[0] == -1 && channels[1] == -1)
{
channels.resize(0);
} else if (channels[0] == channels[1]) {
channels.resize(1);
} else if (channels[1] == -1) {
channels.resize(1);} {
}
}
}
static void normalizeOutputChannels(std::vector<int64_t>&channels) {
normalizeInputChannels(channels);
}
void ChannelSelection::normalizeChannelSelection() {
normalizeInputChannels(mainInputChannels_);
normalizeOutputChannels(mainOutputChannels_);
normalizeInputChannels(auxInputChannels_);
normalizeOutputChannels(auxOutputChannels_);
normalizeInputChannels(sendInputChannels_);
normalizeOutputChannels(sendOutputChannels_);
// If either aux inputs or outputs are zero, don't do ANY aux processing.
if (auxInputChannels_.size() == 0)
{
auxOutputChannels_.resize(0);
} else if (auxOutputChannels_.size() == 0) {
auxInputChannels_.resize(0);
}
// If either main inputs or outputs are empty, add send/receive dummy buffers. (Lv2 plugins shouldn't have to deal with this)
if (mainInputChannels_.size() == 0)
{
mainInputChannels_.resize(1);
mainInputChannels_[0] = -1;
}
if (mainOutputChannels_.size() == 0) {
mainInputChannels_.resize(1);
mainOutputChannels_[0] = -1;
}
// Send buffers are what they are. Let the send plugin deal with it.
}
ChannelRouterSettings::ChannelRouterSettings()
: mainInserts_(Pedalboard::PedalboardType::MainInsert)
, auxInserts_(Pedalboard::PedalboardType::AuxInsert)
{
}
JSON_MAP_BEGIN(ChannelRouterSettings)
JSON_MAP_REFERENCE(ChannelRouterSettings, configured)
JSON_MAP_REFERENCE(ChannelRouterSettings, channelRouterPresetId)
JSON_MAP_REFERENCE(ChannelRouterSettings, mainInputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, mainOutputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, mainInserts)
JSON_MAP_REFERENCE(ChannelRouterSettings, auxInputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, auxOutputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, auxInserts)
JSON_MAP_REFERENCE(ChannelRouterSettings, sendInputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, sendOutputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, controlValues)
JSON_MAP_END()
+113
View File
@@ -0,0 +1,113 @@
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <vector>
#include "json.hpp"
#include "Pedalboard.hpp"
#include <memory>
namespace pipedal
{
class ChannelRouterSettings
{
protected:
static constexpr int64_t CHANNEL_ROUTER_MAIN_INSERT_ID = -4; // Reserved Instance ID for Router Main Inserts.
static constexpr int64_t CHANNEL_ROUTER_AUX_INSERT_ID = -5; // Reserved Instance ID for Router Aux inserts.
bool configured_ = false;
int64_t channelRouterPresetId_ = -1;
std::vector<int64_t> mainInputChannels_ = {1, 1};
std::vector<int64_t> mainOutputChannels_ = {0, 1};
Pedalboard mainInserts_;
std::vector<int64_t> auxInputChannels_ = {-1, -1};
std::vector<int64_t> auxOutputChannels_ = {-1, -1};
Pedalboard auxInserts_;
std::vector<int64_t> sendInputChannels_ = {-1, -1};
std::vector<int64_t> sendOutputChannels_ = {-1, -1};
std::vector<ControlValue> controlValues_;
public:
using self = ChannelRouterSettings;
using ptr = std::shared_ptr<self>;
ChannelRouterSettings();
uint64_t numberOfAudioInputChannels() const;
uint64_t numberOfAudioOutputChannels() const;
JSON_GETTER_SETTER(configured)
JSON_GETTER_SETTER(channelRouterPresetId)
JSON_GETTER_SETTER_REF(mainInputChannels)
JSON_GETTER_SETTER_REF(mainOutputChannels)
JSON_GETTER_SETTER_REF(mainInserts)
JSON_GETTER_SETTER_REF(auxInputChannels)
JSON_GETTER_SETTER_REF(auxOutputChannels)
JSON_GETTER_SETTER_REF(auxInserts)
JSON_GETTER_SETTER_REF(sendInputChannels)
JSON_GETTER_SETTER_REF(sendOutputChannels)
JSON_GETTER_SETTER_REF(controlValues)
DECLARE_JSON_MAP(ChannelRouterSettings);
};
// just the channel selecttions.
class ChannelSelection
{
public:
ChannelSelection() = default;
ChannelSelection(ChannelRouterSettings &settings);
ChannelSelection(const ChannelSelection &other) = default;
ChannelSelection(ChannelSelection &&other) = default;
ChannelSelection &operator=(const ChannelSelection &other) = default;
ChannelSelection &operator=(ChannelSelection &&other) = default;
~ChannelSelection() = default;
const std::vector<int64_t> &mainInputChannels() const { return mainInputChannels_; }
const std::vector<int64_t> &mainOutputChannels() const { return mainOutputChannels_; }
const std::vector<int64_t> &auxInputChannels() const { return auxInputChannels_; }
const std::vector<int64_t> &auxOutputChannels() const { return auxOutputChannels_; }
const std::vector<int64_t> &sendInputChannels() const { return sendInputChannels_; }
const std::vector<int64_t> &sendOutputChannels() const { return sendOutputChannels_; }
std::vector<int64_t> &mainInputChannels() { return mainInputChannels_; }
std::vector<int64_t> &mainOutputChannels() { return mainOutputChannels_; }
std::vector<int64_t> &auxInputChannels() { return auxInputChannels_; }
std::vector<int64_t> &auxOutputChannels() { return auxOutputChannels_; }
std::vector<int64_t> &sendInputChannels() { return sendInputChannels_; }
std::vector<int64_t> &sendOutputChannels() { return sendOutputChannels_; }
private:
void normalizeChannelSelection();
std::vector<int64_t> mainInputChannels_;
std::vector<int64_t> mainOutputChannels_;
std::vector<int64_t> auxInputChannels_;
std::vector<int64_t> auxOutputChannels_;
std::vector<int64_t> sendInputChannels_;
std::vector<int64_t> sendOutputChannels_;
};
}
+94 -108
View File
@@ -1,7 +1,7 @@
/*
* MIT License
*
* 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);
}
+1
View File
@@ -24,6 +24,7 @@
#pragma once
#include <lilv/lilv.h>
#include "AudioDriver.hpp"
#include "JackServerSettings.hpp"
+2 -2
View File
@@ -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;
};
}
-1
View File
@@ -124,5 +124,4 @@ namespace pipedal
DECLARE_JSON_MAP(JackChannelSelection);
};
} // namespace.
+42 -5
View File
@@ -320,6 +320,7 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList, ExistingEffectMap *existingEffects)
{
this->pHost = pHost;
this->pedalboardType = pedalboard.GetInstanceType();
inputVolume.SetSampleRate((float)(this->pHost->GetSampleRate()));
outputVolume.SetSampleRate((float)(this->pHost->GetSampleRate()));
@@ -329,13 +330,15 @@ void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardE
inputVolume.SetTarget(pedalboard.input_volume_db());
outputVolume.SetTarget(pedalboard.output_volume_db());
for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i)
size_t nInputs = std::max(GetNumberOfAudioInputChannels(),(size_t)1);
for (size_t i = 0; i < nInputs; ++i)
{
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
}
auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers, errorList, existingEffects);
int nOutputs = pHost->GetNumberOfOutputAudioChannels();
size_t nOutputs = GetNumberOfAudioOutputChannels();
if (nOutputs == 1)
{
this->pedalboardOutputBuffers.push_back(outputs[0]);
@@ -572,9 +575,14 @@ void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp
{
for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i)
{
int index = vuConfiguration->enabledIndexes[i];
auto& rtIndex = vuConfiguration->enabledIndexes[i];
if (rtIndex.instanceType != this->pedalboardType)
{
continue;
}
int index = rtIndex.index;
VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i];
if (index == Pedalboard::INPUT_VOLUME_ID)
if (index == Pedalboard::START_CONTROL_ID)
{
if (this->pedalboardInputBuffers.size() > 1)
{
@@ -588,7 +596,7 @@ void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp
pUpdate->AccumulateOutputs(&(this->pedalboardInputBuffers[0][0]), samples); // after input volume applied.
}
}
else if (index == Pedalboard::OUTPUT_VOLUME_ID)
else if (index == Pedalboard::END_CONTROL_ID)
{
if (this->pedalboardOutputBuffers.size() > 1)
{
@@ -1016,3 +1024,32 @@ void Lv2Pedalboard::handleTapTempo(uint8_t value, const MidiTimestamp& timestamp
mapping.lastTapTimestamp = timestamp;
}
}
size_t Lv2Pedalboard::GetNumberOfAudioInputChannels() const {
switch (pedalboardType)
{
case Pedalboard::PedalboardType::MainPedalboard:
return pHost->GetChannelSelection().mainInputChannels().size();
case Pedalboard::PedalboardType::MainInsert:
return pHost->GetChannelSelection().mainOutputChannels().size();
case Pedalboard::PedalboardType::AuxInsert:
return pHost->GetChannelSelection().auxInputChannels().size();
default:
throw std::runtime_error("Invalid argument");
}
}
size_t Lv2Pedalboard::GetNumberOfAudioOutputChannels() const {
switch (pedalboardType)
{
case Pedalboard::PedalboardType::MainPedalboard:
return pHost->GetChannelSelection().mainOutputChannels().size();
case Pedalboard::PedalboardType::MainInsert:
return pHost->GetChannelSelection().mainOutputChannels().size();
case Pedalboard::PedalboardType::AuxInsert:
return pHost->GetChannelSelection().auxOutputChannels().size();
default:
throw std::runtime_error("Invalid argument");
}
}
+4
View File
@@ -51,6 +51,7 @@ namespace pipedal
class Lv2Pedalboard
{
private:
Pedalboard::PedalboardType pedalboardType;
IHost *pHost = nullptr;
size_t currentFrameOffset = 0;
DbDezipper inputVolume;
@@ -128,6 +129,9 @@ namespace pipedal
std::vector<IEffect *> &GetEffects() { return realtimeEffects; }
std::vector<std::shared_ptr<IEffect>> &GetSharedEffectList() { return effects; }
size_t GetNumberOfAudioInputChannels() const;
size_t GetNumberOfAudioOutputChannels() const;
int GetIndexOfInstanceId(uint64_t instanceId)
{
for (int i = 0; i < this->realtimeEffects.size(); ++i)
+17 -1
View File
@@ -210,7 +210,7 @@ PedalboardItem Pedalboard::MakeSplit()
Pedalboard Pedalboard::MakeDefault()
Pedalboard Pedalboard::MakeDefault(PedalboardType instanceType)
{
// copy insanity. but it happens so rarely.
Pedalboard result;
@@ -510,6 +510,22 @@ Snapshot Pedalboard::MakeSnapshotFromCurrentSettings(const Pedalboard &previousP
}
Pedalboard::Pedalboard(PedalboardType instanceType)
{
switch (instanceType)
{
case PedalboardType::MainPedalboard:
nextInstanceId_ = 0;
break;
case PedalboardType::MainInsert:
nextInstanceId_ = MAIN_INSERT_INSTANCE_BASE;;
break;
case PedalboardType::AuxInsert:
nextInstanceId_ = AUX_INSERT_INSTANCE_BASE;;
break;
}
}
JSON_MAP_BEGIN(ControlValue)
JSON_MAP_REFERENCE(ControlValue,key)
+45 -3
View File
@@ -230,11 +230,53 @@ 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 PedalboardType {
MainPedalboard,
MainInsert,
AuxInsert
};
static constexpr int64_t MAIN_INSERT_INSTANCE_BASE = TWO_POWER_52-2*MAX_INSTANCE_ID;
static constexpr int64_t AUX_INSERT_INSTANCE_BASE = TWO_POWER_52-1*MAX_INSTANCE_ID;
static constexpr int64_t CHANNEL_ROUTER_CONTROLS_INSTANCE_ID = -4; // Reserved Instance ID for Router Controls.
static constexpr int64_t START_CONTROL_ID = -2; // synthetic PedalboardItem for input volume.
static constexpr int64_t END_CONTROL_ID = -3; // synthetic PedalboardItem for output volume.
static constexpr int64_t MAIN_INSERT_START_CONTROL_ID = MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2;
static constexpr int64_t MAIN_INSERT_END_CONTROL_ID = MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3;
static constexpr int64_t AUX_INSERT_START_CONTROL_ID = AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2;
static constexpr int64_t AUX_INSERT_END_CONTROL_ID = AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3;
static PedalboardType GetInstanceTypeFromInstanceId(int64_t instanceId) {
if (instanceId == START_CONTROL_ID) return PedalboardType::MainPedalboard;
if (instanceId == END_CONTROL_ID) return PedalboardType::MainPedalboard;
if (instanceId >= MAIN_INSERT_INSTANCE_BASE) {
return PedalboardType::MainInsert;
} else if (instanceId >= AUX_INSERT_INSTANCE_BASE) {
return PedalboardType::AuxInsert;
} else {
return PedalboardType::MainPedalboard;
}
}
PedalboardType GetInstanceType() const {
return GetInstanceTypeFromInstanceId(this->nextInstanceId_); // instanceId is irrelevant here.
}
Pedalboard(PedalboardType instanceType = PedalboardType::MainPedalboard);
// deep copy, breaking shared pointers.
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 +308,7 @@ public:
PedalboardItem MakeSplit();
static Pedalboard MakeDefault();
static Pedalboard MakeDefault(Pedalboard::PedalboardType instanceType = Pedalboard::PedalboardType::MainPedalboard);
};
#undef GETTER_SETTER_REF
+22 -14
View File
@@ -34,6 +34,7 @@
#include <chrono>
#include <thread>
#include <sched.h>
#include "ChannelRouterSettings.hpp"
using namespace pipedal;
@@ -169,19 +170,26 @@ public:
delete[] inputBuffers;
delete[] outputBuffers;
}
std::vector<std::string> SelectChannels(const std::vector<std::string> &available, const std::vector<int> &selection)
std::vector<int64_t> SelectChannels(const std::vector<std::string> &available, const std::vector<int> &selection)
{
std::vector<int64_t> result;
if (selection.size() == 0)
return available;
{
result.resize(available.size());
for (size_t i = 0; i < result.size(); ++i)
{
result[i] = (int64_t)i;
}
return result;
}
std::vector<std::string> result;
for (int sel : selection)
{
if (sel < 0 || sel >= available.size())
{
throw PiPedalArgumentException(SS("Invalid channel: " + sel));
}
result.push_back(available[sel]);
result.push_back(sel);
}
return result;
}
@@ -199,22 +207,22 @@ public:
auto &availableInputs = jackConfiguration.inputAudioPorts();
auto &availableOutputs = jackConfiguration.outputAudioPorts();
std::vector<std::string> inputAudioPorts, outputAudioPorts;
std::vector<int64_t> inputAudioPorts, outputAudioPorts;
inputAudioPorts = SelectChannels(availableInputs, this->inputChannels);
outputAudioPorts = SelectChannels(availableOutputs, this->outputChannels);
JackChannelSelection channelSelection(
inputAudioPorts, outputAudioPorts,
std::vector<AlsaMidiDeviceInfo>());
ChannelSelection channelSelection;
channelSelection.mainInputChannels() = inputAudioPorts;
channelSelection.mainOutputChannels() = outputAudioPorts;
audioDriver = CreateAlsaDriver(this);
latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings, channelSelection);
inputBuffers = new float *[channelSelection.GetInputAudioPorts().size()];
outputBuffers = new float *[channelSelection.GetOutputAudioPorts().size()];
inputBuffers = new float *[channelSelection.mainInputChannels().size()];
outputBuffers = new float *[channelSelection.mainOutputChannels().size()];
audioDriver->Activate();
@@ -364,16 +372,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)
+32
View File
@@ -0,0 +1,32 @@
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#ifndef PIPEDAL_NON_INLINE
#if defined(__GNUC__) || defined(__clang__)
#define PIPEDAL_NON_INLINE __attribute__((noinline))
#elif defined(_MSC_VER)
#define PIPEDAL_NON_INLINE __declspec(noinline)
#else
#define PIPEDAL_NON_INLINE
#endif
#endif
+39 -27
View File
@@ -82,7 +82,7 @@ PiPedalModel::PiPedalModel()
this->updater = Updater::Create();
this->updater->Start();
this->currentUpdateStatus = updater->GetCurrentStatus();
this->pedalboard = Pedalboard::MakeDefault();
this->pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard);
#if JACK_HOST
this->jackServerSettings = this->storage.GetJackServerSettings(); // to get onboarding flag.
this->jackServerSettings.ReadJackDaemonConfiguration();
@@ -228,7 +228,14 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration)
this->jackConfiguration = this->jackConfiguration.JackInitialize();
#else
this->jackServerSettings = storage.GetJackServerSettings();
this->jackConfiguration.AlsaInitialize(jackServerSettings);
#endif
this->channelRouterSettings = storage.GetChannelRouterSettings();
pluginHost.OnConfigurationChanged(
jackConfiguration,
storage.GetChannelSelection());
}
@@ -390,7 +397,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::PedalboardType::MainPedalboard);
}
else
{
@@ -1516,17 +1523,13 @@ 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);
FireChannelSelectionChanged(-1);
FireChannelRouterSettingsChanged(-1);
LoadCurrentPedalboard();
this->UpdateRealtimeVuSubscriptions();
@@ -1622,33 +1625,18 @@ 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)
void PiPedalModel::FireChannelRouterSettingsChanged(int64_t clientId)
{
std::lock_guard<std::recursive_mutex> guard{mutex};
{
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
JackChannelSelection channelSelection = storage.GetJackChannelSelection(this->jackConfiguration);
ChannelRouterSettings::ptr channelRouterSettings = this->channelRouterSettings;
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
for (auto &subscriber : t)
{
subscriber->OnChannelSelectionChanged(clientId, channelSelection);
subscriber->OnChannelRouterSettingsChanged(clientId, *channelRouterSettings);
}
}
}
@@ -1765,7 +1753,7 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions()
for (int i = 0; i < activeVuSubscriptions.size(); ++i)
{
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);
}
@@ -3408,6 +3396,30 @@ int64_t PiPedalModel::CopyPresetsToBank(int64_t bankInstanceId, const std::vecto
}
ChannelRouterSettings::ptr PiPedalModel::GetChannelRouterSettings()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
return this->channelRouterSettings;
}
void PiPedalModel::SetChannelRouterSettings(int64_t clientId,ChannelRouterSettings::ptr &settings)
{
{
std::lock_guard<std::recursive_mutex> lock(mutex);
this->channelRouterSettings = settings;
this->storage.SetChannelRouterSettings(settings);
this->pluginHost.OnConfigurationChanged(jackConfiguration, *settings);
CancelAudioRetry();
}
RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications..
this->FireChannelRouterSettingsChanged(clientId);
}
std::string PiPedalModel::Tone3000ThumbnailDirectory()
{
return "/var/pipedal/tone3000_thumbnails";
+8 -3
View File
@@ -40,6 +40,7 @@
#include "Promise.hpp"
#include "AtomConverter.hpp"
#include "FileEntry.hpp"
#include "ChannelRouterSettings.hpp"
#include <unordered_map>
#include "Tone3000Downloader.hpp"
@@ -73,7 +74,7 @@ namespace pipedal
virtual void OnSnapshotModified(int64_t selectedSnapshot, bool modified) = 0;
virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) = 0;
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0;
virtual void OnChannelRouterSettingsChanged(int64_t clientId, const ChannelRouterSettings &channelRouterSettings) = 0;
virtual void OnVuMeterUpdate(const std::vector<VuUpdate> &updates) = 0;
virtual void OnBankIndexChanged(const BankIndex &bankIndex) = 0;
virtual void OnJackServerSettingsChanged(const JackServerSettings &jackServerSettings) = 0;
@@ -188,6 +189,8 @@ namespace pipedal
AtomConverter atomConverter; // must be AFTER pluginHost!
Pedalboard pedalboard;
ChannelRouterSettings::ptr channelRouterSettings;
bool previousPedalboardLoaded = false;
Pedalboard previousPedalboard;
Storage storage;
@@ -215,7 +218,7 @@ namespace pipedal
void FirePresetChanged(bool changed);
void FirePluginPresetsChanged(const std::string &pluginUri);
void FirePedalboardChanged(int64_t clientId, bool reloadAudioThread = true);
void FireChannelSelectionChanged(int64_t clientId);
void FireChannelRouterSettingsChanged(int64_t clientId);
void FireBanksChanged(int64_t clientId);
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
void FireLv2StateChanged(int64_t instanceId, const Lv2PluginState &lv2State);
@@ -424,7 +427,6 @@ namespace pipedal
JackConfiguration GetJackConfiguration();
void SetJackChannelSelection(int64_t clientId, const JackChannelSelection &channelSelection);
JackChannelSelection GetJackChannelSelection();
void SetAlsaSequencerConfiguration(const AlsaSequencerConfiguration &alsaSequencerConfiguration);
@@ -521,6 +523,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.
+25 -18
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022-2023 Robin Davies
// Copyright (c) 2026 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
@@ -594,17 +594,24 @@ JSON_MAP_REFERENCE(SnapshotModifiedBody, snapshotIndex)
JSON_MAP_REFERENCE(SnapshotModifiedBody, modified)
JSON_MAP_END()
class ChannelSelectionChangedBody
class ChannelRouterSettingsChangedBody
{
public:
int64_t clientId_ = -1;
JackChannelSelection *jackChannelSelection_ = nullptr;
ChannelRouterSettingsChangedBody(
int64_t clientId,
const ChannelRouterSettings &channelRouterSettings
): clientId_(clientId), channelRouterSettings_(channelRouterSettings)
{
DECLARE_JSON_MAP(ChannelSelectionChangedBody);
}
int64_t clientId_ = -1;
ChannelRouterSettings channelRouterSettings_;
DECLARE_JSON_MAP(ChannelRouterSettingsChangedBody);
};
JSON_MAP_BEGIN(ChannelSelectionChangedBody)
JSON_MAP_REFERENCE(ChannelSelectionChangedBody, clientId)
JSON_MAP_REFERENCE(ChannelSelectionChangedBody, jackChannelSelection)
JSON_MAP_BEGIN(ChannelRouterSettingsChangedBody)
JSON_MAP_REFERENCE(ChannelRouterSettingsChangedBody, clientId)
JSON_MAP_REFERENCE(ChannelRouterSettingsChangedBody, channelRouterSettings)
JSON_MAP_END()
class PresetsChangedBody
@@ -1481,12 +1488,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;
@@ -1902,6 +1903,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 if (message == "downloadModelsFromTone3000")
{
@@ -2100,11 +2109,9 @@ private:
Send("onShowStatusMonitorChanged", show);
}
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection)
virtual void OnChannelRouterSettingsChanged(int64_t clientId, const ChannelRouterSettings &channelRouterSettings)
{
ChannelSelectionChangedBody body;
body.clientId_ = clientId;
body.jackChannelSelection_ = const_cast<JackChannelSelection *>(&channelSelection);
ChannelRouterSettingsChangedBody body(clientId, channelRouterSettings);
Send("onChannelSelectionChanged", body);
}
+4 -3
View File
@@ -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
View File
@@ -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);
+19 -1
View File
@@ -96,6 +96,24 @@ namespace pipedal
};
struct RealtimePedalboardItemIndex {
RealtimePedalboardItemIndex()
: instanceType(Pedalboard::PedalboardType::MainPedalboard), index(-1)
{
}
RealtimePedalboardItemIndex(
Pedalboard::PedalboardType instanceType,
int64_t index)
: instanceType(instanceType), index(index)
{
}
RealtimePedalboardItemIndex(const RealtimePedalboardItemIndex& other) = default;
RealtimePedalboardItemIndex& operator=(const RealtimePedalboardItemIndex& other) = default;
Pedalboard::PedalboardType instanceType = Pedalboard::PedalboardType::MainPedalboard;
int64_t index = -1;
};
struct RealtimeMidiEventRequest
{
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;
+1 -2
View File
@@ -19,13 +19,12 @@
#pragma once
#include "lilv/lilv.h"
#include "lv2/state/state.h"
#include <cstddef>
#include "json_variant.hpp"
#include "MapFeature.hpp"
#include "IHost.hpp"
#include "lilv/lilv.h"
#include <lilv/lilv.h>
namespace pipedal
{
+187 -49
View File
@@ -197,11 +197,11 @@ void Storage::SetDataRoot(const std::filesystem::path &path)
this->dataRoot__audio_uploads = dataRoot / "audio_uploads";
}
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;
}
@@ -250,7 +250,6 @@ static void removeFileNoThrow(const std::filesystem::path &path)
}
}
void Storage::UpgradeFactoryPresets()
{
auto presetsDirectory = this->GetPresetsDirectory();
@@ -378,7 +377,7 @@ void Storage::Initialize()
LoadCurrentBank();
try
{
LoadChannelSelection();
LoadJackChannelSelection();
}
catch (const std::exception &)
{
@@ -387,8 +386,10 @@ void Storage::Initialize()
LoadWifiConfigSettings();
LoadWifiDirectConfigSettings();
this->channelRouterSettings = LoadChannelRouterSettings();
LoadUserSettings();
UpgradeFactoryPresets();
UpgradeChannelRouterSettings();
}
void Storage::LoadBank(int64_t instanceId)
@@ -471,7 +472,7 @@ void Storage::LoadBankIndex()
if (bankIndex.entries().size() == 0)
{
currentBank.clear();
Pedalboard defaultPedalboard = Pedalboard::MakeDefault();
Pedalboard defaultPedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard);
int64_t instanceId = currentBank.addPreset(defaultPedalboard);
currentBank.selectedPreset(instanceId);
@@ -683,40 +684,46 @@ int64_t Storage::SaveCurrentPresetAs(const Pedalboard &pedalboard, int64_t bankI
}
}
static std::string stripNumericSuffix(const std::string &name) {
static std::string stripNumericSuffix(const std::string &name)
{
// remove (digit*) from the end of the string.
// 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))
{
@@ -725,9 +732,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;
@@ -735,23 +744,27 @@ static std::string makeUniqueName(const std::string &name, const std::set<std::s
}
int64_t Storage::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
{
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);
@@ -763,31 +776,35 @@ int64_t Storage::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector
}
int64_t Storage::CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
{
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;
}
@@ -797,17 +814,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.
@@ -881,7 +896,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();
@@ -973,7 +989,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;
}
@@ -993,7 +1009,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;
@@ -1115,7 +1131,7 @@ AlsaSequencerConfiguration Storage::GetAlsaSequencerConfiguration() const
return this->alsaSequencerConfiguration;
}
void Storage::LoadChannelSelection()
void Storage::LoadJackChannelSelection()
{
auto fileName = this->GetChannelSelectionFileName();
if (std::filesystem::exists(fileName))
@@ -2071,21 +2087,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;
}
@@ -2983,11 +2999,13 @@ const PluginPresetIndex &Storage::GetPluginPresetIndex()
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;
@@ -3010,6 +3028,126 @@ 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);
this->channelSelection = ChannelSelection(*result);
return result;
} catch (const std::exception &e) {
Lv2Log::error("Failed to load Channel Router settings: %s", e.what());
return std::make_shared<ChannelRouterSettings>();
}
}
static int64_t GetUpgradedPortIndex(const std::string &portName)
{
auto nPos = portName.find_last_of('_');
if (nPos != std::string::npos)
{
std::string channelStr = portName.substr(nPos + 1);
try
{
int64_t channelIndex = std::stoll(channelStr);
return channelIndex;
} catch (const std::exception &) {
return -1;
}
}
return -1;
}
void Storage::UpgradeChannelRouterSettings()
{
if (channelRouterSettings == nullptr)
{
channelRouterSettings = std::make_shared<ChannelRouterSettings>();
if (jackChannelSelection.isValid())
{
channelRouterSettings->configured(true);
channelRouterSettings->mainInputChannels().resize(2);
const std::vector<std::string>& oldInputs = jackChannelSelection.GetInputAudioPorts();
std::vector<int64_t>& newInputs = channelRouterSettings->mainInputChannels();
if (oldInputs.size() ==1)
{
int64_t leftIndex = GetUpgradedPortIndex(oldInputs[0]);
newInputs.resize(2);
newInputs[0] = leftIndex;
newInputs[1] = leftIndex;
} else if (oldInputs.size() == 2) {
newInputs.resize(2);
for (size_t i = 0; i < std::min(oldInputs.size(), newInputs.size()); ++i)
{
int64_t portIndex = GetUpgradedPortIndex(oldInputs[i]);
newInputs[i] = portIndex;
}
}
const std::vector<std::string>& oldOutputs = jackChannelSelection.GetOutputAudioPorts();
auto &newMainOutputs = channelRouterSettings->mainOutputChannels();
if (oldOutputs.size() == 1) {
int64_t leftIndex = GetUpgradedPortIndex(oldOutputs[0]);
newMainOutputs.resize(2);
newMainOutputs[0] = leftIndex;
newMainOutputs[1] = leftIndex;
} else if (oldOutputs.size() == 2) {
newMainOutputs.resize(2);
for (size_t i = 0; i < std::min(oldOutputs.size(), newMainOutputs.size()); ++i)
{
int64_t portIndex = GetUpgradedPortIndex(oldOutputs[i]);
newMainOutputs[i] = portIndex;
}
}
auto &newAuxInputs = channelRouterSettings->auxInputChannels();
newAuxInputs.resize(2);
newAuxInputs[0] = -1;
newAuxInputs[1] = -1;
auto &newAuxOutputs = channelRouterSettings->auxOutputChannels();
newAuxOutputs.resize(2);
newAuxOutputs[0] = -1;
newAuxOutputs[1] = -1;
SaveChannelRouterSettings(channelRouterSettings);
}
channelSelection = ChannelSelection(*channelRouterSettings);
}
}
const ChannelSelection& Storage::GetChannelSelection() const {
return channelSelection;
}
JSON_MAP_BEGIN(UserSettings)
JSON_MAP_REFERENCE(UserSettings, governor)
+19 -3
View File
@@ -32,6 +32,8 @@
#include <map>
#include "FilePropertyDirectoryTree.hpp"
#include "AlsaSequencer.hpp"
#include "ChannelRouterSettings.hpp"
namespace pipedal {
@@ -96,7 +98,7 @@ private:
void LoadCurrentBank();
void SaveCurrentBank();
void LoadChannelSelection();
void LoadJackChannelSelection();
void SaveChannelSelection();
void LoadAlsaSequencerConfiguration();
@@ -115,6 +117,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();
@@ -122,8 +133,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;
@@ -204,6 +215,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);