Manifest.json, Channel Router Sync. Alsa device enumeration.

This commit is contained in:
Robin E.R. Davies
2026-02-13 16:06:10 -05:00
parent cf7f499994
commit 564ed914cc
33 changed files with 2001 additions and 1191 deletions
+91
View File
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
viewBox="0 0 16.933332 16.933334"
version="1.1"
id="svg1"
inkscape:export-filename="logo192.png"
inkscape:export-xdpi="288"
inkscape:export-ydpi="288"
inkscape:version="1.4.3 (1:1.4.3+202512261035+0d15f75042)"
sodipodi:docname="Pi-Icon-Inkscape.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="true"
inkscape:lockguides="true"
inkscape:zoom="14.609375"
inkscape:cx="32"
inkscape:cy="32"
inkscape:window-width="2141"
inkscape:window-height="1161"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
id="grid2"
units="px"
originx="0"
originy="0"
spacingx="1.0583333"
spacingy="1.0583333"
empcolor="#000000"
empopacity="0.30196078"
color="#000000"
opacity="0.14901961"
empspacing="4"
enabled="true"
visible="true"
dotted="true" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline">
<circle
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:#5e2edf;fill-opacity:1;stroke:none;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path3"
cx="8.4666653"
cy="8.4666653"
r="8.4666653" />
<g
id="g5"
transform="matrix(1.1241207,0,0,1.1241207,-1.0580763,-1.0508884)">
<circle
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path4"
cx="8.4666662"
cy="8.4666653"
r="4.8967609" />
<ellipse
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.396875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path5"
cx="8.4666662"
cy="8.4666653"
rx="4.2097802"
ry="4.2333322" />
<path
id="path4-9"
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;baseline-shift:baseline;display:inline;overflow:visible;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#5e2edf;stroke-width:2.11667;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="M 12.164115,4.1470337 7.9597209,7.8558512 8.9829142,9.0702474 13.214697,5.3371419 C 12.920982,4.8932514 12.566824,4.4925879 12.164115,4.1470337 Z" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

+91
View File
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
viewBox="0 0 16.933332 16.933334"
version="1.1"
id="svg1"
inkscape:export-filename="../vite/public/logo512.png"
inkscape:export-xdpi="768"
inkscape:export-ydpi="768"
inkscape:version="1.4.3 (1:1.4.3+202512261035+0d15f75042)"
sodipodi:docname="Pi-Icon-Inkscape.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="true"
inkscape:lockguides="true"
inkscape:zoom="14.609375"
inkscape:cx="32"
inkscape:cy="32"
inkscape:window-width="2141"
inkscape:window-height="1161"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
id="grid2"
units="px"
originx="0"
originy="0"
spacingx="1.0583333"
spacingy="1.0583333"
empcolor="#000000"
empopacity="0.30196078"
color="#000000"
opacity="0.14901961"
empspacing="4"
enabled="true"
visible="true"
dotted="true" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline">
<circle
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:#5e2edf;fill-opacity:1;stroke:none;stroke-width:2.07592;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path3"
cx="8.4666653"
cy="8.4666653"
r="8.3036709" />
<g
id="g5"
transform="matrix(1.0057192,0,0,1.0057192,-0.04986822,-0.04842228)">
<circle
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:1.5875;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path4"
cx="8.4666662"
cy="8.4666653"
r="4.8967609" />
<ellipse
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.774042;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
id="path5"
cx="8.4666662"
cy="8.4666653"
rx="4.2097802"
ry="4.2333322" />
<path
id="path4-9"
style="font-variation-settings:'GRAD' 15, 'opsz' 144, 'wdth' 132, 'wght' 200;baseline-shift:baseline;display:inline;overflow:visible;opacity:1;fill:#ffffff;fill-opacity:1;stroke:#5e2edf;stroke-width:2.11667;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="M 12.164115,4.1470337 7.9597209,7.8558512 8.9829142,9.0702474 13.214697,5.3371419 C 12.920982,4.8932514 12.566824,4.4925879 12.164115,4.1470337 Z" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

