This commit is contained in:
Robin E.R. Davies
2026-01-27 23:45:13 -05:00
parent 96cb9967aa
commit 6c441ecfee
28 changed files with 480 additions and 262 deletions
-1
View File
@@ -13,7 +13,6 @@
"/usr/include/x86_64-linux-gnu", "/usr/include/x86_64-linux-gnu",
"/usr/lib" "/usr/lib"
], ],
"compilerPath": "/usr/bin/gcc-12",
"cStandard": "c17", "cStandard": "c17",
"cppStandard": "c++20", "cppStandard": "c++20",
"intelliSenseMode": "linux-gcc-arm64", "intelliSenseMode": "linux-gcc-arm64",
+17 -2
View File
@@ -11,6 +11,17 @@ set (DISPLAY_VERSION "PiPedal v1.5.99-Beta")
set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE}) set (PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
set (CMAKE_INSTALL_PREFIX "/usr/") set (CMAKE_INSTALL_PREFIX "/usr/")
set (LIBZIP_DO_INSTALL OFF CACHE BOOL "Disable libzip install" FORCE)
set (SQLITECPP_INSTALL OFF CACHE BOOL "Disable SQLiteCpp install" FORCE)
set(SUBMODULE_INSTALL OFF CACHE BOOL "" FORCE)
# Remove SQLiteCPP install files.
set(CPACK_SOURCE_IGNORE_FILES
"*.a;sqlite3.h;$(CPACK_SOURCE_IGNORE_FILES)"
)
set (PIPEDAL_EXCLUDE_TESTS false) set (PIPEDAL_EXCLUDE_TESTS false)
include(CTest) include(CTest)
enable_testing() enable_testing()
@@ -96,9 +107,9 @@ set(CPACK_DEBIAN_PACKAGE_SECTION sound)
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE) set(CPACK_DEBIAN_PACKAGE_CONTROL_STRICT_PERMISSION TRUE)
# Accept either libzip4 or libzip5 to satisfy libzip 4 linkage (because Ubuntu nonsense)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libicu-dev ,lv2-dev, ffmpeg, authbind, curl, gpg, alsa-base| pipewire, alsa-utils")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libicu-dev ,lv2-dev,iw, ffmpeg, authbind, curl, gpg, alsa-base| pipewire, alsa-utils")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE}) set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE ${DEBIAN_ARCHITECTURE})
set(CPACK_PACKAGING_INSTALL_PREFIX /usr) set(CPACK_PACKAGING_INSTALL_PREFIX /usr)
set(CPACK_PROJECT_NAME ${PROJECT_NAME}) set(CPACK_PROJECT_NAME ${PROJECT_NAME})
@@ -118,4 +129,8 @@ set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
) )
include(CPack) include(CPack)
message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
-5
View File
@@ -354,11 +354,6 @@ namespace pipedal
std::vector<OutputBufferMix> outputBufferMixes; std::vector<OutputBufferMix> outputBufferMixes;
std::vector<float *> deviceCaptureBuffers;
std::vector<float *> devicePlaybackBuffers;
std::vector<uint8_t> rawCaptureBuffer; std::vector<uint8_t> rawCaptureBuffer;
std::vector<uint8_t> rawPlaybackBuffer; std::vector<uint8_t> rawPlaybackBuffer;
+2 -1
View File
@@ -1,7 +1,7 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2025 Robin E. R. Davies * Copyright (c) 2026 Robin E. R. Davies
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of * Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in * this software and associated documentation files (the "Software"), to deal in
@@ -24,6 +24,7 @@
#pragma once #pragma once
#include <lilv/lilv.h>
#include "AudioDriver.hpp" #include "AudioDriver.hpp"
#include "JackServerSettings.hpp" #include "JackServerSettings.hpp"
+109 -94
View File
@@ -1,18 +1,18 @@
/* /*
* MIT License * MIT License
* *
* Copyright (c) 2022 Robin E. R. Davies * Copyright (c) 2022 Robin E. R. Davies
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of * Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in * this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to * the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do * of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions: * so, subject to the following conditions:
* *
* The above copyright notice and this permission notice shall be included in all * The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software. * copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -27,28 +27,34 @@
#include <cmath> #include <cmath>
#include <mutex> #include <mutex>
#include <iostream> #include <iostream>
#include <algorithm>
#include "AlsaDriver.hpp" #include "AlsaDriver.hpp"
#include "JackDriver.hpp" #include "ChannelRouterSettings.hpp"
using namespace pipedal; using namespace pipedal;
using namespace std; using namespace std;
class AlsaTester : private AudioDriverHost
class AlsaTester: private AudioDriverHost { {
public: public:
enum class TestType { Oscillator, LatencyMonitor, NullTest}; enum class TestType
{
Oscillator,
LatencyMonitor,
NullTest
};
private: private:
AudioDriver *audioDriver = nullptr; AudioDriver *audioDriver = nullptr;
TestType testType; TestType testType;
public: public:
AlsaTester(TestType testType) AlsaTester(TestType testType)
: testType(testType) : testType(testType)
{ {
// audioDriver = CreateAlsaDriver(this); // audioDriver = CreateAlsaDriver(this);
// audioDriver = CreateJackDriver(this); // audioDriver = CreateJackDriver(this);
} }
~AlsaTester() ~AlsaTester()
{ {
@@ -57,47 +63,47 @@ public:
delete[] outputBuffers; delete[] outputBuffers;
} }
bool useJack = false; void initializeChannelSelection(JackConfiguration &jackConfiguration, ChannelSelection &channelSelection)
{
size_t nInputs = std::max(jackConfiguration.inputAudioPorts().size(),size_t(2));
size_t nOutputs = std::max(jackConfiguration.outputAudioPorts().size(),size_t(2));
auto &inputSelections = channelSelection.mainInputChannels();
auto &outputSelections = channelSelection.mainOutputChannels();
inputSelections.resize(nInputs);
for (size_t i = 0; i < nInputs; ++i)
{
inputSelections[i] = i;
}
outputSelections.resize(nOutputs);
for (size_t i = 0; i < nOutputs; ++i)
{
outputSelections[i] = i;
}
}
void Test() void Test()
{ {
AlsaFormatEncodeDecodeTest(this); AlsaFormatEncodeDecodeTest(this);
JackServerSettings serverSettings("hw:M2","hw:M2",48000,32,3); JackServerSettings serverSettings("hw:M2", "hw:M2", 48000, 32, 3);
JackConfiguration jackConfiguration; JackConfiguration jackConfiguration;
if (useJack) jackConfiguration.AlsaInitialize(serverSettings);
{
jackConfiguration.JackInitialize();
} else {
jackConfiguration.AlsaInitialize(serverSettings);
}
JackChannelSelection channelSelection( ChannelSelection channelSelection;
jackConfiguration.inputAudioPorts(),
jackConfiguration.outputAudioPorts(),
jackConfiguration.inputMidiDevices());
#if JACK_HOST
if (useJack)
{
audioDriver = CreateJackDriver(this);
} else {
audioDriver = CreateAlsaDriver(this);
}
#else
audioDriver = CreateAlsaDriver(this); audioDriver = CreateAlsaDriver(this);
#endif
initializeChannelSelection(jackConfiguration, channelSelection);
oscillator.Init(440,jackConfiguration.sampleRate()); oscillator.Init(440, jackConfiguration.sampleRate());
latencyMonitor.Init(jackConfiguration.sampleRate()); latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings,channelSelection); audioDriver->Open(serverSettings, channelSelection);
inputBuffers = new float*[channelSelection.GetInputAudioPorts().size()]; inputBuffers = new float *[channelSelection.mainInputChannels().size()];
outputBuffers = new float*[channelSelection.GetOutputAudioPorts().size()]; outputBuffers = new float *[channelSelection.mainOutputChannels().size()];
audioDriver->Activate(); audioDriver->Activate();
@@ -107,7 +113,7 @@ public:
if (testType == TestType::LatencyMonitor) if (testType == TestType::LatencyMonitor)
{ {
auto latency = this->latencyMonitor.GetLatency(); auto latency = this->latencyMonitor.GetLatency();
double ms = 1000.0*latency/jackConfiguration.sampleRate(); double ms = 1000.0 * latency / jackConfiguration.sampleRate();
cout << "Latency: " << latency << " samples " << ms << "ms" << " xruns: " << GetXruns() << " Cpu: " << audioDriver->CpuUse() << "%" << endl; cout << "Latency: " << latency << " samples " << ms << "ms" << " xruns: " << GetXruns() << " Cpu: " << audioDriver->CpuUse() << "%" << endl;
} }
@@ -115,37 +121,40 @@ public:
audioDriver->Deactivate(); audioDriver->Deactivate();
audioDriver->Close(); audioDriver->Close();
} }
float**inputBuffers = nullptr; float **inputBuffers = nullptr;
float**outputBuffers = nullptr; float **outputBuffers = nullptr;
class Oscillator { class Oscillator
{
private: private:
double dx = 0; double dx = 0;
double x = 0; double x = 0;
double dx2 = 0; double dx2 = 0;
double x2 = 0; double x2 = 0;
public:
public:
void Init(float frequency, size_t sampleRate) void Init(float frequency, size_t sampleRate)
{ {
dx = frequency*3.141592736*2/sampleRate; dx = frequency * 3.141592736 * 2 / sampleRate;
dx2 = 0.5*3.141592736*2/sampleRate; dx2 = 0.5 * 3.141592736 * 2 / sampleRate;
} }
float Next() { float Next()
{
float result = (float)std::cos(x); float result = (float)std::cos(x);
float env = (float)std::cos(x2); float env = (float)std::cos(x2);
x += dx; x += dx;
x2 += dx2; x2 += dx2;
return result*env; return result * env;
} }
}; };
class LatencyMonitor { class LatencyMonitor
enum class State { {
enum class State
{
Idle, Idle,
Waiting, Waiting,
}; };
@@ -156,67 +165,74 @@ public:
size_t current_latency = 0; size_t current_latency = 0;
size_t latency = 0; size_t latency = 0;
std::mutex sync; std::mutex sync;
public: public:
void Init(uint64_t sampleRate) void Init(uint64_t sampleRate)
{ {
idle_samples = (uint64_t)sampleRate*2; idle_samples = (uint64_t)sampleRate * 2;
waiting_samples = (uint64_t)sampleRate*2; waiting_samples = (uint64_t)sampleRate * 2;
state = State::Idle; state = State::Idle;
t = idle_samples; t = idle_samples;
latency = 0; latency = 0;
} }
size_t GetLatency() { size_t GetLatency()
std::lock_guard lock { sync}; {
std::lock_guard lock{sync};
return latency; return latency;
} }
float Next(float input) float Next(float input)
{ {
switch(state) switch (state)
{ {
default: default:
case State::Idle: case State::Idle:
{
if (t-- == 0)
{ {
if (t-- == 0) { state = State::Waiting;
state = State::Waiting; current_latency = 0;
current_latency = 0;
}
return 0.01;
} }
break; return 0.01;
case State::Waiting: }
break;
case State::Waiting:
{
if (std::abs(input) > 0.1 || current_latency > 500)
{ {
if (std::abs(input) > 0.1 || current_latency > 500) { {
{ std::lock_guard lock{sync};
std::lock_guard lock { sync}; latency = current_latency;
latency = current_latency;
}
state = State::Idle;
t = idle_samples;
} else {
++current_latency;
} }
return current_latency < 100 ? 0.25 : 0.0; state = State::Idle;
t = idle_samples;
} }
break; else
{
++current_latency;
}
return current_latency < 100 ? 0.25 : 0.0;
}
break;
} }
} }
}; };
Oscillator oscillator; Oscillator oscillator;
LatencyMonitor latencyMonitor; LatencyMonitor latencyMonitor;
virtual void OnAudioTerminated() { virtual void OnAudioTerminated()
{
} }
virtual void OnAlsaDriverStopped() { virtual void OnAlsaDriverStopped()
{
} }
virtual void OnProcess(size_t nFrames) { virtual void OnProcess(size_t nFrames)
if (testType == TestType::NullTest) return; {
if (testType == TestType::NullTest)
return;
size_t inputs = audioDriver->MainInputBufferCount(); size_t inputs = audioDriver->MainInputBufferCount();
size_t outputs = audioDriver->MainOutputBufferCount(); size_t outputs = audioDriver->MainOutputBufferCount();
@@ -233,14 +249,15 @@ public:
{ {
for (size_t i = 0; i < nFrames; ++i) for (size_t i = 0; i < nFrames; ++i)
{ {
float v = oscillator.Next()*0.25f; float v = oscillator.Next() * 0.25f;
for (size_t c = 0; c < outputs; ++c) for (size_t c = 0; c < outputs; ++c)
{ {
outputBuffers[c][i] = v; outputBuffers[c][i] = v;
} }
} }
} }
else { else
{
for (size_t i = 0; i < nFrames; ++i) for (size_t i = 0; i < nFrames; ++i)
{ {
float v = latencyMonitor.Next(inputBuffers[0][i]); float v = latencyMonitor.Next(inputBuffers[0][i]);
@@ -249,36 +266,34 @@ public:
outputBuffers[c][i] = v; outputBuffers[c][i] = v;
} }
} }
} }
} }
std::mutex sync; std::mutex sync;
uint64_t xruns; uint64_t xruns;
uint64_t GetXruns() { uint64_t GetXruns()
lock_guard lock { sync}; {
lock_guard lock{sync};
return xruns; return xruns;
} }
virtual void OnUnderrun() { virtual void OnUnderrun()
lock_guard lock { sync }; {
lock_guard lock{sync};
++xruns; ++xruns;
} }
}; };
TEST_CASE("alsa_test", "[alsa_test]")
{
TEST_CASE( "alsa_test", "[alsa_test]" ) {
AlsaTester alsaDriver(AlsaTester::TestType::Oscillator); AlsaTester alsaDriver(AlsaTester::TestType::Oscillator);
alsaDriver.Test(); alsaDriver.Test();
} }
TEST_CASE("alsa_midi_test", "[alsa_midi_test]")
TEST_CASE( "alsa_midi_test", "[alsa_midi_test]" ) { {
AlsaTester alsaDriver(AlsaTester::TestType::Oscillator); AlsaTester alsaDriver(AlsaTester::TestType::Oscillator);
MidiDecoderTest(); MidiDecoderTest();
} }
+4 -4
View File
@@ -1900,11 +1900,11 @@ public:
PIPEDAL_NON_INLINE RealtimePedalboardItemIndex GetRealtimeItemIndex(int64_t instanceId) PIPEDAL_NON_INLINE RealtimePedalboardItemIndex GetRealtimeItemIndex(int64_t instanceId)
{ {
Pedalboard::InstanceType instanceType = Pedalboard::GetInstanceTypeFromInstanceId(instanceId); Pedalboard::PedalboardType instanceType = Pedalboard::GetInstanceTypeFromInstanceId(instanceId);
int64_t index = -1; int64_t index = -1;
switch (instanceType) switch (instanceType)
{ {
case Pedalboard::InstanceType::MainPedalboard: case Pedalboard::PedalboardType::MainPedalboard:
if (this->currentPedalboard) { if (this->currentPedalboard) {
if (instanceId == Pedalboard::START_CONTROL_ID) { if (instanceId == Pedalboard::START_CONTROL_ID) {
index = Pedalboard::START_CONTROL_ID; index = Pedalboard::START_CONTROL_ID;
@@ -1915,7 +1915,7 @@ public:
} }
} }
break; break;
case Pedalboard::InstanceType::MainInsert: case Pedalboard::PedalboardType::MainInsert:
if (this->currentMainInsertPedalboard) { if (this->currentMainInsertPedalboard) {
if (instanceId == Pedalboard::MAIN_INSERT_START_CONTROL_ID) { if (instanceId == Pedalboard::MAIN_INSERT_START_CONTROL_ID) {
index = Pedalboard::MAIN_INSERT_START_CONTROL_ID; index = Pedalboard::MAIN_INSERT_START_CONTROL_ID;
@@ -1926,7 +1926,7 @@ public:
} }
} }
break; break;
case Pedalboard::InstanceType::AuxInsert: case Pedalboard::PedalboardType::AuxInsert:
if (this->currentAuxInsertPedalboard) { if (this->currentAuxInsertPedalboard) {
if (instanceId == Pedalboard::AUX_INSERT_START_CONTROL_ID) { if (instanceId == Pedalboard::AUX_INSERT_START_CONTROL_ID) {
index = Pedalboard::START_CONTROL_ID; index = Pedalboard::START_CONTROL_ID;
+1 -1
View File
@@ -240,7 +240,7 @@ namespace pipedal
{ {
// zero length? We can never have a zero-length bank. // zero length? We can never have a zero-length bank.
// Add a default preset and make it the selected preset. // Add a default preset and make it the selected preset.
Pedalboard pedalboard = Pedalboard::MakeDefault(Pedalboard::InstanceType::MainPedalboard); Pedalboard pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard);
this->addPreset(pedalboard); this->addPreset(pedalboard);
newSelection = presets_[0]->instanceId(); newSelection = presets_[0]->instanceId();
} }
+9 -3
View File
@@ -352,8 +352,9 @@ target_compile_definitions(libpipedald PUBLIC "_REENTRANT")
target_include_directories(libpipedald PUBLIC ${PIPEDAL_INCLUDES}) target_include_directories(libpipedald PUBLIC ${PIPEDAL_INCLUDES})
target_link_libraries(libpipedald target_link_libraries(libpipedald
PUBLIC PUBLIC
PiPedalCommon PiPedalCommon
PRIVATE
SQLiteCpp SQLiteCpp
${PipeWire_LIBRARIES} ${PipeWire_LIBRARIES}
) )
@@ -762,6 +763,7 @@ add_executable(pipedal_latency_test
target_link_libraries(pipedal_latency_test PRIVATE pthread asound PiPedalCommon) target_link_libraries(pipedal_latency_test PRIVATE pthread asound PiPedalCommon)
target_include_directories(pipedal_latency_test PRIVATE ${PIPEDAL_INCLUDES})
add_executable(pipedal_alsa_info add_executable(pipedal_alsa_info
alsaCheck.cpp alsaCheck.hpp alsaCheck.cpp alsaCheck.hpp
@@ -859,10 +861,14 @@ add_custom_target (
) )
install (TARGETS pipedalconfig pipedal_kconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin install (TARGETS pipedalconfig pipedal_kconfig pipedal_latency_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin
EXPORT pipedalTargets) EXPORT pipedalTargets
)
install (TARGETS pipedald pipedaladmind pipedal_update DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin install (TARGETS pipedald pipedaladmind pipedal_update DESTINATION ${CMAKE_INSTALL_PREFIX}/sbin
EXPORT pipedalSbinTargets) EXPORT pipedalSbinTargets
)
+2 -2
View File
@@ -129,8 +129,8 @@ void ChannelSelection::normalizeChannelSelection() {
ChannelRouterSettings::ChannelRouterSettings() ChannelRouterSettings::ChannelRouterSettings()
: mainInserts_(Pedalboard::InstanceType::MainInsert) : mainInserts_(Pedalboard::PedalboardType::MainInsert)
, auxInserts_(Pedalboard::InstanceType::AuxInsert) , auxInserts_(Pedalboard::PedalboardType::AuxInsert)
{ {
} }
+23 -17
View File
@@ -49,7 +49,7 @@ namespace pipedal
std::vector<ControlValue> controlValues_; std::vector<ControlValue> controlValues_;
public: public:
using self = ChannelRouterSettings; using self = ChannelRouterSettings;
using ptr = std::shared_ptr<self>; using ptr = std::shared_ptr<self>;
@@ -57,7 +57,7 @@ namespace pipedal
uint64_t numberOfAudioInputChannels() const; uint64_t numberOfAudioInputChannels() const;
uint64_t numberOfAudioOutputChannels() const; uint64_t numberOfAudioOutputChannels() const;
JSON_GETTER_SETTER(configured) JSON_GETTER_SETTER(configured)
JSON_GETTER_SETTER(channelRouterPresetId) JSON_GETTER_SETTER(channelRouterPresetId)
JSON_GETTER_SETTER_REF(mainInputChannels) JSON_GETTER_SETTER_REF(mainInputChannels)
@@ -70,28 +70,34 @@ namespace pipedal
JSON_GETTER_SETTER_REF(sendOutputChannels) JSON_GETTER_SETTER_REF(sendOutputChannels)
JSON_GETTER_SETTER_REF(controlValues) JSON_GETTER_SETTER_REF(controlValues)
DECLARE_JSON_MAP(ChannelRouterSettings); DECLARE_JSON_MAP(ChannelRouterSettings);
}; };
// just the channel selecttions. // just the channel selecttions.
class ChannelSelection { class ChannelSelection
{
public: public:
ChannelSelection() = default; ChannelSelection() = default;
ChannelSelection(ChannelRouterSettings&settings); ChannelSelection(ChannelRouterSettings &settings);
ChannelSelection(const ChannelSelection&other) = default; ChannelSelection(const ChannelSelection &other) = default;
ChannelSelection(ChannelSelection&&other) = default; ChannelSelection(ChannelSelection &&other) = default;
ChannelSelection& operator=(const ChannelSelection&other) = default; ChannelSelection &operator=(const ChannelSelection &other) = default;
ChannelSelection& operator=(ChannelSelection&&other) = default; ChannelSelection &operator=(ChannelSelection &&other) = default;
~ChannelSelection() = default; ~ChannelSelection() = default;
const std::vector<int64_t>&mainInputChannels() const { return mainInputChannels_; } const std::vector<int64_t> &mainInputChannels() const { return mainInputChannels_; }
const std::vector<int64_t>&mainOutputChannels() const { return mainOutputChannels_; } const std::vector<int64_t> &mainOutputChannels() const { return mainOutputChannels_; }
const std::vector<int64_t>&auxInputChannels() const { return auxInputChannels_; } const std::vector<int64_t> &auxInputChannels() const { return auxInputChannels_; }
const std::vector<int64_t>&auxOutputChannels() const { return auxOutputChannels_; } const std::vector<int64_t> &auxOutputChannels() const { return auxOutputChannels_; }
const std::vector<int64_t>&sendInputChannels() const { return sendInputChannels_; } const std::vector<int64_t> &sendInputChannels() const { return sendInputChannels_; }
const std::vector<int64_t>&sendOutputChannels() const { return sendOutputChannels_; } 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: private:
void normalizeChannelSelection(); void normalizeChannelSelection();
+1
View File
@@ -24,6 +24,7 @@
#pragma once #pragma once
#include <lilv/lilv.h>
#include "AudioDriver.hpp" #include "AudioDriver.hpp"
#include "JackServerSettings.hpp" #include "JackServerSettings.hpp"
+40 -3
View File
@@ -320,6 +320,7 @@ std::vector<float *> Lv2Pedalboard::PrepareItems(
void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList, ExistingEffectMap *existingEffects) void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardErrorList &errorList, ExistingEffectMap *existingEffects)
{ {
this->pHost = pHost; this->pHost = pHost;
this->pedalboardType = pedalboard.GetInstanceType();
inputVolume.SetSampleRate((float)(this->pHost->GetSampleRate())); inputVolume.SetSampleRate((float)(this->pHost->GetSampleRate()));
outputVolume.SetSampleRate((float)(this->pHost->GetSampleRate())); outputVolume.SetSampleRate((float)(this->pHost->GetSampleRate()));
@@ -329,13 +330,15 @@ void Lv2Pedalboard::Prepare(IHost *pHost, Pedalboard &pedalboard, Lv2PedalboardE
inputVolume.SetTarget(pedalboard.input_volume_db()); inputVolume.SetTarget(pedalboard.input_volume_db());
outputVolume.SetTarget(pedalboard.output_volume_db()); outputVolume.SetTarget(pedalboard.output_volume_db());
for (int i = 0; i < pHost->GetNumberOfInputAudioChannels(); ++i) size_t nInputs = std::max(GetNumberOfAudioInputChannels(),(size_t)1);
for (size_t i = 0; i < nInputs; ++i)
{ {
this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize())); this->pedalboardInputBuffers.push_back(bufferPool.AllocateBuffer<float>(pHost->GetMaxAudioBufferSize()));
} }
auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers, errorList, existingEffects); auto outputs = PrepareItems(pedalboard.items(), this->pedalboardInputBuffers, errorList, existingEffects);
int nOutputs = pHost->GetNumberOfOutputAudioChannels(); size_t nOutputs = GetNumberOfAudioOutputChannels();
if (nOutputs == 1) if (nOutputs == 1)
{ {
this->pedalboardOutputBuffers.push_back(outputs[0]); this->pedalboardOutputBuffers.push_back(outputs[0]);
@@ -572,7 +575,12 @@ void Lv2Pedalboard::ComputeVus(RealtimeVuBuffers *vuConfiguration, uint32_t samp
{ {
for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i) for (size_t i = 0; i < vuConfiguration->enabledIndexes.size(); ++i)
{ {
int index = vuConfiguration->enabledIndexes[i]; auto& rtIndex = vuConfiguration->enabledIndexes[i];
if (rtIndex.instanceType != this->pedalboardType)
{
continue;
}
int index = rtIndex.index;
VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i]; VuUpdate *pUpdate = &vuConfiguration->vuUpdateWorkingData[i];
if (index == Pedalboard::START_CONTROL_ID) if (index == Pedalboard::START_CONTROL_ID)
{ {
@@ -1016,3 +1024,32 @@ void Lv2Pedalboard::handleTapTempo(uint8_t value, const MidiTimestamp& timestamp
mapping.lastTapTimestamp = timestamp; mapping.lastTapTimestamp = timestamp;
} }
} }
size_t Lv2Pedalboard::GetNumberOfAudioInputChannels() const {
switch (pedalboardType)
{
case Pedalboard::PedalboardType::MainPedalboard:
return pHost->GetChannelSelection().mainInputChannels().size();
case Pedalboard::PedalboardType::MainInsert:
return pHost->GetChannelSelection().mainOutputChannels().size();
case Pedalboard::PedalboardType::AuxInsert:
return pHost->GetChannelSelection().auxInputChannels().size();
default:
throw std::runtime_error("Invalid argument");
}
}
size_t Lv2Pedalboard::GetNumberOfAudioOutputChannels() const {
switch (pedalboardType)
{
case Pedalboard::PedalboardType::MainPedalboard:
return pHost->GetChannelSelection().mainOutputChannels().size();
case Pedalboard::PedalboardType::MainInsert:
return pHost->GetChannelSelection().mainOutputChannels().size();
case Pedalboard::PedalboardType::AuxInsert:
return pHost->GetChannelSelection().auxOutputChannels().size();
default:
throw std::runtime_error("Invalid argument");
}
}
+4
View File
@@ -51,6 +51,7 @@ namespace pipedal
class Lv2Pedalboard class Lv2Pedalboard
{ {
private: private:
Pedalboard::PedalboardType pedalboardType;
IHost *pHost = nullptr; IHost *pHost = nullptr;
size_t currentFrameOffset = 0; size_t currentFrameOffset = 0;
DbDezipper inputVolume; DbDezipper inputVolume;
@@ -128,6 +129,9 @@ namespace pipedal
std::vector<IEffect *> &GetEffects() { return realtimeEffects; } std::vector<IEffect *> &GetEffects() { return realtimeEffects; }
std::vector<std::shared_ptr<IEffect>> &GetSharedEffectList() { return effects; } std::vector<std::shared_ptr<IEffect>> &GetSharedEffectList() { return effects; }
size_t GetNumberOfAudioInputChannels() const;
size_t GetNumberOfAudioOutputChannels() const;
int GetIndexOfInstanceId(uint64_t instanceId) int GetIndexOfInstanceId(uint64_t instanceId)
{ {
for (int i = 0; i < this->realtimeEffects.size(); ++i) for (int i = 0; i < this->realtimeEffects.size(); ++i)
+5 -5
View File
@@ -210,7 +210,7 @@ PedalboardItem Pedalboard::MakeSplit()
Pedalboard Pedalboard::MakeDefault(InstanceType instanceType) Pedalboard Pedalboard::MakeDefault(PedalboardType instanceType)
{ {
// copy insanity. but it happens so rarely. // copy insanity. but it happens so rarely.
Pedalboard result; Pedalboard result;
@@ -510,17 +510,17 @@ Snapshot Pedalboard::MakeSnapshotFromCurrentSettings(const Pedalboard &previousP
} }
Pedalboard::Pedalboard(InstanceType instanceType = InstanceType::MainPedalboard) Pedalboard::Pedalboard(PedalboardType instanceType)
{ {
switch (instanceType) switch (instanceType)
{ {
case InstanceType::MainPedalboard: case PedalboardType::MainPedalboard:
nextInstanceId_ = 0; nextInstanceId_ = 0;
break; break;
case InstanceType::MainInsert: case PedalboardType::MainInsert:
nextInstanceId_ = MAIN_INSERT_INSTANCE_BASE;; nextInstanceId_ = MAIN_INSERT_INSTANCE_BASE;;
break; break;
case InstanceType::AuxInsert: case PedalboardType::AuxInsert:
nextInstanceId_ = AUX_INSERT_INSTANCE_BASE;; nextInstanceId_ = AUX_INSERT_INSTANCE_BASE;;
break; break;
} }
+11 -10
View File
@@ -230,13 +230,14 @@ class Pedalboard {
int64_t selectedPlugin_ = -1; int64_t selectedPlugin_ = -1;
static constexpr int64_t TWO_POWER_52 = 4503599627370496; // Maximum Javascript integer value. static constexpr int64_t TWO_POWER_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 TWO_POWER_48 = 281474976710656; // Number of possible instance IDs for Main Pedalboards.
static constexpr int64_t MAX_INSTANCE_ID = TWO_POWER_48; static constexpr int64_t MAX_INSTANCE_ID = TWO_POWER_48;
public: public:
enum class InstanceType { enum class PedalboardType {
MainPedalboard, MainPedalboard,
MainInsert, MainInsert,
AuxInsert AuxInsert
@@ -253,25 +254,25 @@ public:
static constexpr int64_t AUX_INSERT_START_CONTROL_ID = AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2; 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 constexpr int64_t AUX_INSERT_END_CONTROL_ID = AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3;
static InstanceType GetInstanceTypeFromInstanceId(int64_t instanceId) { static PedalboardType GetInstanceTypeFromInstanceId(int64_t instanceId) {
if (instanceId == START_CONTROL_ID) return InstanceType::MainPedalboard; if (instanceId == START_CONTROL_ID) return PedalboardType::MainPedalboard;
if (instanceId == END_CONTROL_ID) return InstanceType::MainPedalboard; if (instanceId == END_CONTROL_ID) return PedalboardType::MainPedalboard;
if (instanceId >= MAIN_INSERT_INSTANCE_BASE) { if (instanceId >= MAIN_INSERT_INSTANCE_BASE) {
return InstanceType::MainInsert; return PedalboardType::MainInsert;
} else if (instanceId >= AUX_INSERT_INSTANCE_BASE) { } else if (instanceId >= AUX_INSERT_INSTANCE_BASE) {
return InstanceType::AuxInsert; return PedalboardType::AuxInsert;
} else { } else {
return InstanceType::MainPedalboard; return PedalboardType::MainPedalboard;
} }
} }
InstanceType GetInstanceType() const { PedalboardType GetInstanceType() const {
return GetInstanceTypeFromInstanceId(this->nextInstanceId_); // instanceId is irrelevant here. return GetInstanceTypeFromInstanceId(this->nextInstanceId_); // instanceId is irrelevant here.
} }
Pedalboard(InstanceType instanceType = InstanceType::MainPedalboard); Pedalboard(PedalboardType instanceType = PedalboardType::MainPedalboard);
// deep copy, breaking shared pointers. // deep copy, breaking shared pointers.
Pedalboard DeepCopy(); Pedalboard DeepCopy();
@@ -307,7 +308,7 @@ public:
PedalboardItem MakeSplit(); PedalboardItem MakeSplit();
static Pedalboard MakeDefault(Pedalboard::InstanceType instanceType); static Pedalboard MakeDefault(Pedalboard::PedalboardType instanceType = Pedalboard::PedalboardType::MainPedalboard);
}; };
#undef GETTER_SETTER_REF #undef GETTER_SETTER_REF
+18 -10
View File
@@ -34,6 +34,7 @@
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include <sched.h> #include <sched.h>
#include "ChannelRouterSettings.hpp"
using namespace pipedal; using namespace pipedal;
@@ -169,19 +170,26 @@ public:
delete[] inputBuffers; delete[] inputBuffers;
delete[] outputBuffers; delete[] outputBuffers;
} }
std::vector<std::string> SelectChannels(const std::vector<std::string> &available, const std::vector<int> &selection) std::vector<int64_t> SelectChannels(const std::vector<std::string> &available, const std::vector<int> &selection)
{ {
std::vector<int64_t> result;
if (selection.size() == 0) if (selection.size() == 0)
return available; {
result.resize(available.size());
for (size_t i = 0; i < result.size(); ++i)
{
result[i] = (int64_t)i;
}
return result;
}
std::vector<std::string> result;
for (int sel : selection) for (int sel : selection)
{ {
if (sel < 0 || sel >= available.size()) if (sel < 0 || sel >= available.size())
{ {
throw PiPedalArgumentException(SS("Invalid channel: " + sel)); throw PiPedalArgumentException(SS("Invalid channel: " + sel));
} }
result.push_back(available[sel]); result.push_back(sel);
} }
return result; return result;
} }
@@ -199,22 +207,22 @@ public:
auto &availableInputs = jackConfiguration.inputAudioPorts(); auto &availableInputs = jackConfiguration.inputAudioPorts();
auto &availableOutputs = jackConfiguration.outputAudioPorts(); auto &availableOutputs = jackConfiguration.outputAudioPorts();
std::vector<std::string> inputAudioPorts, outputAudioPorts; std::vector<int64_t> inputAudioPorts, outputAudioPorts;
inputAudioPorts = SelectChannels(availableInputs, this->inputChannels); inputAudioPorts = SelectChannels(availableInputs, this->inputChannels);
outputAudioPorts = SelectChannels(availableOutputs, this->outputChannels); outputAudioPorts = SelectChannels(availableOutputs, this->outputChannels);
JackChannelSelection channelSelection( ChannelSelection channelSelection;
inputAudioPorts, outputAudioPorts, channelSelection.mainInputChannels() = inputAudioPorts;
std::vector<AlsaMidiDeviceInfo>()); channelSelection.mainOutputChannels() = outputAudioPorts;
audioDriver = CreateAlsaDriver(this); audioDriver = CreateAlsaDriver(this);
latencyMonitor.Init(jackConfiguration.sampleRate()); latencyMonitor.Init(jackConfiguration.sampleRate());
audioDriver->Open(serverSettings, channelSelection); audioDriver->Open(serverSettings, channelSelection);
inputBuffers = new float *[channelSelection.GetInputAudioPorts().size()]; inputBuffers = new float *[channelSelection.mainInputChannels().size()];
outputBuffers = new float *[channelSelection.GetOutputAudioPorts().size()]; outputBuffers = new float *[channelSelection.mainOutputChannels().size()];
audioDriver->Activate(); audioDriver->Activate();
+14 -8
View File
@@ -81,7 +81,7 @@ PiPedalModel::PiPedalModel()
this->updater = Updater::Create(); this->updater = Updater::Create();
this->updater->Start(); this->updater->Start();
this->currentUpdateStatus = updater->GetCurrentStatus(); this->currentUpdateStatus = updater->GetCurrentStatus();
this->pedalboard = Pedalboard::MakeDefault(Pedalboard::InstanceType::MainPedalboard); this->pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard);
#if JACK_HOST #if JACK_HOST
this->jackServerSettings = this->storage.GetJackServerSettings(); // to get onboarding flag. this->jackServerSettings = this->storage.GetJackServerSettings(); // to get onboarding flag.
this->jackServerSettings.ReadJackDaemonConfiguration(); this->jackServerSettings.ReadJackDaemonConfiguration();
@@ -216,14 +216,20 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration)
storage.Initialize(); storage.Initialize();
pluginHost.SetPluginStoragePath(storage.GetPluginUploadDirectory()); pluginHost.SetPluginStoragePath(storage.GetPluginUploadDirectory());
this->channelRouterSettings = storage.GetChannelRouterSettings();
this->systemMidiBindings = storage.GetSystemMidiBindings(); this->systemMidiBindings = storage.GetSystemMidiBindings();
#if JACK_HOST #if JACK_HOST
this->jackConfiguration = this->jackConfiguration.JackInitialize(); this->jackConfiguration = this->jackConfiguration.JackInitialize();
#else #else
this->jackServerSettings = storage.GetJackServerSettings(); this->jackServerSettings = storage.GetJackServerSettings();
this->jackConfiguration.AlsaInitialize(jackServerSettings);
#endif #endif
this->channelRouterSettings = storage.GetChannelRouterSettings();
pluginHost.OnConfigurationChanged(
jackConfiguration,
storage.GetChannelSelection());
} }
void PiPedalModel::LoadLv2PluginInfo() void PiPedalModel::LoadLv2PluginInfo()
@@ -299,7 +305,7 @@ void PiPedalModel::Load()
if (CrashGuard::HasCrashed()) if (CrashGuard::HasCrashed())
{ {
// ignore the current preset, and load a blank pedalboard in order to avoid a potential plugin crash. // ignore the current preset, and load a blank pedalboard in order to avoid a potential plugin crash.
this->pedalboard = Pedalboard::MakeDefault(Pedalboard::InstanceType::MainPedalboard); this->pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard);
} }
else else
{ {
@@ -1429,7 +1435,7 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver)
this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection); this->pluginHost.OnConfigurationChanged(jackConfiguration, channelSelection);
FireChannelSelectionChanged(-1); FireChannelRouterSettingsChanged(-1);
LoadCurrentPedalboard(); LoadCurrentPedalboard();
this->UpdateRealtimeVuSubscriptions(); this->UpdateRealtimeVuSubscriptions();
@@ -1526,17 +1532,17 @@ std::vector<AlsaSequencerPortSelection> PiPedalModel::GetAlsaSequencerPorts()
} }
void PiPedalModel::FireChannelSelectionChanged(int64_t clientId) void PiPedalModel::FireChannelRouterSettingsChanged(int64_t clientId)
{ {
std::lock_guard<std::recursive_mutex> guard{mutex}; std::lock_guard<std::recursive_mutex> guard{mutex};
{ {
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us) // take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
JackChannelSelection channelSelection = storage.GetJackChannelSelection(this->jackConfiguration); ChannelRouterSettings::ptr channelRouterSettings = this->channelRouterSettings;
std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()}; std::vector<IPiPedalModelSubscriber::ptr> t{subscribers.begin(), subscribers.end()};
for (auto &subscriber : t) for (auto &subscriber : t)
{ {
subscriber->OnChannelSelectionChanged(clientId, channelSelection); subscriber->OnChannelRouterSettingsChanged(clientId, *channelRouterSettings);
} }
} }
} }
@@ -3331,7 +3337,7 @@ void PiPedalModel::SetChannelRouterSettings(int64_t clientId,ChannelRouterSettin
} }
RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications.. RestartAudio(); // no lock to avoid mutex deadlock when reader thread is sending notifications..
this->FireChannelSelectionChanged(clientId); this->FireChannelRouterSettingsChanged(clientId);
} }
+2 -2
View File
@@ -73,7 +73,7 @@ namespace pipedal
virtual void OnSnapshotModified(int64_t selectedSnapshot, bool modified) = 0; virtual void OnSnapshotModified(int64_t selectedSnapshot, bool modified) = 0;
virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) = 0; virtual void OnSelectedSnapshotChanged(int64_t selectedSnapshot) = 0;
virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0; virtual void OnPluginPresetsChanged(const std::string &pluginUri) = 0;
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) = 0; virtual void OnChannelRouterSettingsChanged(int64_t clientId, const ChannelRouterSettings &channelRouterSettings) = 0;
virtual void OnVuMeterUpdate(const std::vector<VuUpdate> &updates) = 0; virtual void OnVuMeterUpdate(const std::vector<VuUpdate> &updates) = 0;
virtual void OnBankIndexChanged(const BankIndex &bankIndex) = 0; virtual void OnBankIndexChanged(const BankIndex &bankIndex) = 0;
virtual void OnJackServerSettingsChanged(const JackServerSettings &jackServerSettings) = 0; virtual void OnJackServerSettingsChanged(const JackServerSettings &jackServerSettings) = 0;
@@ -206,7 +206,7 @@ namespace pipedal
void FirePresetChanged(bool changed); void FirePresetChanged(bool changed);
void FirePluginPresetsChanged(const std::string &pluginUri); void FirePluginPresetsChanged(const std::string &pluginUri);
void FirePedalboardChanged(int64_t clientId, bool reloadAudioThread = true); void FirePedalboardChanged(int64_t clientId, bool reloadAudioThread = true);
void FireChannelSelectionChanged(int64_t clientId); void FireChannelRouterSettingsChanged(int64_t clientId);
void FireBanksChanged(int64_t clientId); void FireBanksChanged(int64_t clientId);
void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration); void FireJackConfigurationChanged(const JackConfiguration &jackConfiguration);
void FireLv2StateChanged(int64_t instanceId, const Lv2PluginState &lv2State); void FireLv2StateChanged(int64_t instanceId, const Lv2PluginState &lv2State);
+16 -11
View File
@@ -537,17 +537,24 @@ JSON_MAP_REFERENCE(SnapshotModifiedBody, snapshotIndex)
JSON_MAP_REFERENCE(SnapshotModifiedBody, modified) JSON_MAP_REFERENCE(SnapshotModifiedBody, modified)
JSON_MAP_END() JSON_MAP_END()
class ChannelSelectionChangedBody class ChannelRouterSettingsChangedBody
{ {
public: public:
int64_t clientId_ = -1; ChannelRouterSettingsChangedBody(
JackChannelSelection *jackChannelSelection_ = nullptr; int64_t clientId,
const ChannelRouterSettings &channelRouterSettings
): clientId_(clientId), channelRouterSettings_(channelRouterSettings)
{
DECLARE_JSON_MAP(ChannelSelectionChangedBody); }
int64_t clientId_ = -1;
ChannelRouterSettings channelRouterSettings_;
DECLARE_JSON_MAP(ChannelRouterSettingsChangedBody);
}; };
JSON_MAP_BEGIN(ChannelSelectionChangedBody) JSON_MAP_BEGIN(ChannelRouterSettingsChangedBody)
JSON_MAP_REFERENCE(ChannelSelectionChangedBody, clientId) JSON_MAP_REFERENCE(ChannelRouterSettingsChangedBody, clientId)
JSON_MAP_REFERENCE(ChannelSelectionChangedBody, jackChannelSelection) JSON_MAP_REFERENCE(ChannelRouterSettingsChangedBody, channelRouterSettings)
JSON_MAP_END() JSON_MAP_END()
class PresetsChangedBody class PresetsChangedBody
@@ -2000,11 +2007,9 @@ private:
Send("onShowStatusMonitorChanged", show); Send("onShowStatusMonitorChanged", show);
} }
virtual void OnChannelSelectionChanged(int64_t clientId, const JackChannelSelection &channelSelection) virtual void OnChannelRouterSettingsChanged(int64_t clientId, const ChannelRouterSettings &channelRouterSettings)
{ {
ChannelSelectionChangedBody body; ChannelRouterSettingsChangedBody body(clientId, channelRouterSettings);
body.clientId_ = clientId;
body.jackChannelSelection_ = const_cast<JackChannelSelection *>(&channelSelection);
Send("onChannelSelectionChanged", body); Send("onChannelSelectionChanged", body);
} }
+5 -5
View File
@@ -98,20 +98,20 @@ namespace pipedal
struct RealtimePedalboardItemIndex { struct RealtimePedalboardItemIndex {
RealtimePedalboardItemIndex() RealtimePedalboardItemIndex()
: instanceType(Pedalboard::InstanceType::MainPedalboard), index(-1) : instanceType(Pedalboard::PedalboardType::MainPedalboard), index(-1)
{ {
} }
RealtimePedalboardItemIndex( RealtimePedalboardItemIndex(
Pedalboard::InstanceType instanceType, Pedalboard::PedalboardType instanceType,
int index) int64_t index)
: instanceType(instanceType), index(index) : instanceType(instanceType), index(index)
{ {
} }
RealtimePedalboardItemIndex(const RealtimePedalboardItemIndex& other) = default; RealtimePedalboardItemIndex(const RealtimePedalboardItemIndex& other) = default;
RealtimePedalboardItemIndex& operator=(const RealtimePedalboardItemIndex& other) = default; RealtimePedalboardItemIndex& operator=(const RealtimePedalboardItemIndex& other) = default;
Pedalboard::InstanceType instanceType = Pedalboard::InstanceType::MainPedalboard; Pedalboard::PedalboardType instanceType = Pedalboard::PedalboardType::MainPedalboard;
int index = -1; int64_t index = -1;
}; };
struct RealtimeMidiEventRequest struct RealtimeMidiEventRequest
+1 -2
View File
@@ -19,13 +19,12 @@
#pragma once #pragma once
#include "lilv/lilv.h"
#include "lv2/state/state.h" #include "lv2/state/state.h"
#include <cstddef> #include <cstddef>
#include "json_variant.hpp" #include "json_variant.hpp"
#include "MapFeature.hpp" #include "MapFeature.hpp"
#include "IHost.hpp" #include "IHost.hpp"
#include "lilv/lilv.h" #include <lilv/lilv.h>
namespace pipedal namespace pipedal
{ {
+4 -3
View File
@@ -377,7 +377,7 @@ void Storage::Initialize()
LoadTone3000Auth(); LoadTone3000Auth();
try try
{ {
LoadChannelSelection(); LoadJackChannelSelection();
} }
catch (const std::exception &) catch (const std::exception &)
{ {
@@ -476,7 +476,7 @@ void Storage::LoadBankIndex()
if (bankIndex.entries().size() == 0) if (bankIndex.entries().size() == 0)
{ {
currentBank.clear(); currentBank.clear();
Pedalboard defaultPedalboard = Pedalboard::MakeDefault(); Pedalboard defaultPedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard);
int64_t instanceId = currentBank.addPreset(defaultPedalboard); int64_t instanceId = currentBank.addPreset(defaultPedalboard);
currentBank.selectedPreset(instanceId); currentBank.selectedPreset(instanceId);
@@ -1135,7 +1135,7 @@ AlsaSequencerConfiguration Storage::GetAlsaSequencerConfiguration() const
return this->alsaSequencerConfiguration; return this->alsaSequencerConfiguration;
} }
void Storage::LoadChannelSelection() void Storage::LoadJackChannelSelection()
{ {
auto fileName = this->GetChannelSelectionFileName(); auto fileName = this->GetChannelSelectionFileName();
if (std::filesystem::exists(fileName)) if (std::filesystem::exists(fileName))
@@ -3102,6 +3102,7 @@ ChannelRouterSettings::ptr Storage::LoadChannelRouterSettings()
json_reader reader(is); json_reader reader(is);
ChannelRouterSettings::ptr result; ChannelRouterSettings::ptr result;
reader.read(&result); reader.read(&result);
this->channelSelection = ChannelSelection(*result);
return result; return result;
} catch (const std::exception &e) { } catch (const std::exception &e) {
Lv2Log::error("Failed to load Channel Router settings: %s", e.what()); Lv2Log::error("Failed to load Channel Router settings: %s", e.what());
+1 -1
View File
@@ -100,7 +100,7 @@ private:
void LoadCurrentBank(); void LoadCurrentBank();
void SaveCurrentBank(); void SaveCurrentBank();
void LoadChannelSelection(); void LoadJackChannelSelection();
void SaveChannelSelection(); void SaveChannelSelection();
void LoadAlsaSequencerConfiguration(); void LoadAlsaSequencerConfiguration();
+124 -7
View File
@@ -1,23 +1,61 @@
import { Pedalboard, ControlValue } from "./Pedalboard.tsx"; import { Pedalboard, ControlValue } from "./Pedalboard.tsx";
import JackServerSettings from "./JackServerSettings.tsx";
import JackConfiguration from "./Jack.tsx";
function isActiveChannel(channels: number[]): boolean {
if (channels.length < 2) return false;
return channels[1] >= 0 || channels[0] >= 0;
}
function chName(ch: number): string {
if (ch === -1) {
return "None";
}
return "Ch " + (ch + 1);
}
function channelPairName(channels: number[], maxChannels: number): string {
if (channels.length !== 2) {
return "Invalid";
}
if (channels[0] === -1 && channels[1] === -1) {
return "None";
}
if (maxChannels === 2) {
if (channels[0] === 0 && channels[1] === 1) {
return "Stereo";
}
if (channels[0] === 0 && (channels[0] === channels[1] || channels[1] === -1)) {
return "Left";
}
if (channels[1] === 1 && (channels[0] === channels[1])) {
return "Right";
}
if (channels[0] === 1 && channels[1] === 0) {
return "Right,Left";
}
}
return chName(channels[0]) + "," + chName(channels[1]);
}
export default class ChannelRouterSettings { export default class ChannelRouterSettings {
configured: boolean = false; configured: boolean = false;
channelRouterPresetId: number = -1; channelRouterPresetId: number = -1;
mainInputChannels: number[] = [1,1]; mainInputChannels: number[] = [1, 1];
mainOutputChannels: number[] = [0,1]; mainOutputChannels: number[] = [0, 1];
mainInserts: Pedalboard = new Pedalboard(); mainInserts: Pedalboard = new Pedalboard();
auxInputChannels: number[] = [0,0]; auxInputChannels: number[] = [0, 0];
auxOutputChannels: number[] = [-1,-1]; auxOutputChannels: number[] = [-1, -1];
auxInserts: Pedalboard = new Pedalboard(); auxInserts: Pedalboard = new Pedalboard();
sendInputChannels: number[] = [-1,-1]; sendInputChannels: number[] = [-1, -1];
sendOutputChannels: number[] = [-1,-1]; sendOutputChannels: number[] = [-1, -1];
controlValues: ControlValue[] = []; controlValues: ControlValue[] = [];
// Inserts... // Inserts...
@@ -55,10 +93,89 @@ export default class ChannelRouterSettings {
return true; return true;
} }
} }
let newValue = new ControlValue(symbol,value); let newValue = new ControlValue(symbol, value);
this.controlValues.push(newValue); this.controlValues.push(newValue);
this.controlValues = this.controlValues.slice(); // trigger observers. this.controlValues = this.controlValues.slice(); // trigger observers.
return true; return true;
} }
getDescription(jackConfiguration: JackConfiguration): string {
if (!this.configured) {
return "Not configured";
}
if (!jackConfiguration.isValid)
{
return "Not configured"
}
if (!this.isValid(jackConfiguration)) {
return "Invalid configuration";
}
let nInputs = jackConfiguration.inputAudioPorts.length;
let nOutputs = jackConfiguration.outputAudioPorts.length;
let description = channelPairName(this.mainInputChannels, nInputs) + " -> " +
channelPairName(this.mainOutputChannels, nOutputs);
if (isActiveChannel(this.auxInputChannels) && isActiveChannel(this.auxOutputChannels))
{
description += " " + "+ re-amp: " + channelPairName(this.auxInputChannels, nInputs) + " -> " +
channelPairName(this.auxOutputChannels, nOutputs);
}
if (isActiveChannel(this.sendInputChannels) && isActiveChannel(this.sendOutputChannels)) {
description += " " + "+ send: " +channelPairName(this.sendOutputChannels, nOutputs)
+ " -> " + channelPairName(this.sendInputChannels, nInputs);
}
return description;
}
canEdit(jackConfiguration: JackConfiguration): boolean {
return jackConfiguration.isValid;
}
isValid(jackConfiguration: JackConfiguration): boolean {
if (!this.configured) {
return false;
}
let maxInputChannels = jackConfiguration.inputAudioPorts.length;
let maxOutputChannels = jackConfiguration.outputAudioPorts.length;
let hasInput = false;
let hasOutput = false;
for (let ch of this.mainInputChannels) {
if (ch >= 0) {
hasInput = true;
if (ch >= maxInputChannels) {
return false;
}
}
}
if (!hasInput) {
return false;
}
for (let ch of this.mainOutputChannels) {
if (ch >= 0) {
hasOutput = true;
if (ch >= maxOutputChannels) {
return false;
}
}
}
if (!hasOutput) {
return false;
}
for (let ch of this.auxInputChannels) {
if (ch >= maxInputChannels) {
return false;
}
}
for (let ch of this.auxOutputChannels) {
if (ch >= maxOutputChannels) {
return false;
}
}
for (let ch of this.sendInputChannels) {
if (ch >= maxInputChannels) {
return false;
}
}
return true;
}
} }
@@ -42,6 +42,11 @@ import DialogEx from './DialogEx';
import { PiPedalModelFactory } from './PiPedalModel'; import { PiPedalModelFactory } from './PiPedalModel';
let debugInputChannels: number | null = 2;
let debugOutputChannels: number | null = 4;
export interface ChannelRouterSettingsDialogProps { export interface ChannelRouterSettingsDialogProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
@@ -309,6 +314,12 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
let ChannelSelect = (routeType: RouteType, channelIndex: number, input: boolean, icon: boolean = true) => { let ChannelSelect = (routeType: RouteType, channelIndex: number, input: boolean, icon: boolean = true) => {
let channelCount = input ? config.inputAudioPorts.length : config.outputAudioPorts.length; let channelCount = input ? config.inputAudioPorts.length : config.outputAudioPorts.length;
if (input && debugInputChannels != null) {
channelCount = debugInputChannels;
}
if (!input && debugOutputChannels != null) {
channelCount = debugOutputChannels;
}
let value: number; let value: number;
@@ -336,7 +347,7 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
} }
onChange={(event) => { onChange={(event) => {
let newValue = event.target.value as number; let newValue = event.target.value as number;
let newSettings = Object.assign(new ChannelRouterSettings(), settings); let newSettings = new ChannelRouterSettings().deserialize(settings);
switch (routeType) { switch (routeType) {
case RouteType.Main: case RouteType.Main:
if (input) { if (input) {
@@ -385,7 +396,11 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
} }
for (let preset of channelRouterPresets) { for (let preset of channelRouterPresets) {
if (preset.id === presetId) { if (preset.id === presetId) {
model.setChannelRouterSettings(preset.settings); let newPreset = Object.assign(new ChannelRouterSettings(), preset.settings);
// mark the presets as inserts.
newPreset.mainInserts.nextInstanceId = Pedalboard.MAIN_INSERT_INSTANCE_BASE;
newPreset.auxInserts.nextInstanceId = Pedalboard.AUX_INSERT_INSTANCE_BASE;
model.setChannelRouterSettings(newPreset);
return; return;
} }
} }
@@ -759,7 +774,7 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
{ChannelSelect(RouteType.Send, 0, true, false)} {ChannelSelect(RouteType.Send, 0, true, false)}
</td> </td>
<td style={cellLeft}> <td style={cellLeft}>
{ChannelSelect(RouteType.Send, 1, false, false)} {ChannelSelect(RouteType.Send, 1, true, false)}
</td> </td>
<td></td> <td></td>
</tr> </tr>
+4 -4
View File
@@ -381,10 +381,10 @@ export class Pedalboard implements Deserializable<Pedalboard> {
static readonly START_CONTROL = -2; // synthetic PedalboardItem for input volume. static readonly START_CONTROL = -2; // synthetic PedalboardItem for input volume.
static readonly END_CONTROL = -3; // synthetic PedalboardItem for output volume. static readonly END_CONTROL = -3; // synthetic PedalboardItem for output volume.
static readonly MAIN_INSERT_START_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2; static readonly MAIN_INSERT_START_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + -2 + MAX_INSTANCE_ID;
static readonly MAIN_INSERT_END_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3; static readonly MAIN_INSERT_END_CONTROL_ID = Pedalboard.MAIN_INSERT_INSTANCE_BASE + -3 + MAX_INSTANCE_ID;
static readonly AUX_INSERT_START_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -2; static readonly AUX_INSERT_START_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + -2 + MAX_INSTANCE_ID
static readonly AUX_INSERT_END_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + MAX_INSTANCE_ID -3; static readonly AUX_INSERT_END_CONTROL_ID = Pedalboard.AUX_INSERT_INSTANCE_BASE + -3 + MAX_INSTANCE_ID;
static readonly START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start"; static readonly START_PEDALBOARD_ITEM_URI = "uri://two-play/pipedal/pedalboard#Start";
+7 -6
View File
@@ -431,9 +431,9 @@ interface MonitorPortOutputBody {
} }
interface ChannelSelectionChangedBody { interface ChannelRouterSettingsChangedBody {
clientId: number; clientId: number;
jackChannelSelection: JackChannelSelection; channelRouterSettings: ChannelRouterSettings;
} }
interface RenamePresetBody { interface RenamePresetBody {
clientId: number; clientId: number;
@@ -730,10 +730,11 @@ export class PiPedalModel //implements PiPedalModel
} else if (message === "onShowStatusMonitorChanged") { } else if (message === "onShowStatusMonitorChanged") {
let value = body as boolean; let value = body as boolean;
this.showStatusMonitor.set(value); this.showStatusMonitor.set(value);
} else if (message === "onChannelSelectionChanged") { } else if (message === "onChannelRouterSettingsChanged") {
let channelSelectionBody = body as ChannelSelectionChangedBody; let channelRouterSettingChangedBody = body as ChannelRouterSettingsChangedBody;
let channelSelection = new JackChannelSelection().deserialize(channelSelectionBody.jackChannelSelection); let channelRouterSettings = new ChannelRouterSettings().deserialize(
this.jackSettings.set(channelSelection); channelRouterSettingChangedBody.channelRouterSettings);
this.channelRouterSettings.set(channelRouterSettings);
} else if (message === "onSnapshotModified") { } else if (message === "onSnapshotModified") {
let { snapshotIndex, modified } = (body as { snapshotIndex: number, modified: boolean }); let { snapshotIndex, modified } = (body as { snapshotIndex: number, modified: boolean });
let snapshots = this.pedalboard.get().snapshots; let snapshots = this.pedalboard.get().snapshots;
+38 -52
View File
@@ -519,29 +519,10 @@ const SettingsDialog = withStyles(
} }
handleSelectChannelsDialogResult(channels: string[] | null): void { handleSelectChannelsDialogResult(channels: string[] | null): void {
if (this.state.jackSettings === null) { return; } this.setState({
if (channels) { showInputSelectDialog: false,
let newSelection = this.state.jackSettings.clone(); showOutputSelectDialog: false
if (this.state.showInputSelectDialog) { });
newSelection.inputAudioPorts = channels;
} else {
newSelection.outputAudioPorts = channels;
}
this.setState({
jackSettings: newSelection,
showInputSelectDialog: false,
showOutputSelectDialog: false
});
this.model.setJackSettings(newSelection);
} else {
this.setState({
showInputSelectDialog: false,
showOutputSelectDialog: false
});
}
} }
midiSummary(): string { midiSummary(): string {
@@ -618,8 +599,10 @@ const SettingsDialog = withStyles(
let disableShutdown = this.state.shuttingDown || this.state.restarting; let disableShutdown = this.state.shuttingDown || this.state.restarting;
let canKeepScreenOn = this.model.canKeepScreenOn; let canKeepScreenOn = this.model.canKeepScreenOn;
let hasAudioConfig = isConfigValid && this.state.jackConfiguration.outputAudioPorts.length >= 1 let hasAudioConfig = isConfigValid
&& this.state.jackConfiguration.inputAudioPorts.length >= 1 && this.state.jackConfiguration.inputAudioPorts.length >= 1
&& this.state.jackConfiguration.outputAudioPorts.length >= 1;
return ( return (
<DialogEx tag="settings" fullScreen open={this.props.open} <DialogEx tag="settings" fullScreen open={this.props.open}
@@ -753,40 +736,43 @@ const SettingsDialog = withStyles(
<SelectHoverBackground selected={false} showHover={true} /> <SelectHoverBackground selected={false} showHover={true} />
<div style={{ width: "100%" }}> <div style={{ width: "100%" }}>
<Typography display="block" variant="body2" noWrap>Channel Routing</Typography> <Typography display="block" variant="body2" noWrap>Channel Routing</Typography>
<Typography display="block" variant="caption" color="textSecondary" noWrap>{ <Typography display="block" variant="caption"
this.state.jackSettings == null ? "" : color={
this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}</Typography> this.state.channelRouterSettings?.isValid(this.state.jackConfiguration) ?? false
? "textSecondary": "error"} noWrap>{
this.state.channelRouterSettings?.getDescription(this.state.jackConfiguration)??""}</Typography>
</div> </div>
</ButtonBase> </ButtonBase>
{/* Old Input and Output selection
<ButtonBase className={classes.setting} onClick={() => this.handleInputSelection()}
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
<ButtonBase className={classes.setting} onClick={() => this.handleInputSelection()} >
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1} <SelectHoverBackground selected={false} showHover={true} />
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }} <div style={{ width: "100%" }}>
<Typography display="block" variant="body2" noWrap>Input channels</Typography>
> <Typography display="block" variant="caption" color="textSecondary" noWrap>{
<SelectHoverBackground selected={false} showHover={true} /> this.state.jackSettings == null ? "" :
<div style={{ width: "100%" }}> this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}</Typography>
<Typography display="block" variant="body2" noWrap>Input channels</Typography> </div>
<Typography display="block" variant="caption" color="textSecondary" noWrap>{ </ButtonBase>
this.state.jackSettings == null ? "" : <ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()}
this.state.jackSettings.getAudioInputDisplayValue(this.state.jackConfiguration)}</Typography> disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1}
</div> style={{ opacity: !isConfigValid ? 0.6 : 1.0 }}
</ButtonBase> >
<ButtonBase className={classes.setting} onClick={() => this.handleOutputSelection()} <SelectHoverBackground selected={false} showHover={true} />
disabled={!isConfigValid || this.state.jackConfiguration.outputAudioPorts.length <= 1} <div style={{ width: "100%" }}>
style={{ opacity: !isConfigValid ? 0.6 : 1.0 }} <Typography display="block" variant="body2" noWrap>Output channels</Typography>
> <Typography display="block" variant="caption" color="textSecondary" noWrap>{
<SelectHoverBackground selected={false} showHover={true} /> this.state.jackSettings == null ? "" :
<div style={{ width: "100%" }}> this.state.jackSettings.getAudioOutputDisplayValue(this.state.jackConfiguration)}</Typography>
<Typography display="block" variant="body2" noWrap>Output channels</Typography> </div>
<Typography display="block" variant="caption" color="textSecondary" noWrap>{ </ButtonBase>
this.state.jackSettings == null ? "" : */}
this.state.jackSettings.getAudioOutputDisplayValue(this.state.jackConfiguration)}</Typography>
</div>
</ButtonBase>
<Divider /> <Divider />
<div > <div >
<Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">MIDI</Typography> <Typography className={classes.sectionHead} display="block" variant="caption" color="secondary">MIDI</Typography>