+17 -1
View File
@@ -1895,8 +1895,14 @@ namespace pipedal
std::vector<float*> &channelBuffers std::vector<float*> &channelBuffers
) { ) {
size_t nChannels = channelSelection.size(); size_t nChannels = channelSelection.size();
channelBuffers.resize(nChannels); if (nChannels == 0)
{
channelBuffers.resize(1);
channelBuffers[0] = zeroInputBuffer;
return;
}
channelBuffers.resize(nChannels);
for (size_t i = 0; i < nChannels; ++i) for (size_t i = 0; i < nChannels; ++i)
{ {
int64_t deviceChannel = channelSelection[i]; int64_t deviceChannel = channelSelection[i];
@@ -1932,6 +1938,16 @@ namespace pipedal
) { ) {
size_t nChannels = channelSelection.size(); size_t nChannels = channelSelection.size();
if (nChannels == 0)
{
channelBuffers.resize(1);
if (discardOutputBuffer != nullptr)
{
discardOutputBuffer = AllocateAudioBuffer();
}
channelBuffers[0] = discardOutputBuffer;
return;
}
channelBuffers.resize(nChannels); channelBuffers.resize(nChannels);
for (size_t i = 0; i < nChannels; ++i) for (size_t i = 0; i < nChannels; ++i)
{ {
+1 -1
View File
@@ -87,7 +87,7 @@ public:
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;
jackConfiguration.AlsaInitialize(serverSettings); jackConfiguration.AlsaInitialize(serverSettings);
+75
View File
@@ -533,6 +533,7 @@ private:
std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue. std::vector<std::shared_ptr<Lv2Pedalboard>> activePedalboards; // pedalboards that have been sent to the audio queue.
Lv2Pedalboard *realtimeActivePedalboard = nullptr; Lv2Pedalboard *realtimeActivePedalboard = nullptr;
Lv2RoutingInserts realtimeRoutingInserts;
uint32_t sampleRate = 0; uint32_t sampleRate = 0;
uint64_t currentSample = 0; uint64_t currentSample = 0;
@@ -894,6 +895,47 @@ private:
return false; // signal to caller that the effect has been replaced, and processing needs to start again. return false; // signal to caller that the effect has been replaced, and processing needs to start again.
} }
case RingBufferCommand::ReplaceRoutingInserts:
{
Lv2RoutingInserts body;
realtimeReader.readComplete(&body);
auto oldValue = this->realtimeRoutingInserts;
this->realtimeRoutingInserts = body;
realtimeWriter.RoutingInsertsReplaced(oldValue);
// invalidate the possibly no-good subscriptions. Model will update them shortly.
freeRealtimeVuConfiguration();
freeRealtimeMonitorPortSubscriptions();
cancelParameterRequests();
Lv2Pedalboard *mainInserts = body.mainInserts;
if (mainInserts)
{
mainInserts->ResetAtomBuffers();
// issue patch gets for all writable path properties.
for (auto pEffect : mainInserts->GetEffects())
{
pEffect->RequestAllPathPatchProperties();
}
mainInserts->UpdateAudioPorts();
}
Lv2Pedalboard *auxInserts = body.auxInserts;
if (auxInserts)
{
auxInserts->ResetAtomBuffers();
// issue patch gets for all writable path properties.
for (auto pEffect : auxInserts->GetEffects())
{
pEffect->RequestAllPathPatchProperties();
}
auxInserts->UpdateAudioPorts();
}
reEntered = false;
return false; // signal to caller that the effect has been replaced, and processing needs to start again.
}
default: default:
throw PiPedalStateException("Unknown Ringbuffer command."); throw PiPedalStateException("Unknown Ringbuffer command.");
} }
@@ -1549,6 +1591,20 @@ public:
hostReader.read(&body); hostReader.read(&body);
OnActivePedalboardReleased(body.oldEffect); OnActivePedalboardReleased(body.oldEffect);
} }
else if (command == RingBufferCommand::RoutingInsertsReplaced)
{
Lv2RoutingInserts body;
hostReader.read(&body);
if (body.mainInserts)
{
OnActivePedalboardReleased(body.mainInserts);
}
if (body.auxInserts)
{
OnActivePedalboardReleased(body.auxInserts);
}
}
else if (command == RingBufferCommand::FreeSnapshot) else if (command == RingBufferCommand::FreeSnapshot)
{ {
IndexedSnapshot *snapshot; IndexedSnapshot *snapshot;
@@ -1782,6 +1838,25 @@ public:
} }
} }
virtual void SetChannelRoutingInserts(
const std::shared_ptr<Lv2Pedalboard> &mainInserts,
const std::shared_ptr<Lv2Pedalboard> &auxInserts
) override {
if (active && mainInserts)
{
mainInserts->Activate();
this->activePedalboards.push_back(mainInserts);
}
if (active && auxInserts)
{
auxInserts->Activate();
this->activePedalboards.push_back(auxInserts);
}
Lv2RoutingInserts routingInserts { mainInserts: mainInserts.get(), auxInserts: auxInserts.get()};
hostWriter.ReplaceRoutingInserts(routingInserts);
}
virtual void SetBypass(uint64_t instanceId, bool enabled) virtual void SetBypass(uint64_t instanceId, bool enabled)
{ {
std::lock_guard guard(mutex); std::lock_guard guard(mutex);
+6
View File
@@ -238,6 +238,12 @@ namespace pipedal
virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0; virtual void SetPedalboard(const std::shared_ptr<Lv2Pedalboard> &pedalboard) = 0;
virtual void SetChannelRoutingInserts(
const std::shared_ptr<Lv2Pedalboard> &mainInserts,
const std::shared_ptr<Lv2Pedalboard> &auxInserts
) = 0;
virtual void SetControlValue(uint64_t instanceId, const std::string &symbol, float value) = 0; virtual void SetControlValue(uint64_t instanceId, const std::string &symbol, float value) = 0;
virtual void SetInputVolume(float value) = 0; virtual void SetInputVolume(float value) = 0;
virtual void SetOutputVolume(float value) = 0; virtual void SetOutputVolume(float value) = 0;
+3 -1
View File
@@ -740,6 +740,7 @@ add_executable(pipedalconfig
ModFileTypes.cpp ModFileTypes.hpp ModFileTypes.cpp ModFileTypes.hpp
MimeTypes.cpp MimeTypes.hpp MimeTypes.cpp MimeTypes.hpp
PiPedalConfiguration.hpp PiPedalConfiguration.cpp PiPedalConfiguration.hpp PiPedalConfiguration.cpp
PiPedalAlsa.hpp PiPedalAlsa.cpp
JackServerSettings.hpp JackServerSettings.cpp JackServerSettings.hpp JackServerSettings.cpp
SystemConfigFile.hpp SystemConfigFile.cpp SystemConfigFile.hpp SystemConfigFile.cpp
WifiChannelSelectors.cpp WifiChannelSelectors.hpp WifiChannelSelectors.cpp WifiChannelSelectors.hpp
@@ -817,6 +818,7 @@ add_executable(pipedaladmind AdminMain.cpp CommandLineParser.hpp
SetWifiConfig.cpp SetWifiConfig.hpp SetWifiConfig.cpp SetWifiConfig.hpp
JackServerSettings.hpp JackServerSettings.cpp JackServerSettings.hpp JackServerSettings.cpp
PiPedalAlsa.hpp PiPedalAlsa.cpp
Lv2SystemdLogger.hpp Lv2SystemdLogger.cpp Lv2SystemdLogger.hpp Lv2SystemdLogger.cpp
SystemConfigFile.hpp SystemConfigFile.cpp SystemConfigFile.hpp SystemConfigFile.cpp
@@ -824,7 +826,7 @@ add_executable(pipedaladmind AdminMain.cpp CommandLineParser.hpp
asan_options.cpp asan_options.cpp
) )
target_link_libraries(pipedaladmind PRIVATE PiPedalCommon pthread atomic stdc++fs systemd ) target_link_libraries(pipedaladmind PRIVATE PiPedalCommon pthread atomic stdc++fs systemd asound)
add_executable(processcopyrights copyrightMain.cpp add_executable(processcopyrights copyrightMain.cpp
CommandLineParser.hpp CommandLineParser.hpp
+30
View File
@@ -135,9 +135,20 @@ ChannelRouterSettings::ChannelRouterSettings()
} }
std::vector<ChannelRouterPresetIndexEntry> ChannelRouterPresetBank::getIndexEntries()
{
std::vector<ChannelRouterPresetIndexEntry> result;
for (auto &entry: entries_)
{
result.push_back(ChannelRouterPresetIndexEntry{entry->id(), entry->name()});
}
return result;
}
JSON_MAP_BEGIN(ChannelRouterSettings) JSON_MAP_BEGIN(ChannelRouterSettings)
JSON_MAP_REFERENCE(ChannelRouterSettings, configured) JSON_MAP_REFERENCE(ChannelRouterSettings, configured)
JSON_MAP_REFERENCE(ChannelRouterSettings, changed)
JSON_MAP_REFERENCE(ChannelRouterSettings, channelRouterPresetId) JSON_MAP_REFERENCE(ChannelRouterSettings, channelRouterPresetId)
JSON_MAP_REFERENCE(ChannelRouterSettings, mainInputChannels) JSON_MAP_REFERENCE(ChannelRouterSettings, mainInputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, mainOutputChannels) JSON_MAP_REFERENCE(ChannelRouterSettings, mainOutputChannels)
@@ -149,3 +160,22 @@ JSON_MAP_REFERENCE(ChannelRouterSettings, sendInputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, sendOutputChannels) JSON_MAP_REFERENCE(ChannelRouterSettings, sendOutputChannels)
JSON_MAP_REFERENCE(ChannelRouterSettings, controlValues) JSON_MAP_REFERENCE(ChannelRouterSettings, controlValues)
JSON_MAP_END() JSON_MAP_END()
JSON_MAP_BEGIN(ChannelRouterPresetIndexEntry)
JSON_MAP_REFERENCE(ChannelRouterPresetIndexEntry, id)
JSON_MAP_REFERENCE(ChannelRouterPresetIndexEntry, name)
JSON_MAP_END()
JSON_MAP_BEGIN(ChannelRouterPresetBankEntry)
JSON_MAP_REFERENCE(ChannelRouterPresetBankEntry, id)
JSON_MAP_REFERENCE(ChannelRouterPresetBankEntry, name)
JSON_MAP_REFERENCE(ChannelRouterPresetBankEntry, channelRouterSettings)
JSON_MAP_END()
JSON_MAP_BEGIN(ChannelRouterPresetBank)
JSON_MAP_REFERENCE(ChannelRouterPresetBank, nextId)
JSON_MAP_REFERENCE(ChannelRouterPresetBank, version)
JSON_MAP_REFERENCE(ChannelRouterPresetBank, entries)
JSON_MAP_END()
+54
View File
@@ -35,6 +35,7 @@ namespace pipedal
bool configured_ = false; bool configured_ = false;
int64_t channelRouterPresetId_ = -1; int64_t channelRouterPresetId_ = -1;
bool changed_ = false;
std::vector<int64_t> mainInputChannels_ = {1, 1}; std::vector<int64_t> mainInputChannels_ = {1, 1};
std::vector<int64_t> mainOutputChannels_ = {0, 1}; std::vector<int64_t> mainOutputChannels_ = {0, 1};
@@ -60,6 +61,7 @@ namespace pipedal
JSON_GETTER_SETTER(configured) JSON_GETTER_SETTER(configured)
JSON_GETTER_SETTER(channelRouterPresetId) JSON_GETTER_SETTER(channelRouterPresetId)
JSON_GETTER_SETTER(changed)
JSON_GETTER_SETTER_REF(mainInputChannels) JSON_GETTER_SETTER_REF(mainInputChannels)
JSON_GETTER_SETTER_REF(mainOutputChannels) JSON_GETTER_SETTER_REF(mainOutputChannels)
JSON_GETTER_SETTER_REF(mainInserts) JSON_GETTER_SETTER_REF(mainInserts)
@@ -110,4 +112,56 @@ namespace pipedal
std::vector<int64_t> sendOutputChannels_; std::vector<int64_t> sendOutputChannels_;
}; };
class ChannelRouterPresetIndexEntry {
private:
int64_t id_ = -1;
std::string name_;
public:
ChannelRouterPresetIndexEntry() = default;
ChannelRouterPresetIndexEntry(const ChannelRouterPresetIndexEntry&other) = default;
ChannelRouterPresetIndexEntry(ChannelRouterPresetIndexEntry&&other) = default;
ChannelRouterPresetIndexEntry&operator=(const ChannelRouterPresetIndexEntry&other) = default;
ChannelRouterPresetIndexEntry&operator=(ChannelRouterPresetIndexEntry&&other) = default;
ChannelRouterPresetIndexEntry(int64_t id, const std::string&name)
: id_(id),name_(name) { }
JSON_GETTER_SETTER(id)
JSON_GETTER_SETTER_REF(name)
DECLARE_JSON_MAP(ChannelRouterPresetIndexEntry);
};
class ChannelRouterPresetBankEntry {
private:
int64_t id_;
std::string name_;
ChannelRouterSettings channelRouterSettings_;
public:
JSON_GETTER_SETTER(id)
JSON_GETTER_SETTER_REF(name)
JSON_GETTER_SETTER_REF(channelRouterSettings)
DECLARE_JSON_MAP(ChannelRouterPresetBankEntry);
};
class ChannelRouterPresetBank {
private:
int64_t nextId_ = 1;
int64_t version_ = 1;
std::vector<std::shared_ptr<ChannelRouterPresetBankEntry>> entries_;
public:
JSON_GETTER_SETTER(nextId)
JSON_GETTER_SETTER(version)
JSON_GETTER_SETTER_REF(entries)
std::vector<ChannelRouterPresetIndexEntry> getIndexEntries();
DECLARE_JSON_MAP(ChannelRouterPresetBank);
};
} }
+2
View File
@@ -149,6 +149,8 @@ void JackConfiguration::AlsaInitialize(
} }
} catch (const std::exception& /*ignore*/) } catch (const std::exception& /*ignore*/)
{ {
this->isValid_ = false;
this->errorStatus_ = "Not configured.";
throw; throw;
} }
+45 -136
View File
@@ -28,6 +28,7 @@
#include <filesystem> #include <filesystem>
#include "Lv2Log.hpp" #include "Lv2Log.hpp"
#include "SysExec.hpp" #include "SysExec.hpp"
#include "PiPedalAlsa.hpp"
#define DRC_FILENAME "/etc/jackdrc" #define DRC_FILENAME "/etc/jackdrc"
@@ -60,33 +61,41 @@ static std::vector<std::string> SplitArgs(const char *szBuff)
return result; return result;
} }
static std::string GetJackStringArg(const std::vector<std::string> &args, const std::string&shortOption, const std::string&longOption) static std::string GetJackStringArg(const std::vector<std::string> &args, const std::string &shortOption, const std::string &longOption)
{ {
std::string strVal; std::string strVal;
for (size_t i = 1; i < args.size(); ++i) for (size_t i = 1; i < args.size(); ++i)
{ {
auto pos = args[i].rfind(longOption,0); auto pos = args[i].rfind(longOption, 0);
if (pos != std::string::npos) { if (pos != std::string::npos)
{
if (args[i].length() == longOption.length()) if (args[i].length() == longOption.length())
{ {
if (i == args.size()-1) { if (i == args.size() - 1)
{
throw PiPedalException("Can't read Jack configuration."); throw PiPedalException("Can't read Jack configuration.");
} }
strVal = args[i+1]; strVal = args[i + 1];
} else { }
else
{
strVal = args[i].substr(longOption.length()); strVal = args[i].substr(longOption.length());
} }
break; break;
} }
pos = args[i].rfind(shortOption,0); pos = args[i].rfind(shortOption, 0);
if (pos != std::string::npos) { if (pos != std::string::npos)
{
if (args[i].length() == shortOption.length()) if (args[i].length() == shortOption.length())
{ {
if (i == args.size()-1) { if (i == args.size() - 1)
{
throw PiPedalException("Can't read Jack configuration."); throw PiPedalException("Can't read Jack configuration.");
} }
strVal = args[i+1]; strVal = args[i + 1];
} else { }
else
{
strVal = args[i].substr(shortOption.length()); strVal = args[i].substr(shortOption.length());
} }
break; break;
@@ -94,9 +103,9 @@ static std::string GetJackStringArg(const std::vector<std::string> &args, const
} }
return strVal; return strVal;
} }
static std::int32_t GetJackArg(const std::vector<std::string> &args, const std::string&shortOption, const std::string&longOption) static std::int32_t GetJackArg(const std::vector<std::string> &args, const std::string &shortOption, const std::string &longOption)
{ {
std::string strVal = GetJackStringArg(args,shortOption,longOption); std::string strVal = GetJackStringArg(args, shortOption, longOption);
try try
{ {
unsigned long value = std::stoul(strVal); unsigned long value = std::stoul(strVal);
@@ -108,144 +117,42 @@ static std::int32_t GetJackArg(const std::vector<std::string> &args, const std::
} }
} }
static std::string GetAlsaDeviceName(const std::vector<AlsaDeviceInfo> &availableDevices, const std::string &id)
void JackServerSettings::ReadJackDaemonConfiguration()
{ {
#if !JACK_HOST if (id.empty())
return; return "";
#endif std::string name;
this->valid_ = false; for (const auto &availableDevice : availableDevices)
std::string lastLine;
{ {
ifstream input(DRC_FILENAME); if (availableDevice.id_ == id)
if (!input.is_open())
{ {
return; name = availableDevice.name_;
}
while (true)
{
std::string line;
std::getline(input,line);
if (line.length() != 0) {
lastLine = line;
}
if (input.eof()) {
break; break;
} }
} }
try if (name.empty())
{ {
std::vector<std::string> argv = SplitArgs(lastLine.c_str()); auto pos = id.find(":");
for (auto i = argv.begin(); i != argv.end(); ++i) if (pos != std::string::npos)
{ {
if ((*i) == "-dalsa") { name = id.substr(pos + 1);
argv.erase(i);
break;
}
}
this->bufferSize_ = GetJackArg(argv, "-p", "--period");
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "--nperiods");
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "--rate");
// read new dual device flags, fallback on old -d/--device
std::string capDev = GetJackStringArg(argv, "-C", "--capture");
std::string playDev = GetJackStringArg(argv, "-P", "--playback");
std::string dev = "";
try { dev = GetJackStringArg(argv, "-d", "--device"); } catch(...) {}
this->alsaInputDevice_ = capDev.empty() ? dev : capDev;
this->alsaOutputDevice_ = playDev.empty() ? dev : playDev;
this->valid_ = true;
}
catch (std::exception &)
{
//Lv2Log::error("Can't parse " DRC_FILENAME);
} }
} }
return name;
} }
void JackServerSettings::FixUpDeviceNames()
void JackServerSettings::WriteDaemonConfig()
{ {
#if JACK_HOST if (
this->valid_ = false; ((!this->alsaInputDevice_.empty()) && this->alsaInputDeviceName_.empty()) ||
(!this->alsaOutputDevice_.empty()) && this->alsaOutputDeviceName_.empty())
{
std::vector<std::string> precedingLines; PiPedalAlsaDevices& alsaDevices = PiPedalAlsaDevices::instance();
std::vector<std::string> argv; std::vector<AlsaDeviceInfo> availableDevices = alsaDevices.GetAlsaDevices();
std::string lastLine;
if (std::filesystem::exists(DRC_FILENAME)) { this->alsaInputDeviceName_ = GetAlsaDeviceName(availableDevices, this->alsaInputDevice_);
ifstream input(DRC_FILENAME); this->alsaOutputDeviceName_ = GetAlsaDeviceName(availableDevices, this->alsaOutputDevice_);
if (!input.is_open())
{
return;
} }
while (true)
{
if (input.eof())
{
break;
}
std::getline(input,lastLine);
precedingLines.push_back(lastLine);
if (input.eof())
{
break;
}
}
// erase blank lines at the end.
while (precedingLines.size() != 0 && precedingLines[precedingLines.size()-1] == "")
{
precedingLines.erase(precedingLines.begin()+precedingLines.size()-1);
}
// erase the last line, which should contain the command invocation.
if (precedingLines.size() != 0)
{
precedingLines.erase(precedingLines.begin()+precedingLines.size()-1);
}
}
// write to the output.
try
{
ofstream output(DRC_FILENAME);
if (!output.is_open())
{
throw PiPedalException("Can't write " DRC_FILENAME);
}
if (precedingLines.size() == 0)
{
// jack1 incantation for promiscuous servers.
output << "#!/bin/sh" <<endl;
output << "export JACK_PROMISCUOUS_SERVER=audio" << endl;
output << "export JACK_NO_AUDIO_RESERVATION=1" << endl;
output << "umask 0" << endl;
}
for (auto line : precedingLines)
{
output << line << endl;
}
// the style used by qjackctl. :-/
// Lower to -P70 in order to allow the USB soft-irq to run at higher priority than JACK (it runs at 80).
output << "/usr/bin/jackd "
<< "-R -P70 --silent"
<< " -dalsa -d" << this->alsaDevice_
<< " -r" << this->sampleRate_
<< " -p" << this->bufferSize_
<< " -n" << this->numberOfBuffers_ << " -Xseq"
<< endl;
if (silentSysExec("/usr/bin/chmod 755 " DRC_FILENAME) != 0)
{
Lv2Log::error("Failed to set permissions on /etc/jackdrc");
}
}
catch (const std::exception &e)
{
}
#else
throw PiPedalStateException("JACK_HOST not enabled at compile time.");
#endif
} }
JSON_MAP_BEGIN(JackServerSettings) JSON_MAP_BEGIN(JackServerSettings)
@@ -256,6 +163,8 @@ JSON_MAP_REFERENCE(JackServerSettings, isJackAudio)
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice) // legacy field JSON_MAP_REFERENCE(JackServerSettings, alsaDevice) // legacy field
JSON_MAP_REFERENCE(JackServerSettings, alsaInputDevice) JSON_MAP_REFERENCE(JackServerSettings, alsaInputDevice)
JSON_MAP_REFERENCE(JackServerSettings, alsaOutputDevice) JSON_MAP_REFERENCE(JackServerSettings, alsaOutputDevice)
JSON_MAP_REFERENCE(JackServerSettings, alsaInputDeviceName)
JSON_MAP_REFERENCE(JackServerSettings, alsaOutputDeviceName)
JSON_MAP_REFERENCE(JackServerSettings, sampleRate) JSON_MAP_REFERENCE(JackServerSettings, sampleRate)
JSON_MAP_REFERENCE(JackServerSettings, bufferSize) JSON_MAP_REFERENCE(JackServerSettings, bufferSize)
JSON_MAP_REFERENCE(JackServerSettings, numberOfBuffers) JSON_MAP_REFERENCE(JackServerSettings, numberOfBuffers)
+11 -5
View File
@@ -33,7 +33,9 @@ namespace pipedal
bool isJackAudio_ = JACK_HOST ? true : false; bool isJackAudio_ = JACK_HOST ? true : false;
bool rebootRequired_ = false; bool rebootRequired_ = false;
std::string alsaInputDevice_; std::string alsaInputDevice_;
std::string alsaInputDeviceName_;
std::string alsaOutputDevice_; std::string alsaOutputDevice_;
std::string alsaOutputDeviceName_;
std::string alsaDevice_; // legacy std::string alsaDevice_; // legacy
uint64_t sampleRate_ = 0; uint64_t sampleRate_ = 0;
uint32_t bufferSize_ = 64; uint32_t bufferSize_ = 64;
@@ -44,6 +46,7 @@ namespace pipedal
JackServerSettings( JackServerSettings(
const std::string &alsaInputDevice, const std::string &alsaInputDevice,
const std::string &alsaOutputDevice, const std::string &alsaOutputDevice,
uint64_t sampleRate, uint64_t sampleRate,
uint32_t bufferSize, uint32_t bufferSize,
uint32_t numberOfBuffers) uint32_t numberOfBuffers)
@@ -55,6 +58,7 @@ namespace pipedal
numberOfBuffers_(numberOfBuffers), numberOfBuffers_(numberOfBuffers),
isOnboarding_(false) isOnboarding_(false)
{ {
FixUpDeviceNames();
} }
uint64_t GetSampleRate() const { return sampleRate_; } uint64_t GetSampleRate() const { return sampleRate_; }
@@ -62,11 +66,13 @@ namespace pipedal
uint32_t GetBufferSize() const { return bufferSize_; } uint32_t GetBufferSize() const { return bufferSize_; }
uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; } uint32_t GetNumberOfBuffers() const { return numberOfBuffers_; }
const std::string &GetAlsaInputDevice() const { return alsaInputDevice_; } const std::string &GetAlsaInputDevice() const { return alsaInputDevice_; }
const std::string &GetAlsaInputDeviceName() const { return alsaInputDeviceName_; }
const std::string &GetAlsaOutputDevice() const { return alsaOutputDevice_; } const std::string &GetAlsaOutputDevice() const { return alsaOutputDevice_; }
const std::string &GetAlsaOutputDeviceName() const { return alsaOutputDeviceName_; }
const std::string &GetLegacyAlsaDevice() const { return alsaDevice_; } //legacy const std::string &GetLegacyAlsaDevice() const { return alsaDevice_; } //legacy
void SetAlsaInputDevice(const std::string &d){ alsaInputDevice_ = d; } void SetAlsaInputDevice(const std::string &id, const std::string&name){ alsaInputDevice_ = id; alsaInputDeviceName_ = name; }
void SetAlsaOutputDevice(const std::string &d){ alsaOutputDevice_ = d; } void SetAlsaOutputDevice(const std::string &id, const std::string&name){ alsaOutputDevice_ = id; alsaOutputDeviceName_ = name; }
void SetLegacyAlsaDevice(const std::string &d) { alsaDevice_ = d; } void SetLegacyAlsaDevice(const std::string &d) { alsaDevice_ = d; }
void UseDummyAudioDevice() { void UseDummyAudioDevice() {
@@ -74,7 +80,9 @@ namespace pipedal
if (sampleRate_ == 0) sampleRate_ = 48000; if (sampleRate_ == 0) sampleRate_ = 48000;
this->alsaDevice_ = "dummy:channels_2"; this->alsaDevice_ = "dummy:channels_2";
this->alsaInputDevice_ = "dummy:channels_2"; this->alsaInputDevice_ = "dummy:channels_2";
this->alsaInputDeviceName_ = "Stopped";
this->alsaOutputDevice_ = "dummy:channels_2"; this->alsaOutputDevice_ = "dummy:channels_2";
this->alsaOutputDeviceName_ = "Stopped";
} }
bool IsDummyAudioDevice() const { bool IsDummyAudioDevice() const {
return return
@@ -83,11 +91,8 @@ namespace pipedal
|| this->alsaInputDevice_.starts_with("dummy:"); || this->alsaInputDevice_.starts_with("dummy:");
} }
void ReadJackDaemonConfiguration();
bool IsValid() const { return valid_; } bool IsValid() const { return valid_; }
void WriteDaemonConfig(); // requires root perms.
void SetRebootRequired(bool value) void SetRebootRequired(bool value)
{ {
rebootRequired_ = value; rebootRequired_ = value;
@@ -106,6 +111,7 @@ namespace pipedal
this->bufferSize_ == other.bufferSize_ && this->bufferSize_ == other.bufferSize_ &&
this->numberOfBuffers_ == other.numberOfBuffers_; this->numberOfBuffers_ == other.numberOfBuffers_;
} }
void FixUpDeviceNames();
DECLARE_JSON_MAP(JackServerSettings); DECLARE_JSON_MAP(JackServerSettings);
}; };
+2 -3
View File
@@ -103,8 +103,7 @@ void PrintHelp()
void ListDevices() void ListDevices()
{ {
PiPedalAlsaDevices alsaDevices; auto devices = PiPedalAlsaDevices::instance().GetAlsaDevices();
auto devices = alsaDevices.GetAlsaDevices();
PrettyPrinter pp; PrettyPrinter pp;
if (devices.size() == 0) if (devices.size() == 0)
@@ -199,7 +198,7 @@ public:
TestResult result; TestResult result;
try try
{ {
JackServerSettings serverSettings(inputDeviceId, outputDeviceId, sampleRate, bufferSize, buffers); JackServerSettings serverSettings(inputDeviceId,outputDeviceId, sampleRate, bufferSize, buffers);
JackConfiguration jackConfiguration; JackConfiguration jackConfiguration;
jackConfiguration.AlsaInitialize(serverSettings); jackConfiguration.AlsaInitialize(serverSettings);
+67 -3
View File
@@ -160,11 +160,14 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
std::vector<AlsaDeviceInfo> result; std::vector<AlsaDeviceInfo> result;
int cardNum = -1; // Start with first card
int err; int err;
std::vector<ProcAlsaDevice> procAlsaDevices = getProcAlsaDevices(); std::vector<ProcAlsaDevice> procAlsaDevices = getProcAlsaDevices();
snd_pcm_info_t *pcminfo = nullptr;
snd_pcm_info_alloca(&pcminfo);
for (const auto &procAlsaDevice: procAlsaDevices) for (const auto &procAlsaDevice: procAlsaDevices)
{ {
std::stringstream ss; std::stringstream ss;
@@ -198,13 +201,60 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
err = snd_ctl_card_info(hDevice, alsaInfo); err = snd_ctl_card_info(hDevice, alsaInfo);
if (err == 0) if (err == 0)
{ {
AlsaDeviceInfo info; AlsaDeviceInfo info;
info.cardId_ = cardNum; info.cardId_ = procAlsaDevice.cardId;
info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo); info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo);
const char *driver = snd_ctl_card_info_get_driver(alsaInfo); const char *driver = snd_ctl_card_info_get_driver(alsaInfo);
(void)driver; (void)driver;
info.name_ = snd_ctl_card_info_get_name(alsaInfo); snd_pcm_info_set_device(pcminfo, procAlsaDevice.subdeviceId);
snd_pcm_info_set_subdevice(pcminfo, 0);
snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK);
if ((err = snd_ctl_pcm_info(hDevice, pcminfo)) < 0) {
snd_pcm_info_set_device(pcminfo, procAlsaDevice.subdeviceId);
snd_pcm_info_set_subdevice(pcminfo, 0);
snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE);
if ((err = snd_ctl_pcm_info(hDevice, pcminfo)) < 0) {
if (err != -ENOENT)
Lv2Log::warning("control digital audio info (%i): %s", info.cardId_, snd_strerror(err));
continue;
}
}
/*
Names and IDs are f-ed up in ALSA. id seems to be vaguely reliable. names are garbage!
Sample data from Unbuntu 24.04.
snd_ctl_card_info_get_id(info) snd_ctl_card_info_get_name(alsaInfo) subdevice snd_pcm_info_get_id(pcminfo) snd_pcm_info_get_name(pcminfo)
Generic HD-Audio Generic 3 HDMI 0 Acer S231HL
Generic HD-Audio Generic 3 HDMI 0 ASUS MG28U
Generic_1 HD-Audio Generic 0 CX11970 Analog CX11970 Analog
M2 M2 USB Audio USB Audio
CODEC USB AUDIO CODEC USB Audio USB Audio
*/
std::string name;
{
// let's assume (without evidence) that names get localized, but ids do not. :-/
std::string cardId = snd_ctl_card_info_get_id(alsaInfo);
std::string cardName = snd_ctl_card_info_get_name(alsaInfo);
std::string pcmId = snd_pcm_info_get_id(pcminfo);
std::string pcmName = snd_pcm_info_get_name(pcminfo);
std::string driver = snd_ctl_card_info_get_driver(alsaInfo);
if (driver == "USB-Audio")
{
name = SS("USB - " << cardId);
}
else if (pcmId == pcmName)
{
name = pcmId;
} else {
name = SS(pcmId << '[' << pcmName << ']');
}
}
info.name_ = name;
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo); info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
// we can't read our own device if it's open so use data that gets // we can't read our own device if it's open so use data that gets
@@ -219,6 +269,8 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
result.push_back(cachedInfo); result.push_back(cachedInfo);
continue; continue;
} }
snd_pcm_t *captureDevice = nullptr; snd_pcm_t *captureDevice = nullptr;
snd_pcm_t *playbackDevice = nullptr; snd_pcm_t *playbackDevice = nullptr;
auto rc = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); auto rc = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
@@ -248,6 +300,7 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
if (captureOk || playbackOk) if (captureOk || playbackOk)
{ {
snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice; snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice;
snd_pcm_hw_params_t *params = nullptr; snd_pcm_hw_params_t *params = nullptr;
err = snd_pcm_hw_params_malloc(&params); err = snd_pcm_hw_params_malloc(&params);
@@ -637,6 +690,17 @@ AlsaMidiDeviceInfo::AlsaMidiDeviceInfo(const char *name, const char *description
this->subdevice_ = subdevice; this->subdevice_ = subdevice;
} }
std::unique_ptr<PiPedalAlsaDevices> PiPedalAlsaDevices::instance_;
PiPedalAlsaDevices&PiPedalAlsaDevices::instance() {
if (!instance_)
{
instance_ = std::unique_ptr<PiPedalAlsaDevices>(new PiPedalAlsaDevices());
}
return (*instance_);
}
JSON_MAP_BEGIN(AlsaDeviceInfo) JSON_MAP_BEGIN(AlsaDeviceInfo)
JSON_MAP_REFERENCE(AlsaDeviceInfo, cardId) JSON_MAP_REFERENCE(AlsaDeviceInfo, cardId)
JSON_MAP_REFERENCE(AlsaDeviceInfo, id) JSON_MAP_REFERENCE(AlsaDeviceInfo, id)
+5 -1
View File
@@ -71,12 +71,16 @@ namespace pipedal {
}; };
class PiPedalAlsaDevices { class PiPedalAlsaDevices {
private:
static std::unique_ptr<PiPedalAlsaDevices> instance_;
PiPedalAlsaDevices() { }
std::map<std::string,AlsaDeviceInfo> cachedDevices; std::map<std::string,AlsaDeviceInfo> cachedDevices;
bool getCachedDevice(const std::string&name, AlsaDeviceInfo*pResult); bool getCachedDevice(const std::string&name, AlsaDeviceInfo*pResult);
void cacheDevice(const std::string&name, const AlsaDeviceInfo&deviceInfo); void cacheDevice(const std::string&name, const AlsaDeviceInfo&deviceInfo);
public: public:
static PiPedalAlsaDevices&instance();
std::vector<AlsaDeviceInfo> GetAlsaDevices(); std::vector<AlsaDeviceInfo> GetAlsaDevices();
}; };
+1 -1
View File
@@ -34,7 +34,7 @@ using namespace std;
static void DiscoveryTest() static void DiscoveryTest()
{ {
cout << "--- Discovery" << endl; cout << "--- Discovery" << endl;
PiPedalAlsaDevices devices; PiPedalAlsaDevices &devices = PiPedalAlsaDevices::instance();
auto result = devices.GetAlsaDevices(); auto result = devices.GetAlsaDevices();
std::cout << result.size() << " ALSA devices found." << std::endl; std::cout << result.size() << " ALSA devices found." << std::endl;
+57 -53
View File
@@ -83,12 +83,9 @@ PiPedalModel::PiPedalModel()
this->updater->Start(); this->updater->Start();
this->currentUpdateStatus = updater->GetCurrentStatus(); this->currentUpdateStatus = updater->GetCurrentStatus();
this->pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard); this->pedalboard = Pedalboard::MakeDefault(Pedalboard::PedalboardType::MainPedalboard);
#if JACK_HOST
this->jackServerSettings = this->storage.GetJackServerSettings(); // to get onboarding flag.
this->jackServerSettings.ReadJackDaemonConfiguration();
#else
this->jackServerSettings = this->storage.GetJackServerSettings(); this->jackServerSettings = this->storage.GetJackServerSettings();
#endif
updater->SetUpdateListener( updater->SetUpdateListener(
[this](const UpdateStatus &updateStatus) [this](const UpdateStatus &updateStatus)
{ {
@@ -224,21 +221,22 @@ void PiPedalModel::Init(const PiPedalConfiguration &configuration)
this->systemMidiBindings = storage.GetSystemMidiBindings(); this->systemMidiBindings = storage.GetSystemMidiBindings();
#if JACK_HOST
this->jackConfiguration = this->jackConfiguration.JackInitialize();
#else
this->jackServerSettings = storage.GetJackServerSettings(); this->jackServerSettings = storage.GetJackServerSettings();
try
{
this->jackConfiguration.AlsaInitialize(jackServerSettings); this->jackConfiguration.AlsaInitialize(jackServerSettings);
#endif }
catch (const std::exception &)
{
//
}
this->channelRouterSettings = storage.GetChannelRouterSettings(); this->channelRouterSettings = storage.GetChannelRouterSettings();
pluginHost.OnConfigurationChanged( pluginHost.OnConfigurationChanged(
jackConfiguration, jackConfiguration,
storage.GetChannelSelection()); storage.GetChannelSelection());
} }
int64_t PiPedalModel::DownloadModelsFromTone3000( int64_t PiPedalModel::DownloadModelsFromTone3000(
int64_t clientId, int64_t clientId,
Tone3000DownloadType downloadType, Tone3000DownloadType downloadType,
@@ -271,14 +269,12 @@ int64_t PiPedalModel::DownloadModelsFromTone3000(
return tone3000Downloader->RequestDownload( return tone3000Downloader->RequestDownload(
downloadType, downloadType,
downloadPath, downloadPath,
tone3000Url tone3000Url);
);
} }
void PiPedalModel::CancelTone3000Download( void PiPedalModel::CancelTone3000Download(
int64_t clientId, int64_t clientId,
int64_t downloadHandle int64_t downloadHandle)
)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
@@ -307,12 +303,12 @@ void PiPedalModel::OnTone3000Progress(const Tone3000DownloadProgress &progress)
} }
} }
void PiPedalModel::OnTone3000DownloadComplete(int64_t handle, const std::string&resultPath) void PiPedalModel::OnTone3000DownloadComplete(int64_t handle, const std::string &resultPath)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
for (auto subscriber : this->subscribers) for (auto subscriber : this->subscribers)
{ {
subscriber->OnTone3000DownloadComplete(handle,resultPath); subscriber->OnTone3000DownloadComplete(handle, resultPath);
} }
} }
@@ -358,20 +354,21 @@ void PiPedalModel::LoadLv2PluginInfo()
{ {
PluginPresets pluginPresets = pluginHost.GetFactoryPluginPresets(plugin->uri()); PluginPresets pluginPresets = pluginHost.GetFactoryPluginPresets(plugin->uri());
storage.SavePluginPresets(plugin->uri(), pluginPresets); storage.SavePluginPresets(plugin->uri(), pluginPresets);
} else { }
else
{
if (pluginPresetIndexVersion == 0) if (pluginPresetIndexVersion == 0)
{ {
if (plugin->uri() == "http://two-play.com/plugins/toob-convolution-reverb" || plugin->uri() == "http://two-play.com/plugins/toob-convolution-reverb-stereo") if (plugin->uri() == "http://two-play.com/plugins/toob-convolution-reverb" || plugin->uri() == "http://two-play.com/plugins/toob-convolution-reverb-stereo")
{ {
// overwrite previous factory presets! // overwrite previous factory presets!
PluginPresets pluginPresets = pluginHost.GetFactoryPluginPresets(plugin->uri()); PluginPresets pluginPresets = pluginHost.GetFactoryPluginPresets(plugin->uri());
for (auto & pluginPreset: pluginPresets.presets_) for (auto &pluginPreset : pluginPresets.presets_)
{ {
storage.SavePluginPreset( storage.SavePluginPreset(
plugin->uri(), plugin->uri(),
pluginPreset); pluginPreset);
} }
} }
} }
} }
@@ -772,13 +769,17 @@ void PiPedalModel::SetPedalboardItemEnable(int64_t clientId, int64_t pedalItemId
std::lock_guard<std::recursive_mutex> guard{mutex}; std::lock_guard<std::recursive_mutex> guard{mutex};
{ {
this->pedalboard.SetItemEnabled(pedalItemId, enabled); this->pedalboard.SetItemEnabled(pedalItemId, enabled);
PedalboardItem * pPedalboardItem = this->pedalboard.GetItem(pedalItemId); PedalboardItem *pPedalboardItem = this->pedalboard.GetItem(pedalItemId);
if (pPedalboardItem) { if (pPedalboardItem)
{
Lv2PluginInfo::ptr pluginInfo = GetPluginInfo(pPedalboardItem->uri()); Lv2PluginInfo::ptr pluginInfo = GetPluginInfo(pPedalboardItem->uri());
if (pluginInfo) { if (pluginInfo)
for (auto &port: pluginInfo->ports()) { {
if (port->is_bypass()) { for (auto &port : pluginInfo->ports())
pPedalboardItem->SetControlValue(port->symbol(), enabled? 1.0: 0.0f); {
if (port->is_bypass())
{
pPedalboardItem->SetControlValue(port->symbol(), enabled ? 1.0 : 0.0f);
} }
} }
} }
@@ -999,7 +1000,7 @@ int64_t PiPedalModel::SavePluginPresetAs(int64_t instanceId, const std::string &
return presetId; return presetId;
} }
int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, int64_t bankInstanceId,const std::string &name, int64_t saveAfterInstanceId) int64_t PiPedalModel::SaveCurrentPresetAs(int64_t clientId, int64_t bankInstanceId, const std::string &name, int64_t saveAfterInstanceId)
{ {
std::lock_guard<std::recursive_mutex> guard{mutex}; std::lock_guard<std::recursive_mutex> guard{mutex};
@@ -1523,7 +1524,7 @@ void PiPedalModel::RestartAudio(bool useDummyAudioDriver)
throw std::runtime_error("Audio configuration not valid."); throw std::runtime_error("Audio configuration not valid.");
} }
const auto & channelSelection = this->storage.GetChannelSelection(); const auto &channelSelection = this->storage.GetChannelSelection();
this->audioHost->Open(jackServerSettings, channelSelection); this->audioHost->Open(jackServerSettings, channelSelection);
@@ -1625,7 +1626,6 @@ std::vector<AlsaSequencerPortSelection> PiPedalModel::GetAlsaSequencerPorts()
return result; return result;
} }
void PiPedalModel::FireChannelRouterSettingsChanged(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};
@@ -2129,8 +2129,7 @@ std::shared_ptr<Lv2PluginInfo> PiPedalModel::GetPluginInfo(const std::string &ur
return pluginHost.GetPluginInfo(uri); return pluginHost.GetPluginInfo(uri);
} }
void PiPedalModel::UpdateDefaults(SnapshotValue &snapshotValue, const PedalboardItem *pedalboardItem_)
void PiPedalModel::UpdateDefaults(SnapshotValue&snapshotValue, const PedalboardItem*pedalboardItem_)
{ {
std::shared_ptr<Lv2PluginInfo> pPlugin = pluginHost.GetPluginInfo(pedalboardItem_->uri()); std::shared_ptr<Lv2PluginInfo> pPlugin = pluginHost.GetPluginInfo(pedalboardItem_->uri());
if (!pPlugin) if (!pPlugin)
@@ -2145,10 +2144,10 @@ void PiPedalModel::UpdateDefaults(SnapshotValue&snapshotValue, const PedalboardI
// Fix incorrect bypass settings presint in Pipedal < 1.5.96 // Fix incorrect bypass settings presint in Pipedal < 1.5.96
for (size_t i = 0; i < pPlugin->ports().size(); ++i) for (size_t i = 0; i < pPlugin->ports().size(); ++i)
{ {
auto& port = pPlugin->ports()[i]; auto &port = pPlugin->ports()[i];
if (port->is_bypass() && port->is_control_port() && port->is_input()) if (port->is_bypass() && port->is_control_port() && port->is_input())
{ {
ControlValue* pValue = snapshotValue.GetControlValue(port->symbol()); ControlValue *pValue = snapshotValue.GetControlValue(port->symbol());
float value = snapshotValue.isEnabled_ ? 1.0f : 0.0f; float value = snapshotValue.isEnabled_ ? 1.0f : 0.0f;
if (pValue == nullptr) if (pValue == nullptr)
@@ -2166,7 +2165,8 @@ void PiPedalModel::UpdateDefaults(SnapshotValue&snapshotValue, const PedalboardI
if (pPlugin->uri() == "http://two-play.com/plugins/toob-nam") if (pPlugin->uri() == "http://two-play.com/plugins/toob-nam")
{ {
ControlValue *pVersion = snapshotValue.GetControlValue("version"); ControlValue *pVersion = snapshotValue.GetControlValue("version");
if (pVersion == nullptr) { if (pVersion == nullptr)
{
ControlValue *pValue = snapshotValue.GetControlValue("inputCalibrationMode"); ControlValue *pValue = snapshotValue.GetControlValue("inputCalibrationMode");
if (pValue == nullptr) if (pValue == nullptr)
{ {
@@ -2175,13 +2175,16 @@ void PiPedalModel::UpdateDefaults(SnapshotValue&snapshotValue, const PedalboardI
} }
// convert old gate threshold to new gate threshold. // convert old gate threshold to new gate threshold.
ControlValue *pGateValue = snapshotValue.GetControlValue("gate"); ControlValue *pGateValue = snapshotValue.GetControlValue("gate");
if (pGateValue) { if (pGateValue)
{
float value = pGateValue->value(); float value = pGateValue->value();
// Is the gate disabled? // Is the gate disabled?
if (value <= -100.0f) if (value <= -100.0f)
{ {
value = -120.0f; // "disabled in new range." value = -120.0f; // "disabled in new range."
} else { }
else
{
value = value * 0.5; // correct the bug in original implementation. value = value * 0.5; // correct the bug in original implementation.
} }
snapshotValue.SetControlValue("gate", value); snapshotValue.SetControlValue("gate", value);
@@ -2245,7 +2248,8 @@ void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered
if (pPlugin->uri() == "http://two-play.com/plugins/toob-nam") if (pPlugin->uri() == "http://two-play.com/plugins/toob-nam")
{ {
ControlValue *pVersion = pedalboardItem->GetControlValue("version"); ControlValue *pVersion = pedalboardItem->GetControlValue("version");
if (pVersion == nullptr) { if (pVersion == nullptr)
{
ControlValue *pValue = pedalboardItem->GetControlValue("inputCalibrationMode"); ControlValue *pValue = pedalboardItem->GetControlValue("inputCalibrationMode");
if (pValue == nullptr) if (pValue == nullptr)
{ {
@@ -2254,13 +2258,16 @@ void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered
} }
// convert old gate threshold to new gate threshold. // convert old gate threshold to new gate threshold.
ControlValue *pGateValue = pedalboardItem->GetControlValue("gate"); ControlValue *pGateValue = pedalboardItem->GetControlValue("gate");
if (pGateValue) { if (pGateValue)
{
float value = pGateValue->value(); float value = pGateValue->value();
// Is the gate disabled? // Is the gate disabled?
if (value <= -100.0f) if (value <= -100.0f)
{ {
value = -120.0f; // "disabled in new range." value = -120.0f; // "disabled in new range."
} else { }
else
{
value = value * 0.5; // correct the bug in original implementation. value = value * 0.5; // correct the bug in original implementation.
} }
pedalboardItem->SetControlValue("gate", value); pedalboardItem->SetControlValue("gate", value);
@@ -2278,18 +2285,20 @@ void PiPedalModel::UpdateDefaults(PedalboardItem *pedalboardItem, std::unordered
{ {
// retroactively correct bug in version of PiPedal prior to 1.5.96. // retroactively correct bug in version of PiPedal prior to 1.5.96.
ControlValue *pValue = pedalboardItem->GetControlValue(port->symbol()); ControlValue *pValue = pedalboardItem->GetControlValue(port->symbol());
float value = pedalboardItem->isEnabled() ? 1.0f: 0.0f; float value = pedalboardItem->isEnabled() ? 1.0f : 0.0f;
if (pValue == nullptr) if (pValue == nullptr)
{ {
pedalboardItem->controlValues().push_back( pedalboardItem->controlValues().push_back(
pipedal::ControlValue(port->symbol().c_str(), value)); pipedal::ControlValue(port->symbol().c_str(), value));
} else { }
else
{
pValue->value(value); pValue->value(value);
} }
}
else
} else { {
ControlValue *pValue = pedalboardItem->GetControlValue(port->symbol()); ControlValue *pValue = pedalboardItem->GetControlValue(port->symbol());
if (pValue == nullptr) if (pValue == nullptr)
{ {
@@ -2357,7 +2366,9 @@ void PiPedalModel::UpdateDefaults(Snapshot *snapshot, std::unordered_map<int64_t
// plugin is no longer present. Remove from the snapshot. // plugin is no longer present. Remove from the snapshot.
snapshot->values_.erase(snapshot->values_.begin() + i); snapshot->values_.erase(snapshot->values_.begin() + i);
--i; --i;
} else { }
else
{
UpdateDefaults(value, f->second); UpdateDefaults(value, f->second);
} }
} }
@@ -3376,7 +3387,6 @@ std::vector<PresetIndexEntry> PiPedalModel::RequestBankPresets(int64_t bankInsta
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
return storage.RequestBankPresets(bankInstanceId); return storage.RequestBankPresets(bankInstanceId);
} }
int64_t PiPedalModel::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets) int64_t PiPedalModel::ImportPresetsFromBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
@@ -3386,24 +3396,21 @@ int64_t PiPedalModel::ImportPresetsFromBank(int64_t bankInstanceId, const std::v
FirePresetsChanged(-1); FirePresetsChanged(-1);
return lastAdded; return lastAdded;
} }
int64_t PiPedalModel::CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets) int64_t PiPedalModel::CopyPresetsToBank(int64_t bankInstanceId, const std::vector<int64_t> &presets)
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
uint64_t lastAdded = storage.CopyPresetsToBank(bankInstanceId, presets); uint64_t lastAdded = storage.CopyPresetsToBank(bankInstanceId, presets);
return lastAdded; return lastAdded;
} }
ChannelRouterSettings::ptr PiPedalModel::GetChannelRouterSettings() ChannelRouterSettings::ptr PiPedalModel::GetChannelRouterSettings()
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
return this->channelRouterSettings; return this->channelRouterSettings;
} }
void PiPedalModel::SetChannelRouterSettings(int64_t clientId,ChannelRouterSettings::ptr &settings) void PiPedalModel::SetChannelRouterSettings(int64_t clientId, ChannelRouterSettings::ptr &settings)
{ {
{ {
std::lock_guard<std::recursive_mutex> lock(mutex); std::lock_guard<std::recursive_mutex> lock(mutex);
@@ -3415,11 +3422,8 @@ 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->FireChannelRouterSettingsChanged(clientId); this->FireChannelRouterSettingsChanged(clientId);
} }
std::string PiPedalModel::Tone3000ThumbnailDirectory() std::string PiPedalModel::Tone3000ThumbnailDirectory()
{ {
return "/var/pipedal/tone3000_thumbnails"; return "/var/pipedal/tone3000_thumbnails";
+1 -1
View File
@@ -152,7 +152,7 @@ namespace pipedal
std::unique_ptr<AvahiService> avahiService; std::unique_ptr<AvahiService> avahiService;
uint16_t webPort; uint16_t webPort;
PiPedalAlsaDevices alsaDevices; PiPedalAlsaDevices &alsaDevices = PiPedalAlsaDevices::instance();
std::recursive_mutex mutex; std::recursive_mutex mutex;
AdminClient adminClient; AdminClient adminClient;
+916 -735
View File
File diff suppressed because it is too large Load Diff
+24 -4
View File
@@ -45,11 +45,19 @@ namespace pipedal
uint8_t cc2_; uint8_t cc2_;
}; };
struct Lv2RoutingInserts {
Lv2Pedalboard*mainInserts = nullptr;
Lv2Pedalboard*auxInserts = nullptr;;
};
enum class RingBufferCommand : int64_t enum class RingBufferCommand : int64_t
{ {
Invalid = 0, Invalid = 0,
ReplaceEffect, ReplaceEffect,
EffectReplaced, EffectReplaced,
ReplaceRoutingInserts,
RoutingInsertsReplaced,
SetValue, SetValue,
SetBypass, SetBypass,
// AudioStopped, // AudioStopped,
@@ -539,16 +547,28 @@ namespace pipedal
write(RingBufferCommand::ReplaceEffect, pedalboard); write(RingBufferCommand::ReplaceEffect, pedalboard);
} }
void EffectReplaced(Lv2Pedalboard *pedalboard)
{
write(RingBufferCommand::EffectReplaced, pedalboard);
}
void ReplaceRoutingInserts(Lv2RoutingInserts routingInserts)
{
write(RingBufferCommand::ReplaceRoutingInserts, routingInserts);
}
void RoutingInsertsReplaced(Lv2RoutingInserts routingInserts)
{
write(RingBufferCommand::ReplaceRoutingInserts, routingInserts);
}
void AudioTerminatedAbnormally() void AudioTerminatedAbnormally()
{ {
AudioStoppedBody body; AudioStoppedBody body;
write(RingBufferCommand::AudioTerminatedAbnormally, body); write(RingBufferCommand::AudioTerminatedAbnormally, body);
} }
void EffectReplaced(Lv2Pedalboard *pedalboard)
{
write(RingBufferCommand::EffectReplaced, pedalboard);
}
void FreeSnapshot(IndexedSnapshot *snapshot) void FreeSnapshot(IndexedSnapshot *snapshot)
{ {
+4 -8
View File
@@ -1931,14 +1931,13 @@ pipedal::JackServerSettings Storage::GetJackServerSettings()
result.GetAlsaInputDevice().empty() && result.GetAlsaInputDevice().empty() &&
result.GetAlsaOutputDevice().empty()) result.GetAlsaOutputDevice().empty())
{ {
result.SetAlsaInputDevice(result.GetLegacyAlsaDevice()); std::string legacyDeviceId = result.GetLegacyAlsaDevice();
result.SetAlsaOutputDevice(result.GetLegacyAlsaDevice()); result.SetAlsaInputDevice(legacyDeviceId, "");
result.SetAlsaOutputDevice(legacyDeviceId, "");
result.SetLegacyAlsaDevice(""); result.SetLegacyAlsaDevice("");
} }
result.FixUpDeviceNames();
} }
#if JACK_HOST
result.Initialize();
#endif
return result; return result;
} }
@@ -1952,9 +1951,6 @@ void Storage::SetJackServerSettings(const pipedal::JackServerSettings &jackConfi
json_writer writer(f, false); json_writer writer(f, false);
writer.write(jackConfiguration); writer.write(jackConfiguration);
} }
#if JACK_HOST
jackConfiguration.Write();
#endif
} }
void Storage::SetSystemMidiBindings(const std::vector<MidiBinding> &bindings) void Storage::SetSystemMidiBindings(const std::vector<MidiBinding> &bindings)
+1
View File
@@ -206,6 +206,7 @@ static void print_device_info(snd_ctl_t* handle, int card, int device) {
// Print basic info // Print basic info
std::cout << "\n Subdevice: " << name << " - " << device_name << std::endl; std::cout << "\n Subdevice: " << name << " - " << device_name << std::endl;
std::cout << " ID: " << snd_pcm_info_get_id(pcminfo) << std::endl; std::cout << " ID: " << snd_pcm_info_get_id(pcminfo) << std::endl;
std::cout << " Name: " << snd_pcm_info_get_name(pcminfo) << std::endl;
std::cout << " Capabilities:"; std::cout << " Capabilities:";
if (has_playback) std::cout << " PLAYBACK"; if (has_playback) std::cout << " PLAYBACK";
if (has_capture) std::cout << " CAPTURE"; if (has_capture) std::cout << " CAPTURE";
+8 -5
View File
@@ -1,20 +1,23 @@
A way to force input to mono? summaries for Mono audio adapaters.
Cab I/Rs: gear="ir" Check PWA on windows, edge and chrome.
Make sure Pi 4 default audio device is still hidden when selecting devices!
A final solution for rate-limiting on Tone3000.
A way to force input to mono?
// yyy: only if the property changed!.
Tooltip on File Property Select: add to release notes. Tooltip on File Property Select: add to release notes.
sAFEfILEnAMES: RENAME, &C. Safe bank names? sAFEfILEnAMES: RENAME, &C. Safe bank names?
NEW BANK NEW BANK
WINDOW SIZE CALCUALTIONS ARE WRONG ON LINUX HIDEF DISPLAYS. WINDOW SIZE CALCUALTIONS ARE WRONG ON LINUX HIDEF DISPLAYS.
ls
Carla Project icons. Carla Project icons.
check reload after change of LV2 plugins. check reload after change of LV2 plugins.
Exactly one underrun per seek in Toob Player Exactly one underrun per seek in Toob Player
json "skip" code doesn't work with complex objects (MidiChannelBinding specifically).
json nan/inf is fatal. json nan/inf is fatal.
- pipewire aux in? - pipewire aux in?
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 27 KiB

+16 -1
View File
@@ -1,6 +1,14 @@
{ {
"manifest_version": 3,
"short_name": "PiPedal", "short_name": "PiPedal",
"name": "PiPedal", "name": "PiPedal",
"author": "Robin E.R. Davies",
"description": "A web-based client for the PiPedal guitar effects processor.",
"developer": {
"name": "Robin E.R. Davies",
"url": "https://rerdavies.github.io"
},
"homepage_url": "https://rerdavies.github.io/pipedal",
"icons": [ "icons": [
{ {
"src": "favicon.ico", "src": "favicon.ico",
@@ -20,5 +28,12 @@
], ],
"start_url": ".", "start_url": ".",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#ffffff" "display": "standalone",
"display_override": [
"fullscreen",
"standalone",
"minimal-ui",
"browser"
],
"background_color": "#000000"
} }
+1
View File
@@ -50,3 +50,4 @@ input[type=number] {
img { img {
user-select: none; user-select: none;
} }
+51 -10
View File
@@ -3,24 +3,34 @@ import { Pedalboard, ControlValue } from "./Pedalboard.tsx";
import JackConfiguration from "./Jack.tsx"; import JackConfiguration from "./Jack.tsx";
export const NONE_CHANNEL: number = -1;
// valid for Aux channel input only.
export const MAIN_OUT_LEFT_CHANNEL: number = -2;
// valid for Aux channel input only.
export const MAIN_OUT_RIGHT_CHANNEL: number = -3;
function isActiveChannel(channels: number[]): boolean { function isActiveChannel(channels: number[]): boolean {
if (channels.length < 2) return false; if (channels.length < 2) return false;
return channels[1] >= 0 || channels[0] >= 0; return channels[1] !== -1 || channels[0] !== -1;
} }
function chName(ch: number): string { function chName(ch: number): string {
if (ch === -1) { if (ch === -1) {
return "None"; return "None";
} }
return "Ch " + (ch + 1); return "Ch" + (ch + 1);
} }
function channelPairName(channels: number[], maxChannels: number): string { function channelPairName(channels: number[], maxChannels: number, input: boolean): string {
if (channels.length !== 2) { if (channels.length !== 2) {
return "Invalid"; return "Invalid";
} }
if (channels[0] === -1 && channels[1] === -1) { if (channels[0] === -1 && channels[1] === -1) {
return "None"; return "None";
} }
if (maxChannels === 1) {
return input ? "Input": "Output";
}
if (maxChannels === 2) { if (maxChannels === 2) {
if (channels[0] === 0 && channels[1] === 1) { if (channels[0] === 0 && channels[1] === 1) {
@@ -36,13 +46,28 @@ function channelPairName(channels: number[], maxChannels: number): string {
return "Right,Left"; return "Right,Left";
} }
} }
return chName(channels[0]) + "," + chName(channels[1]); if (channels[0] === channels[1]) {
return chName(channels[0]);
}
return '[' + chName(channels[0]) + " " + chName(channels[1]) + "]";
} }
function arrayEquals(a: number[], b: number[]): boolean {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
export default class ChannelRouterSettings { export default class ChannelRouterSettings {
configured: boolean = false; configured: boolean = false;
modified: boolean = false;
channelRouterPresetId: number = -1; channelRouterPresetId: number = -1;
mainInputChannels: number[] = [1, 1]; mainInputChannels: number[] = [1, 1];
@@ -61,6 +86,7 @@ export default class ChannelRouterSettings {
deserialize(obj: any): ChannelRouterSettings { deserialize(obj: any): ChannelRouterSettings {
this.configured = obj.configured; this.configured = obj.configured;
this.modified = obj.modified;
this.channelRouterPresetId = obj.channelRouterPresetId ? obj.channelRouterPresetId : -1; this.channelRouterPresetId = obj.channelRouterPresetId ? obj.channelRouterPresetId : -1;
this.mainInputChannels = obj.mainInputChannels.slice(); this.mainInputChannels = obj.mainInputChannels.slice();
this.mainOutputChannels = obj.mainOutputChannels.slice(); this.mainOutputChannels = obj.mainOutputChannels.slice();
@@ -73,6 +99,9 @@ export default class ChannelRouterSettings {
this.controlValues = ControlValue.deserializeArray(obj.controlValues); this.controlValues = ControlValue.deserializeArray(obj.controlValues);
return this; return this;
} }
clone() : ChannelRouterSettings {
return new ChannelRouterSettings().deserialize(this);
}
getControlValue(symbol: string): number | null { getControlValue(symbol: string): number | null {
for (let control of this.controlValues) { for (let control of this.controlValues) {
if (control.key === symbol) { if (control.key === symbol) {
@@ -111,16 +140,21 @@ export default class ChannelRouterSettings {
} }
let nInputs = jackConfiguration.inputAudioPorts.length; let nInputs = jackConfiguration.inputAudioPorts.length;
let nOutputs = jackConfiguration.outputAudioPorts.length; let nOutputs = jackConfiguration.outputAudioPorts.length;
let description = channelPairName(this.mainInputChannels, nInputs) + " -> " + let description = channelPairName(this.mainInputChannels, nInputs,true) + " -> " +
channelPairName(this.mainOutputChannels, nOutputs); channelPairName(this.mainOutputChannels, nOutputs,false);
if (isActiveChannel(this.auxInputChannels) && isActiveChannel(this.auxOutputChannels)) if (isActiveChannel(this.auxInputChannels) && isActiveChannel(this.auxOutputChannels))
{ {
description += " " + "+ re-amp: " + channelPairName(this.auxInputChannels, nInputs) + " -> " + if (arrayEquals(this.mainInputChannels, this.auxInputChannels))
channelPairName(this.auxOutputChannels, nOutputs); {
description += ", re-amp -> " + channelPairName(this.auxOutputChannels,nOutputs, false);
} else {
description += " " + ", aux: " + channelPairName(this.auxInputChannels, nInputs,true) + " -> " +
channelPairName(this.auxOutputChannels, nOutputs,false);
}
} }
if (isActiveChannel(this.sendInputChannels) && isActiveChannel(this.sendOutputChannels)) { if (isActiveChannel(this.sendInputChannels) && isActiveChannel(this.sendOutputChannels)) {
description += " " + "+ send: " +channelPairName(this.sendOutputChannels, nOutputs) description += " " + ", send: " +channelPairName(this.sendOutputChannels, nOutputs, false)
+ " -> " + channelPairName(this.sendInputChannels, nInputs); + " -> " + channelPairName(this.sendInputChannels, nInputs,true);
} }
return description; return description;
} }
@@ -128,6 +162,7 @@ export default class ChannelRouterSettings {
return jackConfiguration.isValid; return jackConfiguration.isValid;
} }
isValid(jackConfiguration: JackConfiguration): boolean { isValid(jackConfiguration: JackConfiguration): boolean {
if (!this.configured) { if (!this.configured) {
return false; return false;
@@ -176,5 +211,11 @@ export default class ChannelRouterSettings {
} }
return true; return true;
} }
isAuxActive() : boolean {
return isActiveChannel(this.auxInputChannels) && isActiveChannel(this.auxOutputChannels);
}
isSendActive() : boolean{
return isActiveChannel(this.sendInputChannels) && isActiveChannel(this.sendOutputChannels);
}
} }
+294 -110
View File
@@ -31,20 +31,22 @@ import MenuItem from '@mui/material/MenuItem';
import ChannelRouterSettingsHelpDialog from './ChannelRouterSettingsHelpDialog'; import ChannelRouterSettingsHelpDialog from './ChannelRouterSettingsHelpDialog';
import SpeakerIcon from '@mui/icons-material/Speaker'; import SpeakerIcon from '@mui/icons-material/Speaker';
import MicIcon from '@mui/icons-material/Mic'; import MicIcon from '@mui/icons-material/Mic';
import SaveIconOutline from '@mui/icons-material/Save';
import DialogTitle from '@mui/material/DialogTitle'; import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent'; import DialogContent from '@mui/material/DialogContent';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import ChannelRouterSettings from './ChannelRouterSettings'; import ChannelRouterSettings, { MAIN_OUT_LEFT_CHANNEL, MAIN_OUT_RIGHT_CHANNEL } from './ChannelRouterSettings';
import { Pedalboard, ControlValue } from './Pedalboard'; import { Pedalboard } from './Pedalboard';
import DialogEx from './DialogEx'; import DialogEx from './DialogEx';
import { PiPedalModelFactory } from './PiPedalModel'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
import { JackConfiguration } from './Jack';
let debugInputChannels: number | null = 2; let debugInputChannels: number | null = 1;
let debugOutputChannels: number | null = 4; let debugOutputChannels: number | null = 1;
export interface ChannelRouterSettingsDialogProps { export interface ChannelRouterSettingsDialogProps {
@@ -65,27 +67,31 @@ interface ChannelRouterSettingsPreset {
} }
let channelRouterPresets: ChannelRouterSettingsPreset[] = [ let DEFAULT_MONO_TO_MONO_ROUTING_PRESET = -2; // corresponds to Input->Output.
{ let DEFAULT_LEFT_TO_STEREO_ROUTING_PRESET = -3; // corresponds to Input->Stereo out.
id: -2, name: "Left -> Left", settings: new ChannelRouterSettings().deserialize({ let DEFAULT_RIGHT_TO_STEREO_ROUTING_PRESET = -4; // corresponds to Right->Stereo out.
let factoryRouterPresets: ChannelRouterSettings[] = [
// Input->Output
new ChannelRouterSettings().deserialize({
configured: true, configured: true,
channelRouterPresetId: -2, channelRouterPresetId: -2,
mainInputChannels: [0, 0], mainInputChannels: [0, -1],
mainOutputChannels: [0, 0], mainOutputChannels: [0, -1],
mainInserts: new Pedalboard(), mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1], auxInputChannels: [-1, -1],
auxOutputChannels: [-1, -1], auxOutputChannels: [-1, -1],
auxInserts: new Pedalboard(), auxInserts: new Pedalboard(),
sendInputChannels: [-1, -1], sendInputChannels: [-1, -1],
sendOutputChannels: [-1, -1], sendOutputChannels: [-1, -1],
controlValues: [], controlValues: []
}) })
}, ,
{ // Input->Stereo out.
id: -3, name: "Left -> Stereo", settings: new ChannelRouterSettings().deserialize({ new ChannelRouterSettings().deserialize({
configured: true, configured: true,
channelRouterPresetId: -3, channelRouterPresetId: -3,
mainInputChannels: [0, 0], mainInputChannels: [0, -1],
mainOutputChannels: [0, 1], mainOutputChannels: [0, 1],
mainInserts: new Pedalboard(), mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1], auxInputChannels: [-1, -1],
@@ -93,14 +99,14 @@ let channelRouterPresets: ChannelRouterSettingsPreset[] = [
auxInserts: new Pedalboard(), auxInserts: new Pedalboard(),
sendInputChannels: [-1, -1], sendInputChannels: [-1, -1],
sendOutputChannels: [-1, -1], sendOutputChannels: [-1, -1],
controlValues: [], controlValues: []
}) })
}, ,
{ // Right->Stereo out.
id: -4, name: "Right -> Stereo", settings: new ChannelRouterSettings().deserialize({ new ChannelRouterSettings().deserialize({
configured: true, configured: true,
channelRouterPresetId: -4, channelRouterPresetId: -4,
mainInputChannels: [1, 1], mainInputChannels: [1,-1],
mainOutputChannels: [0, 1], mainOutputChannels: [0, 1],
mainInserts: new Pedalboard(), mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1], auxInputChannels: [-1, -1],
@@ -110,84 +116,119 @@ let channelRouterPresets: ChannelRouterSettingsPreset[] = [
sendOutputChannels: [-1, -1], sendOutputChannels: [-1, -1],
controlValues: [], controlValues: [],
}) })
}, ,
{ new ChannelRouterSettings().deserialize({
id: -5, name: "Right -> Left + re-amp", settings: new ChannelRouterSettings().deserialize({
configured: true, configured: true,
channelRouterPresetId: -5, channelRouterPresetId: -5,
mainInputChannels: [1, 1], mainInputChannels: [1,-1],
mainOutputChannels: [0, 0], mainOutputChannels: [0, -1],
mainInserts: new Pedalboard(), mainInserts: new Pedalboard(),
auxInputChannels: [1, 1], auxInputChannels: [1,-1],
auxOutputChannels: [1, 1], auxOutputChannels: [1,-1],
auxInserts: new Pedalboard(),
sendInputChannels: [-1, - 1],
sendOutputChannels: [-1, -1],
controlValues: [],
})
},
{
id: -6, name: "Left -> Left + send", settings: new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -6,
mainInputChannels: [0, 0],
mainOutputChannels: [0, 0],
mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1],
auxOutputChannels: [-1, - 1],
auxInserts: new Pedalboard(),
sendInputChannels: [1, 1],
sendOutputChannels: [1, 1],
controlValues: [],
})
},
{
id: -7, name: "Right -> Right + send", settings: new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -7,
mainInputChannels: [1, 1],
mainOutputChannels: [1, 1],
mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1],
auxOutputChannels: [-1, - 1],
auxInserts: new Pedalboard(),
sendInputChannels: [0, 0],
sendOutputChannels: [0, 0],
controlValues: [],
})
},
{
id: -8, name: "Left -> Stereo + re-amp", settings: new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -8,
mainInputChannels: [0, 0],
mainOutputChannels: [0, 1],
mainInserts: new Pedalboard(),
auxInputChannels: [0, 0],
auxOutputChannels: [2, 2],
auxInserts: new Pedalboard(), auxInserts: new Pedalboard(),
sendInputChannels: [-1, -1], sendInputChannels: [-1, -1],
sendOutputChannels: [-1, -1], sendOutputChannels: [-1, -1],
controlValues: [], controlValues: [],
}) })
}, ,
{ new ChannelRouterSettings().deserialize({
id: -9, name: "Left -> Stereo + stereo send", settings: new ChannelRouterSettings().deserialize({ configured: true,
channelRouterPresetId: -6,
mainInputChannels: [1,-1],
mainOutputChannels: [1,-1],
mainInserts: new Pedalboard(),
auxInputChannels: [0, -1],
auxOutputChannels: [0, -1],
auxInserts: new Pedalboard(),
sendInputChannels: [-1, -1],
sendOutputChannels: [-1, -1],
controlValues: [],
})
,
new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -7,
mainInputChannels: [1,-1],
mainOutputChannels: [0, -1],
mainInserts: new Pedalboard(),
auxInputChannels: [0, -1],
auxOutputChannels: [1,-1],
auxInserts: new Pedalboard(),
sendInputChannels: [-1, -1],
sendOutputChannels: [-1, -1],
controlValues: []
})
,
new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -8,
mainInputChannels: [0, -1],
mainOutputChannels: [0, -1],
mainInserts: new Pedalboard(),
auxInputChannels: [1,-1],
auxOutputChannels: [1,-1],
auxInserts: new Pedalboard(),
sendInputChannels: [-1, -1],
sendOutputChannels: [-1, -1],
controlValues: []
})
,
new ChannelRouterSettings().deserialize({
configured: true, configured: true,
channelRouterPresetId: -9, channelRouterPresetId: -9,
mainInputChannels: [0, 0], mainInputChannels: [0, -1],
mainOutputChannels: [0, -1],
mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1],
auxOutputChannels: [-1, -1],
auxInserts: new Pedalboard(),
sendInputChannels: [1,-1],
sendOutputChannels: [1,-1],
controlValues: []
})
,
new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -10,
mainInputChannels: [1,-1],
mainOutputChannels: [1,-1],
mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1],
auxOutputChannels: [-1, -1],
auxInserts: new Pedalboard(),
sendInputChannels: [0, -1],
sendOutputChannels: [0, -1],
controlValues: []
})
,
new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -11,
mainInputChannels: [0, -1],
mainOutputChannels: [0, 1],
mainInserts: new Pedalboard(),
auxInputChannels: [0, -1],
auxOutputChannels: [2, -1],
auxInserts: new Pedalboard(),
sendInputChannels: [-1, -1],
sendOutputChannels: [-1, -1],
controlValues: []
})
,
new ChannelRouterSettings().deserialize({
configured: true,
channelRouterPresetId: -12,
mainInputChannels: [0, -1],
mainOutputChannels: [0, 1], mainOutputChannels: [0, 1],
mainInserts: new Pedalboard(), mainInserts: new Pedalboard(),
auxInputChannels: [-1, -1], auxInputChannels: [-1, -1],
auxOutputChannels: [-1, - 1], auxOutputChannels: [-1, -1],
auxInserts: new Pedalboard(), auxInserts: new Pedalboard(),
sendInputChannels: [2, 3], sendInputChannels: [2, 3],
sendOutputChannels: [2, 3], sendOutputChannels: [2, 3],
controlValues: [], controlValues: []
}) })
}, ,
]; ];
let cellPortraitInOutDiv: React.CSSProperties = { let cellPortraitInOutDiv: React.CSSProperties = {
@@ -248,41 +289,132 @@ let cellPortraitInOut: React.CSSProperties = {
verticalAlign: "middle" verticalAlign: "middle"
}; };
function MakeChannelMenu(channelCount: number): React.ReactElement[] {
function normalizeChannelSelection(channelSelection: number[]) {
if (channelSelection.length === 2) {
if (channelSelection[0] === -1) {
channelSelection[0] = channelSelection[1];
channelSelection[1] = -1;
} else {
if (channelSelection[0] === channelSelection[1]) {
channelSelection[1] = -1;
}
}
}
}
function makeRoutingPresets(audioConfig: JackConfiguration): ChannelRouterSettingsPreset[] {
let result: ChannelRouterSettingsPreset[] = [];
for (let preset of factoryRouterPresets) {
if (preset.isValid(audioConfig)) {
normalizeChannelSelection(preset.mainInputChannels);
normalizeChannelSelection(preset.mainOutputChannels);
normalizeChannelSelection(preset.auxInputChannels);
normalizeChannelSelection(preset.auxOutputChannels);
normalizeChannelSelection(preset.sendInputChannels);
normalizeChannelSelection(preset.sendOutputChannels);
let newPreset = {
id: preset.channelRouterPresetId,
name: preset.getDescription(audioConfig),
settings: preset
};
result.push(newPreset);
}
}
return result;
}
function MakeChannelMenu(channelCount: number, routeType: RouteType, input: boolean, disallowedSelections: number[]): React.ReactElement[] {
let items: React.ReactElement[] = [(<MenuItem key={-1} value={-1}>None</MenuItem>)] let items: React.ReactElement[] = [(<MenuItem key={-1} value={-1}>None</MenuItem>)]
if (channelCount === 0) { if (channelCount === 0) {
return items; return items;
} }
if (channelCount === 2) { if (channelCount === 1) {
items.push((<MenuItem key={0} value={0}>Left</MenuItem>)); if (input) {
items.push((<MenuItem key={1} value={1}>Right</MenuItem>)); items.push((<MenuItem key={0} value={0} disabled={disallowedSelections.includes(0)}>Input</MenuItem>));
return items; } else {
items.push((<MenuItem key={0} value={0} disabled={disallowedSelections.includes(0)}>Output</MenuItem>));
} }
} else if (channelCount === 2) {
items.push((<MenuItem key={0} value={0} disabled={disallowedSelections.includes(0)}>Left</MenuItem>));
items.push((<MenuItem key={1} value={1} disabled={disallowedSelections.includes(1)}>Right</MenuItem>));
return items;
} else {
for (let i = 0; i < channelCount; ++i) { for (let i = 0; i < channelCount; ++i) {
items.push((<MenuItem key={i} value={i}>Ch {i + 1}</MenuItem>)); items.push((<MenuItem key={i} value={i} disabled={disallowedSelections.includes(i)}>Ch {i + 1}</MenuItem>));
}
}
if (routeType === RouteType.Aux && input) {
items.push((<MenuItem key={MAIN_OUT_LEFT_CHANNEL} value={MAIN_OUT_LEFT_CHANNEL} >Main Out L</MenuItem>));
items.push((<MenuItem key={MAIN_OUT_RIGHT_CHANNEL} value={MAIN_OUT_RIGHT_CHANNEL} >Main Out R</MenuItem>));
} }
return items; return items;
} }
function makeDebugConfig(baseConfig: JackConfiguration): JackConfiguration {
let config = new JackConfiguration().deserialize(baseConfig); // clone.
for (let i = config.inputAudioPorts.length; i < (debugInputChannels ?? 0); ++i) {
config.inputAudioPorts.push("ch" + (i + 1));
}
if (config.inputAudioPorts.length > (debugInputChannels ?? 0)) {
config.inputAudioPorts = config.inputAudioPorts.slice(0, debugInputChannels ?? 0);
}
for (let i = config.outputAudioPorts.length; i < (debugOutputChannels ?? 0); ++i) {
config.outputAudioPorts.push("ch" + (i + 1));
}
if (config.outputAudioPorts.length > (debugOutputChannels ?? 0)) {
config.outputAudioPorts = config.outputAudioPorts.slice(0, debugOutputChannels ?? 0);
}
return config;
}
function getChannelRouterSettingsPresetById(presetId: number): ChannelRouterSettings {
for (let preset of factoryRouterPresets) {
if (preset.channelRouterPresetId === presetId) {
return preset.clone();
}
}
throw new Error("Error setting defaults. Preset not found: " + presetId);
}
function GetDefaultChannelRouterSettings(model: PiPedalModel) : ChannelRouterSettings {
let currentSettings = model.channelRouterSettings.get();
if (currentSettings.configured) {
return currentSettings;
}
// we're onboarding the user, so set an appropriate default channel routing preset based on the number of ports on the audio interface.
let jackSettings = model.jackConfiguration.get();
let numInputs = jackSettings.inputAudioPorts.length;
let numOutputs = jackSettings.outputAudioPorts.length;
if (numInputs < 2 || numOutputs < 2) {
currentSettings = getChannelRouterSettingsPresetById(DEFAULT_MONO_TO_MONO_ROUTING_PRESET);
} else if (numInputs === 2 && numOutputs === 2) {
currentSettings = getChannelRouterSettingsPresetById(DEFAULT_RIGHT_TO_STEREO_ROUTING_PRESET);
} else {
currentSettings = getChannelRouterSettingsPresetById(DEFAULT_LEFT_TO_STEREO_ROUTING_PRESET);
}
model.setChannelRouterSettings(currentSettings);
return model.channelRouterSettings.get();
}
function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) { function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
//const classes = useStyles(); //const classes = useStyles();
const { open, onClose } = props; const { open, onClose } = props;
let model = PiPedalModelFactory.getInstance(); let model = PiPedalModelFactory.getInstance();
let config = model.jackConfiguration.get(); let config = model.jackConfiguration.get();
if (debugInputChannels != null || debugOutputChannels != null) {
config = makeDebugConfig(config);
}
let channelRouterPresets = makeRoutingPresets(config);
const [settings, setSettings] = useState<ChannelRouterSettings>(GetDefaultChannelRouterSettings(model));
const [settings, setSettings] = useState<ChannelRouterSettings>(model.channelRouterSettings.get());
const [controlValues, setControlValues] = useState<ControlValue[]>(model.channelRouterControlValues.get());
const [showHelp, setShowHelp] = useState<boolean>(false); const [showHelp, setShowHelp] = useState<boolean>(false);
if (settings) {
}
if (controlValues) {
}
React.useEffect(() => { React.useEffect(() => {
if (open) { if (open) {
@@ -290,13 +422,8 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
setSettings(model.channelRouterSettings.get()); setSettings(model.channelRouterSettings.get());
}; };
model.channelRouterSettings.addOnChangedHandler(handleSettingsChanged); model.channelRouterSettings.addOnChangedHandler(handleSettingsChanged);
let handleControlsChanged = () => {
setControlValues(model.channelRouterControlValues.get());
};
model.channelRouterControlValues.addOnChangedHandler(handleControlsChanged);
return () => { return () => {
model.channelRouterSettings.removeOnChangedHandler(handleSettingsChanged); model.channelRouterSettings.removeOnChangedHandler(handleSettingsChanged);
model.channelRouterControlValues.removeOnChangedHandler(handleControlsChanged);
} }
} else { } else {
return () => { }; return () => { };
@@ -321,28 +448,50 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
let value: number; let value: number;
let disallowedSelections: number[] = [];
let channelSelection: number[] = [];
switch (routeType) { switch (routeType) {
case RouteType.Main: case RouteType.Main:
value = input ? settings.mainInputChannels[channelIndex] : settings.mainOutputChannels[channelIndex]; channelSelection = input ? settings.mainInputChannels : settings.mainOutputChannels;
break; break;
case RouteType.Aux: case RouteType.Aux:
value = input ? settings.auxInputChannels[channelIndex] : settings.auxOutputChannels[channelIndex]; channelSelection = input ? settings.auxInputChannels : settings.auxOutputChannels;
break; break;
case RouteType.Send: case RouteType.Send:
value = input ? settings.sendInputChannels[channelIndex] : settings.sendOutputChannels[channelIndex]; channelSelection = input ? settings.sendInputChannels : settings.sendOutputChannels;
break; break;
} }
value = channelSelection[channelIndex];
if (value >= channelCount) { if (value >= channelCount) {
value = -1; value = -1;
} }
if (routeType === RouteType.Send) {
if (input) {
disallowedSelections = settings.mainInputChannels.concat(settings.auxInputChannels);
} else {
disallowedSelections = settings.mainOutputChannels.concat(settings.auxOutputChannels);
}
}
if (channelIndex === 1) {
if (channelSelection[0] !== -1) {
disallowedSelections.push(channelSelection[0]);
}
}
let error = false;
if (routeType === RouteType.Main && channelSelection[0] === -1 && channelSelection[1] === -1) {
error = true;
}
return ( return (
<Select variant="standard" style={{ width: 90 }} value={value} <Select variant="standard" style={{ width: icon ? 130 : 110 }} value={value}
startAdornment={ startAdornment={
icon ? ( icon ? (
input ? (<MicIcon style={{ opacity: 0.6, width: 16, height: 16, marginRight: 4 }} />) : input ? (<MicIcon style={{ opacity: 0.6, width: 16, height: 16, marginRight: 4 }} />) :
(<SpeakerIcon style={{ opacity: 0.6, width: 16, height: 16, marginRight: 4 }} />) (<SpeakerIcon style={{ opacity: 0.6, width: 16, height: 16, marginRight: 4 }} />)
) : undefined ) : undefined
} }
error={error}
onChange={(event) => { onChange={(event) => {
let newValue = event.target.value as number; let newValue = event.target.value as number;
let newSettings = new ChannelRouterSettings().deserialize(settings); let newSettings = new ChannelRouterSettings().deserialize(settings);
@@ -350,32 +499,43 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
case RouteType.Main: case RouteType.Main:
if (input) { if (input) {
newSettings.mainInputChannels[channelIndex] = newValue; newSettings.mainInputChannels[channelIndex] = newValue;
normalizeChannelSelection(newSettings.mainInputChannels);
} else { } else {
newSettings.mainOutputChannels[channelIndex] = newValue; newSettings.mainOutputChannels[channelIndex] = newValue;
normalizeChannelSelection(newSettings.mainOutputChannels);
} }
break; break;
case RouteType.Aux: case RouteType.Aux:
if (input) { if (input) {
newSettings.auxInputChannels[channelIndex] = newValue; newSettings.auxInputChannels[channelIndex] = newValue;
normalizeChannelSelection(newSettings.auxInputChannels);
} else { } else {
newSettings.auxOutputChannels[channelIndex] = newValue; newSettings.auxOutputChannels[channelIndex] = newValue;
normalizeChannelSelection(newSettings.auxOutputChannels);
} }
break; break;
case RouteType.Send: case RouteType.Send:
if (input) { if (input) {
newSettings.sendInputChannels[channelIndex] = newValue; newSettings.sendInputChannels[channelIndex] = newValue;
normalizeChannelSelection(newSettings.sendInputChannels);
} else { } else {
newSettings.sendOutputChannels[channelIndex] = newValue; newSettings.sendOutputChannels[channelIndex] = newValue;
normalizeChannelSelection(newSettings.sendOutputChannels);
} }
break; break;
} }
if (newSettings.channelRouterPresetId >= 0) {
newSettings.modified = true; // custom.
} else {
newSettings.modified = true;
newSettings.channelRouterPresetId = -1; // custom. newSettings.channelRouterPresetId = -1; // custom.
}
newSettings.configured = true; newSettings.configured = true;
model.setChannelRouterSettings(newSettings); model.setChannelRouterSettings(newSettings);
}} }}
> >
{MakeChannelMenu(channelCount)} {MakeChannelMenu(channelCount, routeType, input, disallowedSelections)}
</Select> </Select>
) )
} }
@@ -437,18 +597,32 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
return items; return items;
} }
let handleSave = () => {
};
let PresetSelect = (width: number | string | undefined) => { let PresetSelect = (width: number | string | undefined) => {
return ( return (
<div style={{ display: "flex", flexDirection: "row", marginTop: 2, alignItems: "center" }}> <div style={{ display: "flex", flexDirection: "row", marginTop: 2, alignItems: "center" }}>
<Select variant="standard" style={{ width: width }} value={settings.channelRouterPresetId} <div style={{ flex: "0 0 auto" }}>
<IconButtonEx tooltip="Save current preset"
style={{ flex: "0 0 auto", color: "#FFFFFF" }}
onClick={(e) => { handleSave(); }}
size="large">
<SaveIconOutline style={{ opacity: 0.75 }} color="inherit" />
</IconButtonEx>
</div>
<div style={{ flex: "1 1", display: "relative" }}>
<Select variant="standard" style={{ width: "100%" }} value={settings.channelRouterPresetId}
onChange={(event) => { onChange={(event) => {
ApplyPresetId(event.target.value); ApplyPresetId(event.target.value);
}} }}
> >
{GeneratePresetMenuItems()} {GeneratePresetMenuItems()}
</Select> </Select>
<IconButtonEx tooltip="Presets" aria-label="more-presets"> </div>
<IconButtonEx tooltip="Presets" aria-label="more-presets" style={{ marginRight: 24 }}>
<MoreVertIcon style={{ opacity: 0.6 }} onClick={() => { }} /> <MoreVertIcon style={{ opacity: 0.6 }} onClick={() => { }} />
</IconButtonEx> </IconButtonEx>
</div> </div>
@@ -457,6 +631,7 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
let LandscapeView = () => { let LandscapeView = () => {
let auxInsertDisabled = !settings.isAuxActive();
return ( return (
<div style={{ <div style={{
display: "flex", flexFlow: "row", alignItems: "center", justifyContent: "center", display: "flex", flexFlow: "row", alignItems: "center", justifyContent: "center",
@@ -518,7 +693,11 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
</td> </td>
<td rowSpan={2} style={cellLeft}>{Vu()}</td> <td rowSpan={2} style={cellLeft}>{Vu()}</td>
<td rowSpan={2} style={cellLeft}> <td rowSpan={2} style={cellLeft}>
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}> <Button variant="outlined" style={{
textTransform: "none", borderRadius: 24,
}}
disabled={auxInsertDisabled}>
Inserts Inserts
</Button> </Button>
</td> </td>
@@ -574,6 +753,7 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
); );
}; };
let PortraitView = () => { let PortraitView = () => {
let auxInsertDisabled = !settings.isAuxActive();
return ( return (
<div style={{ <div style={{
display: "flex", flexFlow: "row", alignItems: "center", justifyContent: "center", display: "flex", flexFlow: "row", alignItems: "center", justifyContent: "center",
@@ -685,7 +865,11 @@ function ChannelRouterSettingsDialog(props: ChannelRouterSettingsDialogProps) {
<td style={cellPortraitControlStrip}>{Vu()}</td> <td style={cellPortraitControlStrip}>{Vu()}</td>
<td style={cellPortraitControlStrip}> <td style={cellPortraitControlStrip}>
<div style={{ marginLeft: 8, marginRight: 8 }}> <div style={{ marginLeft: 8, marginRight: 8 }}>
<Button variant="outlined" style={{ textTransform: "none", borderRadius: 24 }}> <Button variant="outlined" style={{
textTransform: "none", borderRadius: 24,
}}
disabled={auxInsertDisabled}>
Inserts Inserts
</Button> </Button>
</div> </div>
@@ -105,15 +105,15 @@ export default class ChannelRouterSettingsHelpDialog extends ResizeResponsiveCom
<DialogContent style={{ minHeight: 96 }}> <DialogContent style={{ minHeight: 96 }}>
<div style={{ fontSize: "0.9em", lineHeight: "18pt" }}> <div style={{ fontSize: "0.9em", lineHeight: "18pt" }}>
<p> The Mixer Routing Dialog determines how audio signals are processed and routed between input and output audio channels. <p> The Audio Channel Routing Dialog determines how audio signals are processed and routed between input and output audio channels.
</p> </p>
<p>Guitar effects processing occurs on the <i>Main</i> route only. Plugins in the main PiPedal window are <p>By default, guitar effects processing occurs on the <i>Main</i> route only. Plugins in the main PiPedal window are
applied before any insert plugins that have been added to the <i>Main</i> route. Main insert plugins are applied before any insert plugins that have been added to the <i>Main</i> route. Main insert plugins are
applied globally, regardless of the selected preset or effects in the main PiPedal window. You might applied globally, regardless of the selected preset or effects in the main PiPedal window. You might
want to add an EQ or reverb plugin here, to allow your presets to be globally adjusted to suit the room want to add an EQ or reverb plugin here, to allow your presets to be globally adjusted to suit the room
in which you are currently performing. in which you are currently performing.
</p> </p>
<p>Plugins in the main PiPedal window are not applied to the <i>Aux</i> route. The Aux route is intended to be <p>Plugins in the main PiPedal window are not applied to the <i>Aux</i> route by default. The Aux route is intended to be
used for used for
</p> </p>
<ul> <ul>
@@ -128,7 +128,13 @@ export default class ChannelRouterSettingsHelpDialog extends ResizeResponsiveCom
mixing it with the processed guitar signal, or passing it out on a dedicated output channel. mixing it with the processed guitar signal, or passing it out on a dedicated output channel.
</li> </li>
</ul> </ul>
<p>although you may find other creative uses for the Aux channel as well.</p> <p>However, if "Main Out L" or "Main Out R" is selected as an Aux input channel, then the Aux route will receive the
output of the processed Main route. Main route insrts are not applied
to the Aux route input signal, so you can have an independent set of inserts applied to the Main and Aux output signals.
An example of how one might use this feature: sending the output of the Main route to
a soundboard or PA system without reverb or EQ applied; and then applying reverb and EQ only to the Aux route whose
outputs will be used as inputs to a stage monitor or headphones for the performer.
</p>
<p>If the Main and Aux routes share output channels, then the results of the Main and Output signals are <p>If the Main and Aux routes share output channels, then the results of the Main and Output signals are
summed together on those output channels. For stereo mixing, assign the same left and right output summed together on those output channels. For stereo mixing, assign the same left and right output
channels to both the Main and Aux routes. If you have a 2x4 or 4x4 audio interface with two guitar input channels channels to both the Main and Aux routes. If you have a 2x4 or 4x4 audio interface with two guitar input channels
@@ -142,7 +148,8 @@ export default class ChannelRouterSettingsHelpDialog extends ResizeResponsiveCom
TooB Send plugin will be sent to the external hardware effect, and the output the external hardware TooB Send plugin will be sent to the external hardware effect, and the output the external hardware
effect will be returned to PiPedal at the output of the TooB Send plugin. PiPedal currently supports effect will be returned to PiPedal at the output of the TooB Send plugin. PiPedal currently supports
only one Send plugin instance per preset; and the Send and Return channels may not be shared with the Main only one Send plugin instance per preset; and the Send and Return channels may not be shared with the Main
or Aux routes. or Aux routes. Note that you may only use one instance of the TooB Send plugin per preset. The TooB Send plugin
may not be used as a Main or Aux insert effect.
</p> </p>
</div> </div>
+9 -12
View File
@@ -27,6 +27,8 @@ export default class JackServerSettings {
this.rebootRequired = input.rebootRequired; this.rebootRequired = input.rebootRequired;
this.alsaInputDevice = input.alsaInputDevice ?? ""; this.alsaInputDevice = input.alsaInputDevice ?? "";
this.alsaOutputDevice = input.alsaOutputDevice ?? ""; this.alsaOutputDevice = input.alsaOutputDevice ?? "";
this.alsaInputDeviceName = input.alsaInputDeviceName ?? this.alsaInputDevice;
this.alsaOutputDeviceName = input.alsaOutputDeviceName ?? this.alsaOutputDevice;
this.sampleRate = input.sampleRate; this.sampleRate = input.sampleRate;
this.bufferSize = input.bufferSize; this.bufferSize = input.bufferSize;
this.numberOfBuffers = input.numberOfBuffers; this.numberOfBuffers = input.numberOfBuffers;
@@ -50,6 +52,8 @@ export default class JackServerSettings {
isJackAudio = false; isJackAudio = false;
alsaInputDevice: string = ""; alsaInputDevice: string = "";
alsaOutputDevice: string = ""; alsaOutputDevice: string = "";
alsaInputDeviceName: string = "";
alsaOutputDeviceName: string = "";
sampleRate = 48000; sampleRate = 48000;
bufferSize = 64; bufferSize = 64;
numberOfBuffers = 3; numberOfBuffers = 3;
@@ -74,19 +78,12 @@ export default class JackServerSettings {
return "Not selected"; return "Not selected";
} }
let inDev = this.alsaInputDevice.startsWith("hw:")
? this.alsaInputDevice.substring(3)
: this.alsaInputDevice;
let outDev = this.alsaOutputDevice.startsWith("hw:")
? this.alsaOutputDevice.substring(3)
: this.alsaOutputDevice;
let device: string; let device: string;
if (inDev === outDev) { if (this.alsaInputDevice === this.alsaOutputDevice) {
device = inDev; device = this.alsaInputDeviceName;;
}
} else { else {
device = inDev + "-> " + outDev; device = this.alsaInputDeviceName + " -> " + this.alsaOutputDeviceName;
} }
return `${device} ${this.sampleRate} ${this.bufferSize}x${this.numberOfBuffers}`; return `${device} ${this.sampleRate} ${this.bufferSize}x${this.numberOfBuffers}`;
} }
@@ -580,10 +580,20 @@ const JackServerSettingsDialog = withStyles(
this.setState({ suppressDeviceWarning: checked }); this.setState({ suppressDeviceWarning: checked });
localStorage.setItem("suppressSeparateDeviceWarning", checked ? "1" : "0"); localStorage.setItem("suppressSeparateDeviceWarning", checked ? "1" : "0");
} }
getDeviceName(deviceId: string): string {
if (!this.state.alsaDevices) return deviceId;
for (let i = 0; i < this.state.alsaDevices.length; ++i) {
if (this.state.alsaDevices[i].id === deviceId) {
return this.state.alsaDevices[i].name;
}
}
return deviceId;
}
handleInputDeviceChanged(e: any) { handleInputDeviceChanged(e: any) {
const d = e.target.value as string; const d = e.target.value as string;
let s = this.state.jackServerSettings.clone(); let s = this.state.jackServerSettings.clone();
s.alsaInputDevice = d; s.alsaInputDevice = d;
s.alsaInputDeviceName = this.getDeviceName(d);
s.valid = false; s.valid = false;
let settings = this.applyAlsaDevices(s, this.state.alsaDevices); let settings = this.applyAlsaDevices(s, this.state.alsaDevices);
this.setState({ this.setState({
@@ -597,6 +607,7 @@ const JackServerSettingsDialog = withStyles(
const d = e.target.value as string; const d = e.target.value as string;
let s = this.state.jackServerSettings.clone(); let s = this.state.jackServerSettings.clone();
s.alsaOutputDevice = d; s.alsaOutputDevice = d;
s.alsaOutputDeviceName = this.getDeviceName(d);
s.valid = false; s.valid = false;
let settings = this.applyAlsaDevices(s, this.state.alsaDevices); let settings = this.applyAlsaDevices(s, this.state.alsaDevices);
this.setState({ this.setState({