Merge branch 'splitio_base' of https://github.com/rerdavies/pipedal into dev_splitio_merge
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
wpa_cli -ip2p-dev-wlan0 GET device_name pipedal
|
wpa_cli -ip2p-dev-wlan0 GET device_name pipedal
|
||||||
wpa_cli -ip2p-dev-wlan0 GET country CA
|
wpa_cli -ip2p-dev-wlan0 GET country CA
|
||||||
wpa_cli -ip2p-dev-wlan0 GET device_type 1-0050F204-1
|
wpa_cli -ip2p-dev-wlan0 GET device_type 1-0050F204-1
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ https://github.com/user-attachments/assets/9a9fd0c6-78fc-4284-8b44-6a1929c00cc6
|
|||||||
### [The Build System](https://rerdavies.github.io/pipedal/TheBuildSystem.html)
|
### [The Build System](https://rerdavies.github.io/pipedal/TheBuildSystem.html)
|
||||||
### [How to Debug PiPedal](https://rerdavies.github.io/pipedal/Debugging.html)
|
### [How to Debug PiPedal](https://rerdavies.github.io/pipedal/Debugging.html)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### [PiPedal Architecture](https://rerdavies.github.io/pipedal/Architecture.html)
|
#### [PiPedal Architecture](https://rerdavies.github.io/pipedal/Architecture.html)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -6,4 +6,4 @@
|
|||||||
# bundle install
|
# bundle install
|
||||||
#
|
#
|
||||||
cd docs
|
cd docs
|
||||||
bundle exec jekyll serve --host
|
bundle exec jekyll serve --host 0.0.0.0
|
||||||
@@ -21,6 +21,7 @@ Run the following commands to install dependent libraries required by the PiPeda
|
|||||||
|
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt upgrade
|
sudo apt upgrade
|
||||||
|
|
||||||
sudo apt install -y liblilv-dev libboost-dev \
|
sudo apt install -y liblilv-dev libboost-dev \
|
||||||
libsystemd-dev catch libasound2-dev uuid-dev \
|
libsystemd-dev catch libasound2-dev uuid-dev \
|
||||||
authbind libavahi-client-dev libnm-dev libicu-dev \
|
authbind libavahi-client-dev libnm-dev libicu-dev \
|
||||||
|
|||||||
@@ -13,15 +13,16 @@ available through the Code plugins store) has configured itself, build commands
|
|||||||
If you are not using Visual Studio Code, you can configure, build and install PiPedal using CMake build tools. For your convenience,
|
If you are not using Visual Studio Code, you can configure, build and install PiPedal using CMake build tools. For your convenience,
|
||||||
the following shell scripts have been provided in the root of the project.
|
the following shell scripts have been provided in the root of the project.
|
||||||
|
|
||||||
./init.sh # Configure the CMake build for the first time, or if you
|
./init.sh # Configure the CMake build for the first time, or if you
|
||||||
# have changed one of the CMakeList.txt files. (release build)
|
# have changed one of the CMakeList.txt files. (release build)
|
||||||
|
|
||||||
./mk.sh # Build all targets (release build)
|
./mk.sh # Build all targets (release build)
|
||||||
|
|
||||||
sudo ./install.sh # Deploy all targets
|
sudo ./install.sh # Deploy all targets
|
||||||
|
|
||||||
sudo ./makePackage.sh # Build a .deb file for distribution.
|
sudo ./makePackage.sh # Build a .deb file for distribution.
|
||||||
|
|
||||||
|
|
||||||
If you are using a development environment other than Visual Studio Code, it should be fairly straightforward to figure out how
|
If you are using a development environment other than Visual Studio Code, it should be fairly straightforward to figure out how
|
||||||
to incorporate the PiPedal build procedure into your IDE workflow by using the contents of the build shell scripts as a model.
|
to incorporate the PiPedal build procedure into your IDE workflow by using the contents of the build shell scripts as a model.
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
cd build
|
cd build
|
||||||
cpack -G DEB -C Release -config CPackConfig.cmake
|
cpack -G DEB -C Release -config CPackConfig.cmake
|
||||||
cd ..
|
cd ..
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
${SCRIPT_DIR}/../build/src/profilePlugin -s 100 -w ToobNam_Profile -o /tmp/ToobNam.perf &&\
|
${SCRIPT_DIR}/../build/src/profilePlugin -s 100 -w ToobNam_Profile -o /tmp/ToobNam.perf &&\
|
||||||
google-pprof --text ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/ToobAmp.lv2/ToobAmp.so /tmp/ToobNam.perf >./ToobNam.txt
|
google-pprof --text ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/ToobAmp.lv2/ToobAmp.so /tmp/ToobNam.perf >./ToobNam.txt
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
${SCRIPT_DIR}/../build/src/profilePlugin -w Nam_Profile -o /tmp/nam.perf &&\
|
${SCRIPT_DIR}/../build/src/profilePlugin -w Nam_Profile -o /tmp/nam.perf &&\
|
||||||
google-pprof --web ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/neural_amp_modeler.lv2/neural_amp_modeler.so /tmp/nam.perf >./nam.txt
|
google-pprof --web ${SCRIPT_DIR}/../build/src/profilePlugin --add_lib /usr/lib/lv2/neural_amp_modeler.lv2/neural_amp_modeler.so /tmp/nam.perf >./nam.txt
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
rm -rf ./lv2/aarch64/*
|
rm -rf ./lv2/aarch64/*
|
||||||
|
|
||||||
cp -R /usr/lib/lv2/ToobAmp.lv2/ ./lv2/aarch64/
|
cp -R /usr/lib/lv2/ToobAmp.lv2/ ./lv2/aarch64/
|
||||||
|
|||||||
+10
-4
@@ -683,6 +683,7 @@ namespace pipedal
|
|||||||
|
|
||||||
if (this->captureHandle)
|
if (this->captureHandle)
|
||||||
{
|
{
|
||||||
|
this->alsa_device_name = this->jackServerSettings.GetAlsaInputDevice();
|
||||||
AlsaConfigureStream(
|
AlsaConfigureStream(
|
||||||
this->alsa_device_name,
|
this->alsa_device_name,
|
||||||
"capture",
|
"capture",
|
||||||
@@ -695,6 +696,7 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
if (this->playbackHandle)
|
if (this->playbackHandle)
|
||||||
{
|
{
|
||||||
|
this->alsa_device_name = this->jackServerSettings.GetAlsaOutputDevice();
|
||||||
AlsaConfigureStream(
|
AlsaConfigureStream(
|
||||||
this->alsa_device_name,
|
this->alsa_device_name,
|
||||||
"playback",
|
"playback",
|
||||||
@@ -1326,7 +1328,9 @@ namespace pipedal
|
|||||||
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
alsa_device_name = jackServerSettings.GetAlsaInputDevice();
|
std::string inputName = jackServerSettings.GetAlsaInputDevice();
|
||||||
|
std::string outputName = jackServerSettings.GetAlsaOutputDevice();
|
||||||
|
|
||||||
|
|
||||||
this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers();
|
this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers();
|
||||||
this->bufferSize = jackServerSettings.GetBufferSize();
|
this->bufferSize = jackServerSettings.GetBufferSize();
|
||||||
@@ -1335,7 +1339,8 @@ namespace pipedal
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
err = snd_pcm_open(&playbackHandle, alsa_device_name.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
this->alsa_device_name = outputName;
|
||||||
|
err = snd_pcm_open(&playbackHandle, outputName.c_str(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
{
|
{
|
||||||
switch (errno)
|
switch (errno)
|
||||||
@@ -1369,8 +1374,9 @@ namespace pipedal
|
|||||||
{
|
{
|
||||||
snd_pcm_nonblock(playbackHandle, 0);
|
snd_pcm_nonblock(playbackHandle, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
err = snd_pcm_open(&captureHandle, alsa_device_name.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
|
this->alsa_device_name = inputName;
|
||||||
|
err = snd_pcm_open(&captureHandle, inputName.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
|
||||||
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public:
|
|||||||
|
|
||||||
AlsaFormatEncodeDecodeTest(this);
|
AlsaFormatEncodeDecodeTest(this);
|
||||||
|
|
||||||
JackServerSettings serverSettings("hw:M2",48000,32,3);
|
JackServerSettings serverSettings("hw:M2","hw:M2",48000,32,3);
|
||||||
|
|
||||||
JackConfiguration jackConfiguration;
|
JackConfiguration jackConfiguration;
|
||||||
if (useJack)
|
if (useJack)
|
||||||
|
|||||||
+11
-6
@@ -140,14 +140,19 @@ namespace pipedal
|
|||||||
overheadTime = readTime;
|
overheadTime = readTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
SampleT totalTime = writeTime+ readTime + processingTime;
|
SampleT totalTime = writeTime + readTime + processingTime;
|
||||||
SampleT maxTime = waitTime+processingTime;
|
SampleT maxTime = waitTime + processingTime;
|
||||||
|
|
||||||
|
|
||||||
float result = 100.0f*(processingTime)/(maxTime);
|
float result = 0.0f;
|
||||||
float overhead = 100.0F*(overheadTime*2)/totalTime;
|
float overhead = 0.0f;
|
||||||
|
if (maxTime != 0 && totalTime != 0)
|
||||||
{
|
{
|
||||||
std::lock_guard lock { sync};
|
result = 100.0f * (processingTime) / (maxTime);
|
||||||
|
overhead = 100.0f * (overheadTime * 2) / totalTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock{sync};
|
||||||
currentCpuUse = result;
|
currentCpuUse = result;
|
||||||
currentOverhead = overhead;
|
currentOverhead = overhead;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -517,6 +517,8 @@ namespace pipedal
|
|||||||
result.sampleRates_.push_back(48000);
|
result.sampleRates_.push_back(48000);
|
||||||
result.minBufferSize_ = 16;
|
result.minBufferSize_ = 16;
|
||||||
result.maxBufferSize_ = 1024;
|
result.maxBufferSize_ = 1024;
|
||||||
|
result.supportsCapture_ = true;
|
||||||
|
result.supportsPlayback_ = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,13 @@ void JackServerSettings::ReadJackDaemonConfiguration()
|
|||||||
this->bufferSize_ = GetJackArg(argv, "-p", "--period");
|
this->bufferSize_ = GetJackArg(argv, "-p", "--period");
|
||||||
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "--nperiods");
|
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "--nperiods");
|
||||||
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "--rate");
|
this->sampleRate_ = (uint32_t)GetJackArg(argv, "-r", "--rate");
|
||||||
this->alsaDevice_ = GetJackStringArg(argv,"-d", "--device");
|
// 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;
|
this->valid_ = true;
|
||||||
}
|
}
|
||||||
catch (std::exception &)
|
catch (std::exception &)
|
||||||
@@ -246,7 +252,9 @@ JSON_MAP_REFERENCE(JackServerSettings, valid)
|
|||||||
JSON_MAP_REFERENCE(JackServerSettings, isOnboarding)
|
JSON_MAP_REFERENCE(JackServerSettings, isOnboarding)
|
||||||
JSON_MAP_REFERENCE(JackServerSettings, rebootRequired)
|
JSON_MAP_REFERENCE(JackServerSettings, rebootRequired)
|
||||||
JSON_MAP_REFERENCE(JackServerSettings, isJackAudio)
|
JSON_MAP_REFERENCE(JackServerSettings, isJackAudio)
|
||||||
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice)
|
JSON_MAP_REFERENCE(JackServerSettings, alsaDevice) // legacy field
|
||||||
|
JSON_MAP_REFERENCE(JackServerSettings, alsaInputDevice)
|
||||||
|
JSON_MAP_REFERENCE(JackServerSettings, alsaOutputDevice)
|
||||||
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)
|
||||||
|
|||||||
+22
-15
@@ -31,7 +31,9 @@ namespace pipedal
|
|||||||
bool isOnboarding_ = true;
|
bool isOnboarding_ = true;
|
||||||
bool isJackAudio_ = JACK_HOST ? true : false;
|
bool isJackAudio_ = JACK_HOST ? true : false;
|
||||||
bool rebootRequired_ = false;
|
bool rebootRequired_ = false;
|
||||||
std::string alsaDevice_;
|
std::string alsaInputDevice_;
|
||||||
|
std::string alsaOutputDevice_;
|
||||||
|
std::string alsaDevice_; // legacy
|
||||||
uint64_t sampleRate_ = 0;
|
uint64_t sampleRate_ = 0;
|
||||||
uint32_t bufferSize_ = 64;
|
uint32_t bufferSize_ = 64;
|
||||||
uint32_t numberOfBuffers_ = 3;
|
uint32_t numberOfBuffers_ = 3;
|
||||||
@@ -39,10 +41,14 @@ namespace pipedal
|
|||||||
public:
|
public:
|
||||||
JackServerSettings();
|
JackServerSettings();
|
||||||
JackServerSettings(
|
JackServerSettings(
|
||||||
const std::string alsaInputDevice,
|
const std::string &alsaInputDevice,
|
||||||
uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
|
const std::string &alsaOutputDevice,
|
||||||
|
uint64_t sampleRate,
|
||||||
|
uint32_t bufferSize,
|
||||||
|
uint32_t numberOfBuffers)
|
||||||
: valid_(true),
|
: valid_(true),
|
||||||
alsaDevice_(alsaInputDevice),
|
alsaInputDevice_(alsaInputDevice),
|
||||||
|
alsaOutputDevice_(alsaOutputDevice),
|
||||||
sampleRate_(sampleRate),
|
sampleRate_(sampleRate),
|
||||||
bufferSize_(bufferSize),
|
bufferSize_(bufferSize),
|
||||||
numberOfBuffers_(numberOfBuffers),
|
numberOfBuffers_(numberOfBuffers),
|
||||||
@@ -51,9 +57,13 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t GetSampleRate() const { return sampleRate_; }
|
uint64_t GetSampleRate() const { return sampleRate_; }
|
||||||
|
|
||||||
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 alsaDevice_; }
|
const std::string &GetAlsaInputDevice() const { return alsaInputDevice_; }
|
||||||
|
const std::string &GetAlsaOutputDevice() const { return alsaOutputDevice_; }
|
||||||
|
const std::string &GetLegacyAlsaDevice() const { return alsaDevice_; } //legacy
|
||||||
|
void SetLegacyAlsaDevice(const std::string &d) { alsaDevice_ = d; }
|
||||||
void UseDummyAudioDevice() {
|
void UseDummyAudioDevice() {
|
||||||
this->valid_ = true;
|
this->valid_ = true;
|
||||||
if (sampleRate_ == 0) sampleRate_ = 48000;
|
if (sampleRate_ == 0) sampleRate_ = 48000;
|
||||||
@@ -69,14 +79,6 @@ namespace pipedal
|
|||||||
|
|
||||||
bool IsValid() const { return valid_; }
|
bool IsValid() const { return valid_; }
|
||||||
|
|
||||||
// JackServerSettings(uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
|
|
||||||
// {
|
|
||||||
// this->valid_ = true;
|
|
||||||
// this->rebootRequired_ = true;
|
|
||||||
// this->sampleRate_ = sampleRate;
|
|
||||||
// this->bufferSize_ = bufferSize;
|
|
||||||
// this->numberOfBuffers_ = numberOfBuffers;
|
|
||||||
// }
|
|
||||||
void WriteDaemonConfig(); // requires root perms.
|
void WriteDaemonConfig(); // requires root perms.
|
||||||
void SetRebootRequired(bool value)
|
void SetRebootRequired(bool value)
|
||||||
{
|
{
|
||||||
@@ -87,9 +89,14 @@ namespace pipedal
|
|||||||
isOnboarding_ = value;
|
isOnboarding_ = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Equals(const JackServerSettings &other)
|
bool Equals(const JackServerSettings &other) const
|
||||||
{
|
{
|
||||||
return this->alsaDevice_ == other.alsaDevice_ && this->sampleRate_ == other.sampleRate_ && this->bufferSize_ == other.bufferSize_ && this->numberOfBuffers_ == other.numberOfBuffers_;
|
return this->alsaInputDevice_ == other.alsaInputDevice_ &&
|
||||||
|
this->alsaOutputDevice_ == other.alsaOutputDevice_ &&
|
||||||
|
this->alsaDevice_ == other.alsaDevice_ &&
|
||||||
|
this->sampleRate_ == other.sampleRate_ &&
|
||||||
|
this->bufferSize_ == other.bufferSize_ &&
|
||||||
|
this->numberOfBuffers_ == other.numberOfBuffers_;
|
||||||
}
|
}
|
||||||
|
|
||||||
DECLARE_JSON_MAP(JackServerSettings);
|
DECLARE_JSON_MAP(JackServerSettings);
|
||||||
|
|||||||
+25
-16
@@ -57,9 +57,9 @@ void PrintHelp()
|
|||||||
pp << "Copyright (c) 2022 Robin Davies\n";
|
pp << "Copyright (c) 2022 Robin Davies\n";
|
||||||
pp << "\n";
|
pp << "\n";
|
||||||
pp << Indent(0) << "Syntax\n\n";
|
pp << Indent(0) << "Syntax\n\n";
|
||||||
pp << Indent(2) << "pipedal_latency_test [<options>] <device-name>\n\n";
|
pp << Indent(2) << "pipedal_latency_test [<options>] <input-device> [<output-device>]\n\n";
|
||||||
pp << "where <device-name> is the name of an ALSA device. Typically this should be the name of a hardware "
|
pp << "where <input-device> is the name of an ALSA capture device and <output-device> is the name of a playback device. "
|
||||||
"device (a device name starting with 'hw:').\n\n";
|
"If <output-device> is omitted, the input device will be used for both capture and playback. Typically the device names start with 'hw:'.\n\n";
|
||||||
pp << Indent(0) << "Options\n\n";
|
pp << Indent(0) << "Options\n\n";
|
||||||
pp << Indent(15);
|
pp << Indent(15);
|
||||||
|
|
||||||
@@ -95,7 +95,8 @@ void PrintHelp()
|
|||||||
|
|
||||||
pp << Indent(0) << "Examples\n\n";
|
pp << Indent(0) << "Examples\n\n";
|
||||||
pp << Indent(2) << "pipedal_latency_test --list\n\n";
|
pp << Indent(2) << "pipedal_latency_test --list\n\n";
|
||||||
pp << Indent(2) << "pipedal_latency_test hw:M2\n\n";
|
pp << Indent(2) << "pipedal_latency_test hw:M2 hw:M2\n";
|
||||||
|
pp << Indent(2) << "pipedal_latency_test hw:M2 hw:Device2\n\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ListDevices()
|
void ListDevices()
|
||||||
@@ -138,7 +139,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
AudioDriver *audioDriver = nullptr;
|
AudioDriver *audioDriver = nullptr;
|
||||||
|
|
||||||
const std::string &deviceId;
|
const std::string &inputDeviceId;
|
||||||
|
const std::string &outputDeviceId;
|
||||||
ChannelsT inputChannels;
|
ChannelsT inputChannels;
|
||||||
ChannelsT outputChannels;
|
ChannelsT outputChannels;
|
||||||
uint32_t sampleRate;
|
uint32_t sampleRate;
|
||||||
@@ -147,11 +149,13 @@ private:
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
AlsaTester(
|
AlsaTester(
|
||||||
const std::string &deviceId,
|
const std::string &inputDeviceId,
|
||||||
|
const std::string &outputDeviceId,
|
||||||
const ChannelsT &inputChannels,
|
const ChannelsT &inputChannels,
|
||||||
const ChannelsT &outputChannels,
|
const ChannelsT &outputChannels,
|
||||||
uint32_t sampleRate, int bufferSize, int buffers)
|
uint32_t sampleRate, int bufferSize, int buffers)
|
||||||
: deviceId(deviceId),
|
: inputDeviceId(inputDeviceId),
|
||||||
|
outputDeviceId(outputDeviceId),
|
||||||
sampleRate(sampleRate),
|
sampleRate(sampleRate),
|
||||||
inputChannels(inputChannels),
|
inputChannels(inputChannels),
|
||||||
outputChannels(outputChannels),
|
outputChannels(outputChannels),
|
||||||
@@ -186,7 +190,7 @@ public:
|
|||||||
TestResult result;
|
TestResult result;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
JackServerSettings serverSettings(deviceId, sampleRate, bufferSize, buffers);
|
JackServerSettings serverSettings(inputDeviceId, outputDeviceId, sampleRate, bufferSize, buffers);
|
||||||
|
|
||||||
JackConfiguration jackConfiguration;
|
JackConfiguration jackConfiguration;
|
||||||
jackConfiguration.AlsaInitialize(serverSettings);
|
jackConfiguration.AlsaInitialize(serverSettings);
|
||||||
@@ -403,12 +407,13 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
TestResult RunLatencyTest(
|
TestResult RunLatencyTest(
|
||||||
const std::string deviceId,
|
const std::string inputDeviceId,
|
||||||
|
const std::string outputDeviceId,
|
||||||
const ChannelsT &inputChannels,
|
const ChannelsT &inputChannels,
|
||||||
const ChannelsT &outputChannels,
|
const ChannelsT &outputChannels,
|
||||||
uint32_t sampleRate, int bufferSize, int buffers)
|
uint32_t sampleRate, int bufferSize, int buffers)
|
||||||
{
|
{
|
||||||
AlsaTester tester(deviceId, inputChannels, outputChannels, sampleRate, bufferSize, buffers);
|
AlsaTester tester(inputDeviceId, outputDeviceId, inputChannels, outputChannels, sampleRate, bufferSize, buffers);
|
||||||
return tester.Test();
|
return tester.Test();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,13 +433,14 @@ static std::string overheadDisplay(float value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RunLatencyTest(
|
void RunLatencyTest(
|
||||||
const std::string &deviceId,
|
const std::string &inputDeviceId,
|
||||||
|
const std::string &outputDeviceId,
|
||||||
const ChannelsT &inputChannels,
|
const ChannelsT &inputChannels,
|
||||||
const ChannelsT &outputChannels,
|
const ChannelsT &outputChannels,
|
||||||
uint32_t sampleRate)
|
uint32_t sampleRate)
|
||||||
{
|
{
|
||||||
PrettyPrinter pp;
|
PrettyPrinter pp;
|
||||||
pp << "Device: " << deviceId << " Rate: " << sampleRate << "\n\n";
|
pp << "Input: " << inputDeviceId << " Output: " << outputDeviceId << " Rate: " << sampleRate << "\n\n";
|
||||||
|
|
||||||
const int SIZE_COLUMN_WIDTH = 8;
|
const int SIZE_COLUMN_WIDTH = 8;
|
||||||
const int BUFFERS_COLUMN_WIDTH = 20;
|
const int BUFFERS_COLUMN_WIDTH = 20;
|
||||||
@@ -461,7 +467,7 @@ void RunLatencyTest(
|
|||||||
|
|
||||||
for (auto bufferCount : bufferCounts)
|
for (auto bufferCount : bufferCounts)
|
||||||
{
|
{
|
||||||
auto result = RunLatencyTest(deviceId, inputChannels,outputChannels, sampleRate, bufferSize, bufferCount);
|
auto result = RunLatencyTest(inputDeviceId, outputDeviceId, inputChannels,outputChannels, sampleRate, bufferSize, bufferCount);
|
||||||
|
|
||||||
pp.Column(column);
|
pp.Column(column);
|
||||||
column += BUFFERS_COLUMN_WIDTH;
|
column += BUFFERS_COLUMN_WIDTH;
|
||||||
@@ -556,11 +562,14 @@ std:
|
|||||||
{
|
{
|
||||||
ListDevices();
|
ListDevices();
|
||||||
}
|
}
|
||||||
else if (parser.Arguments().size() == 1)
|
else if (parser.Arguments().size() >= 1 && parser.Arguments().size() <= 2)
|
||||||
{
|
{
|
||||||
inputChannels = ParseChannels(strInputChannels);
|
inputChannels = ParseChannels(strInputChannels);
|
||||||
outputChannels = ParseChannels(strInputChannels);
|
outputChannels = ParseChannels(strOutputChannels);
|
||||||
RunLatencyTest(parser.Arguments()[0], inputChannels, outputChannels, sampleRate);
|
|
||||||
|
std::string inDev = parser.Arguments()[0];
|
||||||
|
std::string outDev = parser.Arguments().size() == 2 ? parser.Arguments()[1] : inDev;
|
||||||
|
RunLatencyTest(inDev, outDev, inputChannels, outputChannels, sampleRate);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
+55
-16
@@ -22,6 +22,7 @@
|
|||||||
#include "alsa/asoundlib.h"
|
#include "alsa/asoundlib.h"
|
||||||
#include "Lv2Log.hpp"
|
#include "Lv2Log.hpp"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
@@ -111,6 +112,7 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
|||||||
}
|
}
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
{
|
{
|
||||||
|
snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice; // xxx: HECK NO!
|
||||||
snd_pcm_hw_params_t *params = nullptr;
|
snd_pcm_hw_params_t *params = nullptr;
|
||||||
err = snd_pcm_hw_params_malloc(¶ms);
|
err = snd_pcm_hw_params_malloc(¶ms);
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
@@ -121,29 +123,42 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
|||||||
unsigned int minRate = 0, maxRate = 0;
|
unsigned int minRate = 0, maxRate = 0;
|
||||||
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
|
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
|
||||||
int dir;
|
int dir;
|
||||||
|
|
||||||
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
|
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
{
|
{
|
||||||
err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir);
|
err = snd_pcm_hw_params_get_rate_max(params, &maxRate, &dir);
|
||||||
|
if (err == 0)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < sizeof(RATES) / sizeof(RATES[0]); ++i)
|
||||||
|
{
|
||||||
|
uint32_t rate = RATES[i];
|
||||||
|
if (rate >= minRate && rate <= maxRate)
|
||||||
|
{
|
||||||
|
info.sampleRates_.push_back(rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Lv2Log::warning(SS("Failed to get maximum sample rate for device '" << info.name_ << "'."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Lv2Log::warning(SS("Failed to get minimum sample rate for device '" << info.name_ << "'."));
|
||||||
|
}
|
||||||
|
|
||||||
if (err == 0)
|
if (err == 0)
|
||||||
{
|
{
|
||||||
err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize);
|
err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize);
|
||||||
}
|
if (err == 0)
|
||||||
if (err == 0)
|
|
||||||
{
|
|
||||||
err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize);
|
|
||||||
}
|
|
||||||
if (err == 0)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < sizeof(RATES) / sizeof(RATES[0]); ++i)
|
|
||||||
{
|
{
|
||||||
uint32_t rate = RATES[i];
|
err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize);
|
||||||
if (rate >= minRate && rate <= maxRate)
|
|
||||||
{
|
|
||||||
info.sampleRates_.push_back(rate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (err == 0)
|
||||||
|
{
|
||||||
if (minBufferSize < 16)
|
if (minBufferSize < 16)
|
||||||
{
|
{
|
||||||
minBufferSize = 16;
|
minBufferSize = 16;
|
||||||
@@ -158,7 +173,19 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
|||||||
}
|
}
|
||||||
if (params != nullptr)
|
if (params != nullptr)
|
||||||
snd_pcm_hw_params_free(params);
|
snd_pcm_hw_params_free(params);
|
||||||
snd_pcm_close(hDevice);
|
if (captureOk) // HECK NO!! REVIEW THIS!
|
||||||
|
snd_pcm_close(captureDevice);
|
||||||
|
if (playbackOk && playbackDevice != captureDevice)
|
||||||
|
snd_pcm_close(playbackDevice);
|
||||||
|
if (err == 0)
|
||||||
|
{
|
||||||
|
cacheDevice(info.name_, info);
|
||||||
|
result.push_back(info);
|
||||||
|
}
|
||||||
|
else if (getCachedDevice(info.name_, &info))
|
||||||
|
{
|
||||||
|
result.push_back(info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -174,12 +201,24 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
|
|||||||
}
|
}
|
||||||
snd_config_update_free_global();
|
snd_config_update_free_global();
|
||||||
|
|
||||||
|
auto isFiltered = [](const AlsaDeviceInfo &d) {
|
||||||
|
std::string name = d.name_ + " " + d.longName_;
|
||||||
|
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c){return std::tolower(c);});
|
||||||
|
return name.find("hdmi") != std::string::npos || name.find("bcm2835") != std::string::npos;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<AlsaDeviceInfo> filtered;
|
||||||
|
for (auto &d : result)
|
||||||
|
{
|
||||||
|
if (!isFiltered(d)) filtered.push_back(d);
|
||||||
|
}
|
||||||
|
|
||||||
Lv2Log::debug("GetAlsaDevices --");
|
Lv2Log::debug("GetAlsaDevices --");
|
||||||
for (auto &device : result)
|
for (auto &device : filtered)
|
||||||
{
|
{
|
||||||
Lv2Log::debug(SS(" " << device.name_ << " " << device.longName_ << " " << device.cardId_));
|
Lv2Log::debug(SS(" " << device.name_ << " " << device.longName_ << " " << device.cardId_));
|
||||||
}
|
}
|
||||||
return result;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaMidiDeviceInfo::Direction direction, std::vector<AlsaMidiDeviceInfo> *result)
|
static void AddMidiCardDevicesToList(snd_ctl_t *ctl, int card, int device, AlsaMidiDeviceInfo::Direction direction, std::vector<AlsaMidiDeviceInfo> *result)
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ namespace pipedal {
|
|||||||
std::string longName_;
|
std::string longName_;
|
||||||
std::vector<uint32_t> sampleRates_;
|
std::vector<uint32_t> sampleRates_;
|
||||||
uint32_t minBufferSize_ = 0,maxBufferSize_ = 0;
|
uint32_t minBufferSize_ = 0,maxBufferSize_ = 0;
|
||||||
|
bool supportsCapture_ = false;
|
||||||
|
bool supportsPlayback_ = false;
|
||||||
|
|
||||||
bool isDummyDevice() const {
|
bool isDummyDevice() const {
|
||||||
return id_.starts_with("dummy:");
|
return id_.starts_with("dummy:");
|
||||||
|
|||||||
@@ -1750,6 +1750,14 @@ pipedal::JackServerSettings Storage::GetJackServerSettings()
|
|||||||
{
|
{
|
||||||
json_reader reader(f);
|
json_reader reader(f);
|
||||||
reader.read(&result);
|
reader.read(&result);
|
||||||
|
if (!result.GetLegacyAlsaDevice().empty() &&
|
||||||
|
result.GetAlsaInputDevice().empty() &&
|
||||||
|
result.GetAlsaOutputDevice().empty())
|
||||||
|
{
|
||||||
|
result.SetAlsaInputDevice(result.GetLegacyAlsaDevice());
|
||||||
|
result.SetAlsaOutputDevice(result.GetLegacyAlsaDevice());
|
||||||
|
result.SetLegacyAlsaDevice("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#if JACK_HOST
|
#if JACK_HOST
|
||||||
result.Initialize();
|
result.Initialize();
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
valgrind --leak-check=full --log-file=vg.output ./build/src/pipedald /etc/pipedal/config ./vite/dist -port 0.0.0.0:8080 -log-level debug
|
valgrind --leak-check=full --log-file=vg.output ./build/src/pipedald /etc/pipedal/config ./vite/dist -port 0.0.0.0:8080 -log-level debug
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ export default class AlsaDeviceInfo {
|
|||||||
this.sampleRates = input.sampleRates as number[];
|
this.sampleRates = input.sampleRates as number[];
|
||||||
this.minBufferSize = input.minBufferSize;
|
this.minBufferSize = input.minBufferSize;
|
||||||
this.maxBufferSize = input.maxBufferSize;
|
this.maxBufferSize = input.maxBufferSize;
|
||||||
|
this.supportsCapture = input.supportsCapture ? true : false;
|
||||||
|
this.supportsPlayback = input.supportsPlayback ? true : false;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
static deserialize_array(input: any): AlsaDeviceInfo[]
|
static deserialize_array(input: any): AlsaDeviceInfo[]
|
||||||
|
|||||||
@@ -189,4 +189,46 @@ export default class JackHostStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDisplayViewNoCpu(label: string, status?: JackHostStatus): React.ReactNode {
|
||||||
|
if (!status) {
|
||||||
|
return (<div style={{ whiteSpace: "nowrap" }}>
|
||||||
|
<Typography variant="caption" color="inherit">{label}</Typography>
|
||||||
|
<Typography variant="caption"> </Typography>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
if (status.restarting) {
|
||||||
|
return (
|
||||||
|
<div style={{ whiteSpace: "nowrap" }}>
|
||||||
|
<Typography variant="caption" color="inherit">{label}</Typography>
|
||||||
|
<span style={{ color: RED_COLOR }}>
|
||||||
|
<Typography variant="caption" color="inherit">Restarting </Typography>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
} else if (!status.active) {
|
||||||
|
return (
|
||||||
|
<div style={{ whiteSpace: "nowrap" }}>
|
||||||
|
<Typography variant="caption" color="inherit">{label}</Typography>
|
||||||
|
|
||||||
|
<span style={{ color: RED_COLOR }}>
|
||||||
|
<Typography variant="caption" color="inherit">{status.errorMessage === "" ? "Audio\u00A0Stopped" : status.errorMessage} </Typography>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let underrunError = status.msSinceLastUnderrun < 15 * 1000;
|
||||||
|
return (
|
||||||
|
<div style={{ whiteSpace: "nowrap" }}>
|
||||||
|
<Typography variant="caption" color="inherit">{label}</Typography>
|
||||||
|
<span style={{ color: underrunError ? RED_COLOR : GREEN_COLOR }}>
|
||||||
|
<Typography variant="caption" color="inherit">
|
||||||
|
XRuns: {status.underruns + ""}
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -48,18 +48,44 @@ export default class JackServerSettings {
|
|||||||
isOnboarding: boolean = true;
|
isOnboarding: boolean = true;
|
||||||
rebootRequired = false;
|
rebootRequired = false;
|
||||||
isJackAudio = false;
|
isJackAudio = false;
|
||||||
alsaDevice: string = "";
|
alsaInputDevice: string = "";
|
||||||
|
alsaOutputDevice: string = "";
|
||||||
sampleRate = 48000;
|
sampleRate = 48000;
|
||||||
bufferSize = 64;
|
bufferSize = 64;
|
||||||
numberOfBuffers = 3;
|
numberOfBuffers = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure this instance to use the dummy audio device. This mirrors the
|
||||||
|
* behaviour of the backend JackServerSettings::UseDummyAudioDevice method
|
||||||
|
* and is used when temporarily releasing ALSA devices.
|
||||||
|
* Needed when changing devices and when testing new settings with apply button.
|
||||||
|
*/
|
||||||
|
useDummyAudioDevice() {
|
||||||
|
if (this.sampleRate === 0) {
|
||||||
|
this.sampleRate = 48000;
|
||||||
|
}
|
||||||
|
this.valid = true;
|
||||||
|
this.alsaInputDevice = "__DUMMY_AUDIO__dummy:channels_2";
|
||||||
|
this.alsaOutputDevice = "__DUMMY_AUDIO__dummy:channels_2";
|
||||||
|
}
|
||||||
|
|
||||||
getSummaryText() {
|
getSummaryText() {
|
||||||
if (this.valid) {
|
if (!this.valid || !this.alsaInputDevice || !this.alsaOutputDevice) {
|
||||||
let device = this.alsaDevice;
|
return "Not selected";
|
||||||
if (device.startsWith("hw:")) device = device.substring(3);
|
}
|
||||||
return device + " - Rate: " + this.sampleRate + " BufferSize: " + this.bufferSize + " Buffers: " + this.numberOfBuffers;
|
|
||||||
|
let inDev = this.alsaInputDevice.startsWith("hw:")
|
||||||
|
? this.alsaInputDevice.substring(3)
|
||||||
|
: this.alsaInputDevice;
|
||||||
|
let outDev = this.alsaOutputDevice.startsWith("hw:")
|
||||||
|
? this.alsaOutputDevice.substring(3)
|
||||||
|
: this.alsaOutputDevice;
|
||||||
|
|
||||||
|
if (inDev === outDev) {
|
||||||
|
return inDev;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return "Not configured";
|
return inDev+" → "+outDev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,9 @@
|
|||||||
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
import { Component } from 'react';
|
|
||||||
|
|
||||||
import { Theme } from '@mui/material/styles';
|
import { Theme } from '@mui/material/styles';
|
||||||
|
|
||||||
import WithStyles from './WithStyles';
|
import WithStyles from './WithStyles';
|
||||||
@@ -40,8 +39,36 @@ import DialogContent from '@mui/material/DialogContent';
|
|||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||||
|
import IconButtonEx from './IconButtonEx';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
|
||||||
import AlsaDeviceInfo from './AlsaDeviceInfo';
|
import AlsaDeviceInfo from './AlsaDeviceInfo';
|
||||||
|
import ResizeResponsiveComponent from './ResizeResponsiveComponent';
|
||||||
|
|
||||||
|
function filterDevices(devices: AlsaDeviceInfo[]): AlsaDeviceInfo[] {
|
||||||
|
return devices.filter(d => {
|
||||||
|
const name = (d.name + ' ' + d.longName).toLowerCase();
|
||||||
|
return !(name.includes('hdmi') || name.includes('bcm2835'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortDevices(devices: AlsaDeviceInfo[]): AlsaDeviceInfo[] {
|
||||||
|
function category(d: AlsaDeviceInfo): number {
|
||||||
|
if (d.supportsCapture && d.supportsPlayback) return 0;
|
||||||
|
if (d.longName.toLowerCase().includes("usb")) return 1;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
let copy = [...devices];
|
||||||
|
copy.sort((a,b) => {
|
||||||
|
let ca = category(a);
|
||||||
|
let cb = category(b);
|
||||||
|
if (ca !== cb) return ca - cb;
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
const MIN_BUFFER_SIZE = 16;
|
const MIN_BUFFER_SIZE = 16;
|
||||||
const MAX_BUFFER_SIZE = 2048;
|
const MAX_BUFFER_SIZE = 2048;
|
||||||
@@ -52,12 +79,17 @@ interface BufferSetting {
|
|||||||
numberOfBuffers: number;
|
numberOfBuffers: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const INVALID_DEVICE_ID = "_invalid_";
|
// empty string used when no valid device is selected - the default.
|
||||||
|
const INVALID_DEVICE_ID = "";
|
||||||
interface JackServerSettingsDialogState {
|
interface JackServerSettingsDialogState {
|
||||||
latencyText: string;
|
latencyText: string;
|
||||||
jackServerSettings: JackServerSettings;
|
jackServerSettings: JackServerSettings;
|
||||||
alsaDevices?: AlsaDeviceInfo[];
|
alsaDevices?: AlsaDeviceInfo[];
|
||||||
okEnabled: boolean;
|
okEnabled: boolean;
|
||||||
|
fullScreen: boolean;
|
||||||
|
compactWidth: boolean;
|
||||||
|
showDeviceWarning: boolean;
|
||||||
|
dontShowWarningAgain: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
@@ -69,6 +101,9 @@ const styles = (theme: Theme) =>
|
|||||||
selectEmpty: {
|
selectEmpty: {
|
||||||
marginTop: theme.spacing(2),
|
marginTop: theme.spacing(2),
|
||||||
},
|
},
|
||||||
|
inputLabel: {
|
||||||
|
whiteSpace: "nowrap"
|
||||||
|
}
|
||||||
});
|
});
|
||||||
export interface JackServerSettingsDialogProps extends WithStyles<typeof styles> {
|
export interface JackServerSettingsDialogProps extends WithStyles<typeof styles> {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -78,13 +113,15 @@ export interface JackServerSettingsDialogProps extends WithStyles<typeof styles>
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getLatencyText(settings?: JackServerSettings ): string {
|
function getLatencyText(settings?: JackServerSettings ): string {
|
||||||
if (!settings)
|
if (!settings) {
|
||||||
{
|
|
||||||
return "\u00A0";
|
return "\u00A0";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.valid) return "\u00A0";
|
if (!settings.sampleRate || !settings.bufferSize || !settings.numberOfBuffers) {
|
||||||
let ms = settings.bufferSize * settings.numberOfBuffers / settings.sampleRate * 1000;
|
return "\u00A0";
|
||||||
|
}
|
||||||
|
|
||||||
|
let ms = (settings.bufferSize * settings.numberOfBuffers) / settings.sampleRate * 1000;
|
||||||
return ms.toFixed(1) + "ms";
|
return ms.toFixed(1) + "ms";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,158 +208,286 @@ function getBestBuffers(
|
|||||||
return {bufferSize: bufferSize, numberOfBuffers:numberOfBuffers};
|
return {bufferSize: bufferSize, numberOfBuffers:numberOfBuffers};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// otherwise select a sensible starting value.
|
// otherwise select a sensible starting value.
|
||||||
|
|
||||||
// favored default: 64x3.
|
// Build candidate sizes with 32 first if present.
|
||||||
if (64*3 >= alsaDeviceInfo.minBufferSize && 64*3 <= alsaDeviceInfo.maxBufferSize)
|
let validBufferSizes = getValidBufferSizesMultiple(inDevice, outDevice);
|
||||||
{
|
validBufferSizes.sort((a,b)=>a-b);
|
||||||
return { bufferSize: 64, numberOfBuffers: 3};
|
if (validBufferSizes.indexOf(32) !== -1) {
|
||||||
|
validBufferSizes = [32, ...validBufferSizes.filter(v => v !== 32)];
|
||||||
}
|
}
|
||||||
// if that isn't possible then minBufferSize/2 x 4.
|
|
||||||
bufferSize = alsaDeviceInfo.minBufferSize/2;
|
function tryPick(count: number): BufferSetting | undefined {
|
||||||
|
for (let bs of validBufferSizes) {
|
||||||
|
const counts = getValidBufferCountsMultiple(bs, inDevice, outDevice);
|
||||||
|
if (counts.indexOf(count) !== -1) {
|
||||||
|
if (bs * count >= minSize && bs * count <= maxSize) {
|
||||||
|
return { bufferSize: bs, numberOfBuffers: count };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = tryPick(4);
|
||||||
|
if (!result) result = tryPick(3);
|
||||||
|
if (!result) result = tryPick(2);
|
||||||
|
if (result) return result;
|
||||||
|
|
||||||
|
// Fallback to previous behaviour.
|
||||||
|
if (64*3 >= minSize && 64*3 <= maxSize) {
|
||||||
|
return { bufferSize: 64, numberOfBuffers: 3 };
|
||||||
|
}
|
||||||
|
bufferSize = minSize/2;
|
||||||
numberOfBuffers = 4;
|
numberOfBuffers = 4;
|
||||||
// otherwise, minBufferSize/2 x 2.
|
if (bufferSize * numberOfBuffers > maxSize) {
|
||||||
if (bufferSize*numberOfBuffers > alsaDeviceInfo.maxBufferSize)
|
|
||||||
{
|
|
||||||
numberOfBuffers = 2;
|
numberOfBuffers = 2;
|
||||||
}
|
}
|
||||||
return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers};
|
return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers };
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function isOkEnabled(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[])
|
function isOkEnabled(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[])
|
||||||
{
|
{
|
||||||
if (!jackServerSettings.valid) return false;
|
|
||||||
if (!alsaDevices) return false;
|
if (!alsaDevices) return false;
|
||||||
let alsaDevice: AlsaDeviceInfo | undefined = undefined;
|
if (!jackServerSettings.alsaInputDevice || !jackServerSettings.alsaOutputDevice) return false;
|
||||||
for (let i = 0; i < alsaDevices.length; ++i)
|
|
||||||
{
|
const inDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaInputDevice && d.supportsCapture);
|
||||||
if (alsaDevices[i].id === jackServerSettings.alsaDevice)
|
const outDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaOutputDevice && d.supportsPlayback);
|
||||||
{
|
if (!inDevice || !outDevice) return false;
|
||||||
alsaDevice = alsaDevices[i];
|
|
||||||
}
|
const sampleRates = intersectArrays(inDevice.sampleRates, outDevice.sampleRates);
|
||||||
}
|
if (sampleRates.indexOf(jackServerSettings.sampleRate) === -1) return false;
|
||||||
if (!alsaDevice)
|
|
||||||
{
|
const deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers;
|
||||||
return false;
|
const minSize = Math.max(inDevice.minBufferSize, outDevice.minBufferSize);
|
||||||
}
|
const maxSize = Math.min(inDevice.maxBufferSize, outDevice.maxBufferSize);
|
||||||
let deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers;
|
if (deviceBufferSize < minSize || deviceBufferSize > maxSize) return false;
|
||||||
if (deviceBufferSize < alsaDevice.minBufferSize || deviceBufferSize > alsaDevice.maxBufferSize)
|
|
||||||
{
|
const validBufferCounts = getValidBufferCountsMultiple(jackServerSettings.bufferSize, inDevice, outDevice);
|
||||||
return false;
|
if (validBufferCounts.indexOf(jackServerSettings.numberOfBuffers) === -1) return false;
|
||||||
}
|
|
||||||
let validBufferCounts = getValidBufferCounts(jackServerSettings.bufferSize, alsaDevice);
|
|
||||||
let ix = validBufferCounts.indexOf(jackServerSettings.numberOfBuffers);
|
|
||||||
if (ix === -1)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const JackServerSettingsDialog = withStyles(
|
const JackServerSettingsDialog = withStyles(
|
||||||
class extends Component<JackServerSettingsDialogProps, JackServerSettingsDialogState> {
|
class extends ResizeResponsiveComponent<JackServerSettingsDialogProps, JackServerSettingsDialogState> {
|
||||||
|
|
||||||
model: PiPedalModel;
|
model: PiPedalModel;
|
||||||
|
ignoreClose: boolean = false;
|
||||||
|
|
||||||
constructor(props: JackServerSettingsDialogProps) {
|
constructor(props: JackServerSettingsDialogProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.model = PiPedalModelFactory.getInstance();
|
this.model = PiPedalModelFactory.getInstance();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.suppressDeviceWarning = localStorage.getItem("suppressSeparateDeviceWarning") === "1";
|
||||||
this.state = {
|
this.state = {
|
||||||
latencyText: getLatencyText(props.jackServerSettings),
|
latencyText: getLatencyText(props.jackServerSettings),
|
||||||
jackServerSettings: props.jackServerSettings.clone(), // invalid, but not nullish
|
jackServerSettings: props.jackServerSettings.clone(), // invalid, but not nullish
|
||||||
alsaDevices: undefined,
|
alsaDevices: undefined,
|
||||||
okEnabled: false
|
okEnabled: false,
|
||||||
|
fullScreen: this.getFullScreen(),
|
||||||
|
compactWidth: document.documentElement.clientWidth < 600,
|
||||||
|
showDeviceWarning: false,
|
||||||
|
dontShowWarningAgain: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
mounted: boolean = false;
|
mounted: boolean = false;
|
||||||
|
|
||||||
|
suppressDeviceWarning: boolean;
|
||||||
|
/**
|
||||||
|
* Copy of the settings when the dialog is opened. Pressing Apply only tests these settings temporarily. Closing the dialog without OK re-applies the saved copy so the audio driver returns to its previous configuration.
|
||||||
|
*/
|
||||||
|
originalJackServerSettings?: JackServerSettings;
|
||||||
|
|
||||||
|
getFullScreen() {
|
||||||
|
return document.documentElement.clientWidth < 420 ||
|
||||||
|
document.documentElement.clientHeight < 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowSizeChanged(width: number, height: number): void {
|
||||||
|
super.onWindowSizeChanged(width, height);
|
||||||
|
const fullScreen = this.getFullScreen();
|
||||||
|
const compactWidth = width < 600;
|
||||||
|
if (fullScreen !== this.state.fullScreen || compactWidth !== this.state.compactWidth) {
|
||||||
|
this.setState({ fullScreen, compactWidth });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
requestAlsaInfo() {
|
requestAlsaInfo() {
|
||||||
this.model.getAlsaDevices()
|
this.model.getAlsaDevices()
|
||||||
.then((devices) => {
|
.then((devices) => {
|
||||||
if (this.mounted) {
|
if (this.mounted) {
|
||||||
if (this.props.open) {
|
if (this.props.open) {
|
||||||
let settings = this.applyAlsaDevices(this.props.jackServerSettings, devices);
|
let f = filterDevices(devices);
|
||||||
|
let settings = this.applyAlsaDevices(this.state.jackServerSettings, f);
|
||||||
this.setState({
|
this.setState({
|
||||||
alsaDevices: devices,
|
alsaDevices: f,
|
||||||
jackServerSettings: settings,
|
jackServerSettings: settings,
|
||||||
latencyText: getLatencyText(settings),
|
latencyText: getLatencyText(settings),
|
||||||
okEnabled: isOkEnabled(settings,devices)
|
okEnabled: isOkEnabled(settings, f)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({ alsaDevices: devices });
|
this.setState({ alsaDevices: filterDevices(devices) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
// Error requesting ALSA info.
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** Persist the current settings to permanent storage. */
|
||||||
|
saveSettings(settings?: JackServerSettings): void {
|
||||||
|
const s = (settings ?? this.state.jackServerSettings).clone();
|
||||||
|
// Fire and forget. Errors will be handled by PiPedalModel's internal error handling (e.g., showAlert).
|
||||||
|
this.model.setJackServerSettings(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the current settings temporarily so they can be restored if the
|
||||||
|
* dialog is cancelled.
|
||||||
|
*/
|
||||||
|
saveSettingsTemporary(settings?: JackServerSettings) {
|
||||||
|
this.originalJackServerSettings = (settings ?? this.state.jackServerSettings).clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the provided settings to the audio system without persisting
|
||||||
|
* them. Falls back to the regular setter if the temporary apply API is
|
||||||
|
* unavailable.
|
||||||
|
*/
|
||||||
|
applySettings(settings?: JackServerSettings): void {
|
||||||
|
const s = (settings ?? this.state.jackServerSettings).clone();
|
||||||
|
s.valid = true;
|
||||||
|
|
||||||
|
const ws = this.model.webSocket;
|
||||||
|
if (ws) {
|
||||||
|
ws.request<void>("applyJackServerSettings", s)
|
||||||
|
.catch(() => { }); // Fire and forget
|
||||||
|
} else {
|
||||||
|
this.model.setJackServerSettings(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revert the dialog state to the persisted settings. The reverted
|
||||||
|
* settings are applied immediately as well.
|
||||||
|
*/
|
||||||
|
revertSettings() {
|
||||||
|
this.model.getJackServerSettings()
|
||||||
|
.then((s) => {
|
||||||
|
const applied = this.applyAlsaDevices(s.clone(), this.state.alsaDevices);
|
||||||
|
this.setState({
|
||||||
|
jackServerSettings: applied,
|
||||||
|
latencyText: getLatencyText(applied),
|
||||||
|
okEnabled: isOkEnabled(applied, this.state.alsaDevices)
|
||||||
|
});
|
||||||
|
this.applySettings(s);
|
||||||
|
})
|
||||||
|
.catch(() => { });
|
||||||
|
}
|
||||||
|
|
||||||
applyAlsaDevices(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) {
|
applyAlsaDevices(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) {
|
||||||
let result = jackServerSettings.clone();
|
let result = jackServerSettings.clone();
|
||||||
if (!alsaDevices) {
|
if (!alsaDevices) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result.valid = false;
|
|
||||||
if (alsaDevices.length === 0) {
|
let devices = filterDevices(alsaDevices);
|
||||||
result.alsaDevice = INVALID_DEVICE_ID;
|
let inDevice = devices.find(d => d.id === result.alsaInputDevice && d.supportsCapture);
|
||||||
|
let outDevice = devices.find(d => d.id === result.alsaOutputDevice && d.supportsPlayback);
|
||||||
|
|
||||||
|
if (!inDevice || !outDevice) {
|
||||||
|
const capture = devices.filter(d => d.supportsCapture);
|
||||||
|
const playback = devices.filter(d => d.supportsPlayback);
|
||||||
|
if (capture.length === 1 && playback.length === 1) {
|
||||||
|
inDevice = capture[0];
|
||||||
|
outDevice = playback[0];
|
||||||
|
result.alsaInputDevice = inDevice.id;
|
||||||
|
result.alsaOutputDevice = outDevice.id;
|
||||||
|
} else if (capture.length === 1 && capture[0].supportsPlayback && playback.length === 0) {
|
||||||
|
inDevice = capture[0];
|
||||||
|
outDevice = capture[0];
|
||||||
|
result.alsaInputDevice = inDevice.id;
|
||||||
|
result.alsaOutputDevice = outDevice.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inDevice || !outDevice) {
|
||||||
|
result.valid = false;
|
||||||
|
if (!inDevice) result.alsaInputDevice = INVALID_DEVICE_ID;
|
||||||
|
if (!outDevice) result.alsaOutputDevice = INVALID_DEVICE_ID;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectedDevice: AlsaDeviceInfo | undefined = undefined;
|
let sampleRates = intersectArrays(inDevice.sampleRates, outDevice.sampleRates);
|
||||||
for (let i = 0; i < alsaDevices.length; ++i) {
|
if (sampleRates.length !== 0 && sampleRates.indexOf(result.sampleRate) === -1) {
|
||||||
if (alsaDevices[i].id === result.alsaDevice) {
|
let bestSr = sampleRates[0];
|
||||||
selectedDevice = alsaDevices[i]
|
let bestErr = 1e36;
|
||||||
break;
|
for (let sr of sampleRates) {
|
||||||
|
let err = (sr - result.sampleRate) * (sr - result.sampleRate);
|
||||||
|
if (err < bestErr) { bestErr = err; bestSr = sr; }
|
||||||
}
|
}
|
||||||
|
result.sampleRate = bestSr;
|
||||||
}
|
}
|
||||||
if (!selectedDevice) {
|
|
||||||
selectedDevice = alsaDevices[0];
|
|
||||||
result.alsaDevice = selectedDevice.id;
|
|
||||||
|
|
||||||
}
|
let bestBuffers = getBestBuffers(inDevice, outDevice, result.bufferSize, result.numberOfBuffers);
|
||||||
if (result.sampleRate === 0) result.sampleRate = 48000;
|
|
||||||
result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate);
|
|
||||||
|
|
||||||
let bestBuffers = getBestBuffers(selectedDevice,result.bufferSize,result.numberOfBuffers);
|
|
||||||
result.bufferSize = bestBuffers.bufferSize;
|
result.bufferSize = bestBuffers.bufferSize;
|
||||||
result.numberOfBuffers = bestBuffers.numberOfBuffers;
|
result.numberOfBuffers = bestBuffers.numberOfBuffers;
|
||||||
|
|
||||||
result.valid = true;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
super.componentDidMount();
|
||||||
this.mounted = true;
|
this.mounted = true;
|
||||||
|
this.ignoreClose = false;
|
||||||
if (this.props.open) {
|
if (this.props.open) {
|
||||||
this.requestAlsaInfo();
|
this.requestAlsaInfo();
|
||||||
|
this.saveSettingsTemporary(this.props.jackServerSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(oldProps: JackServerSettingsDialogProps) {
|
componentDidUpdate(oldProps: JackServerSettingsDialogProps) {
|
||||||
if ((this.props.open && !oldProps.open) && this.mounted) {
|
if ((this.props.open && !oldProps.open) && this.mounted) {
|
||||||
let settings = this.applyAlsaDevices(this.props.jackServerSettings.clone(), this.state.alsaDevices);
|
this.ignoreClose = false;
|
||||||
this.setState({
|
this.saveSettingsTemporary(this.props.jackServerSettings);
|
||||||
|
// When opening, preserve the current settings until ALSA device
|
||||||
|
// information is loaded. If we don't have device info yet, just
|
||||||
|
// clone the provided settings without applying device defaults.
|
||||||
|
let settings = this.state.alsaDevices
|
||||||
|
? this.applyAlsaDevices(this.props.jackServerSettings.clone(), this.state.alsaDevices)
|
||||||
|
: this.props.jackServerSettings.clone();
|
||||||
|
|
||||||
|
this.setState({
|
||||||
jackServerSettings: settings,
|
jackServerSettings: settings,
|
||||||
latencyText: getLatencyText(settings),
|
latencyText: getLatencyText(settings),
|
||||||
okEnabled: isOkEnabled(settings,this.state.alsaDevices)
|
okEnabled: isOkEnabled(settings, this.state.alsaDevices)
|
||||||
});
|
});
|
||||||
if (!this.state.alsaDevices) {
|
if (!this.state.alsaDevices) {
|
||||||
this.requestAlsaInfo();
|
this.requestAlsaInfo();
|
||||||
}
|
}
|
||||||
}
|
} else if (!this.props.open && oldProps.open) {
|
||||||
|
this.originalJackServerSettings = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
super.componentWillUnmount();
|
||||||
this.mounted = false;
|
this.mounted = false;
|
||||||
|
if (this.originalJackServerSettings) {
|
||||||
|
// Revert any unapplied changes when the dialog is unmounted
|
||||||
|
this.applySettings(this.originalJackServerSettings);
|
||||||
|
this.originalJackServerSettings = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
getSelectedDevice(deviceId: string): AlsaDeviceInfo | null {
|
|
||||||
if (!this.state.alsaDevices) return null;
|
getSelectedDevice(deviceId: string): AlsaDeviceInfo | undefined {
|
||||||
|
if (!this.state.alsaDevices) return undefined;
|
||||||
for (let i = 0; i < this.state.alsaDevices.length; ++i) {
|
for (let i = 0; i < this.state.alsaDevices.length; ++i) {
|
||||||
if (this.state.alsaDevices[i].id === deviceId) {
|
if (this.state.alsaDevices[i].id === deviceId) {
|
||||||
return this.state.alsaDevices[i];
|
return this.state.alsaDevices[i];
|
||||||
@@ -354,6 +519,7 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
if (!selectedDevice) return;
|
if (!selectedDevice) return;
|
||||||
let settings = this.state.jackServerSettings.clone();
|
let settings = this.state.jackServerSettings.clone();
|
||||||
settings.sampleRate = rate;
|
settings.sampleRate = rate;
|
||||||
|
settings.valid = false;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
jackServerSettings: settings,
|
jackServerSettings: settings,
|
||||||
@@ -368,7 +534,8 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
if (!selectedDevice) return;
|
if (!selectedDevice) return;
|
||||||
let settings = this.state.jackServerSettings.clone();
|
let settings = this.state.jackServerSettings.clone();
|
||||||
settings.bufferSize = size;
|
settings.bufferSize = size;
|
||||||
let bestBufferSetting = getBestBuffers(selectedDevice,settings.bufferSize,settings.numberOfBuffers,true);
|
settings.valid = false;
|
||||||
|
let bestBufferSetting = getBestBuffers(inDev,outDev,settings.bufferSize,settings.numberOfBuffers,true);
|
||||||
settings.bufferSize = bestBufferSetting.bufferSize;
|
settings.bufferSize = bestBufferSetting.bufferSize;
|
||||||
settings.numberOfBuffers = bestBufferSetting.numberOfBuffers;
|
settings.numberOfBuffers = bestBufferSetting.numberOfBuffers;
|
||||||
|
|
||||||
@@ -385,6 +552,7 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
if (!selectedDevice) return;
|
if (!selectedDevice) return;
|
||||||
let settings = this.state.jackServerSettings.clone();
|
let settings = this.state.jackServerSettings.clone();
|
||||||
settings.numberOfBuffers = bufferCount;
|
settings.numberOfBuffers = bufferCount;
|
||||||
|
settings.valid = false;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
jackServerSettings: settings,
|
jackServerSettings: settings,
|
||||||
@@ -394,73 +562,177 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleApply() {
|
handleApply() {
|
||||||
if (this.state.okEnabled)
|
if (this.state.okEnabled) {
|
||||||
{
|
this.applySettings(); // Fire and forget
|
||||||
this.props.onApply(this.state.jackServerSettings.clone());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
handleOk() {
|
||||||
|
if (!this.state.okEnabled) return;
|
||||||
|
|
||||||
|
const proceedWithOk = () => {
|
||||||
|
const settings = this.state.jackServerSettings.clone();
|
||||||
|
settings.valid = true;
|
||||||
|
this.ignoreClose = true; // Indicate that the closing is intentional
|
||||||
|
this.applySettings(settings); // Fire and forget
|
||||||
|
this.saveSettings(settings); // Fire and forget
|
||||||
|
this.originalJackServerSettings = undefined;
|
||||||
|
if (this.props.onApply) {
|
||||||
|
this.props.onApply(settings.clone());
|
||||||
|
}
|
||||||
|
this.props.onClose(); // Close the dialog
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.state.jackServerSettings.alsaInputDevice !== this.state.jackServerSettings.alsaOutputDevice
|
||||||
|
&& !this.suppressDeviceWarning) {
|
||||||
|
this.setState({ showDeviceWarning: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
proceedWithOk();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleWarningProceed() {
|
||||||
|
if (this.state.dontShowWarningAgain) {
|
||||||
|
localStorage.setItem("suppressSeparateDeviceWarning", "1");
|
||||||
|
this.suppressDeviceWarning = true;
|
||||||
|
}
|
||||||
|
this.setState({ showDeviceWarning: false, dontShowWarningAgain: false }, () => {
|
||||||
|
const settings = this.state.jackServerSettings.clone();
|
||||||
|
settings.valid = true;
|
||||||
|
this.ignoreClose = true;
|
||||||
|
this.applySettings(settings); // Fire and forget
|
||||||
|
this.saveSettings(settings); // Fire and forget
|
||||||
|
this.originalJackServerSettings = undefined;
|
||||||
|
if (this.props.onApply) {
|
||||||
|
this.props.onApply(settings.clone());
|
||||||
|
}
|
||||||
|
this.props.onClose(); // Close the dialog after the warning
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWarningCancel() {
|
||||||
|
this.setState({ showDeviceWarning: false, dontShowWarningAgain: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWarningCheck(e: any, checked: boolean) {
|
||||||
|
this.setState({ dontShowWarningAgain: checked });
|
||||||
|
}
|
||||||
|
handleInputDeviceChanged(e: any) {
|
||||||
|
const d = e.target.value as string;
|
||||||
|
let s = this.state.jackServerSettings.clone();
|
||||||
|
s.alsaInputDevice = d;
|
||||||
|
s.valid = false;
|
||||||
|
let settings = this.applyAlsaDevices(s, this.state.alsaDevices);
|
||||||
|
this.setState({
|
||||||
|
jackServerSettings: settings,
|
||||||
|
latencyText: getLatencyText(settings),
|
||||||
|
okEnabled: isOkEnabled(settings, this.state.alsaDevices)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOutputDeviceChanged(e: any) {
|
||||||
|
const d = e.target.value as string;
|
||||||
|
let s = this.state.jackServerSettings.clone();
|
||||||
|
s.alsaOutputDevice = d;
|
||||||
|
s.valid = false;
|
||||||
|
let settings = this.applyAlsaDevices(s, this.state.alsaDevices);
|
||||||
|
this.setState({
|
||||||
|
jackServerSettings: settings,
|
||||||
|
latencyText: getLatencyText(settings),
|
||||||
|
okEnabled: isOkEnabled(settings, this.state.alsaDevices)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
const classes = withStyles.getClasses(this.props);
|
const classes = withStyles.getClasses(this.props);
|
||||||
|
|
||||||
const { onClose, open } = this.props;
|
const { onClose, open } = this.props;
|
||||||
|
|
||||||
|
//Ignore close rutine if the ignoreclose is true. (After OK or Proceed or Multi Device Warning)
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose();
|
if (this.ignoreClose) {
|
||||||
};
|
return;
|
||||||
let waitingForDevices = !this.state.alsaDevices
|
} else {
|
||||||
let noDevices = this.state.alsaDevices && this.state.alsaDevices.length === 0;
|
if (this.originalJackServerSettings) {
|
||||||
let selectedDevice: AlsaDeviceInfo | undefined = undefined;
|
// Revert any applied settings
|
||||||
if (this.state.alsaDevices) {
|
this.applySettings(this.originalJackServerSettings); // Fire and forget
|
||||||
for (let device of this.state.alsaDevices) {
|
this.originalJackServerSettings = undefined;
|
||||||
if (device.id === this.state.jackServerSettings.alsaDevice) {
|
|
||||||
selectedDevice = device;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
onClose();
|
||||||
let bufferSizes: number[] = getValidBufferSizes(selectedDevice);
|
};
|
||||||
let bufferCounts = getValidBufferCounts(this.state.jackServerSettings.bufferSize,selectedDevice);
|
|
||||||
let bufferSizeDisabled = !selectedDevice;
|
const sortedDevices = sortDevices(this.state.alsaDevices ?? []);
|
||||||
let bufferCountDisabled = !selectedDevice;
|
|
||||||
|
let selectedInputDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaInputDevice);
|
||||||
|
let selectedOutputDevice = this.getSelectedDevice(this.state.jackServerSettings.alsaOutputDevice);
|
||||||
|
|
||||||
|
const devicesSelected = (selectedInputDevice && selectedOutputDevice);
|
||||||
|
|
||||||
|
let bufferSizes: number[] = devicesSelected ?
|
||||||
|
getValidBufferSizesMultiple(selectedInputDevice, selectedOutputDevice) : [];
|
||||||
|
let bufferCounts = devicesSelected ?
|
||||||
|
getValidBufferCountsMultiple(this.state.jackServerSettings.bufferSize, selectedInputDevice, selectedOutputDevice) : [];
|
||||||
|
let bufferSizeDisabled = !devicesSelected;
|
||||||
|
let bufferCountDisabled = !devicesSelected;
|
||||||
|
let sampleRates = devicesSelected && selectedInputDevice && selectedOutputDevice ?
|
||||||
|
intersectArrays(selectedInputDevice.sampleRates, selectedOutputDevice.sampleRates)
|
||||||
|
: [];
|
||||||
|
let sampleRateOptions = sampleRates.length === 0 && this.state.jackServerSettings.sampleRate ? [this.state.jackServerSettings.sampleRate] : sampleRates;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<DialogEx tag="jack" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
|
<DialogEx tag="jack" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
|
||||||
onEnterKey={() => {
|
onEnterKey={() => {
|
||||||
this.handleApply();
|
this.handleOk();
|
||||||
}}
|
}}
|
||||||
|
fullScreen={this.state.fullScreen}
|
||||||
|
|
||||||
>
|
>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div>
|
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||||
<FormControl className={classes.formControl}>
|
<div style={{ display: "flex", flexDirection: "row", alignItems: "center", gap: 8 }}>
|
||||||
<InputLabel htmlFor="jsd_device">Device</InputLabel>
|
<div style={{ display: "flex", flexDirection: this.state.compactWidth ? "column" : "row", gap: 8 }}>
|
||||||
<Select variant="standard" onChange={(e) => this.handleDeviceChanged(e)}
|
{/* Audio Input Device */}
|
||||||
value={this.state.jackServerSettings.alsaDevice}
|
<FormControl variant="standard" className={classes.formControl}>
|
||||||
style={{width: 220}}
|
<InputLabel shrink className={classes.inputLabel} htmlFor="jsd_inputDevice">Input Device</InputLabel>
|
||||||
inputProps={{
|
<Select variant="standard"
|
||||||
name: "Device",
|
id="jsd_inputDevice"
|
||||||
id: "jsd_device",
|
value={this.state.jackServerSettings.alsaInputDevice}
|
||||||
}}
|
onChange={e => this.handleInputDeviceChanged(e)}
|
||||||
|
disabled={!this.state.alsaDevices || this.state.alsaDevices.length === 0}
|
||||||
|
style={{ width: 220 }}
|
||||||
>
|
>
|
||||||
{(noDevices && !waitingForDevices) &&
|
{sortedDevices.filter(d => d.supportsCapture).map(d => (
|
||||||
(
|
<MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>
|
||||||
<MenuItem value={INVALID_DEVICE_ID}>No suitable devices.</MenuItem>
|
)) || <MenuItem value="" disabled>Loading...</MenuItem>}
|
||||||
)
|
|
||||||
}
|
|
||||||
{((!noDevices) && !waitingForDevices) && (
|
|
||||||
this.state.alsaDevices!.map((device) =>
|
|
||||||
(
|
|
||||||
<MenuItem key={device.id} value={device.id}>{device.name}</MenuItem>
|
|
||||||
)
|
|
||||||
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
|
{/* Audio Output Device */}
|
||||||
|
<FormControl variant="standard" className={classes.formControl}>
|
||||||
|
<InputLabel shrink className={classes.inputLabel} htmlFor="jsd_outputDevice">Output Device</InputLabel>
|
||||||
|
<Select variant="standard"
|
||||||
|
id="jsd_outputDevice"
|
||||||
|
value={this.state.jackServerSettings.alsaOutputDevice}
|
||||||
|
onChange={e => this.handleOutputDeviceChanged(e)}
|
||||||
|
disabled={!this.state.alsaDevices || this.state.alsaDevices.length === 0}
|
||||||
|
style={{ width: 220 }}
|
||||||
|
>
|
||||||
|
{sortedDevices.filter(d => d.supportsPlayback).map(d => (
|
||||||
|
<MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>
|
||||||
|
)) || <MenuItem value="" disabled>Loading...</MenuItem>}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</div>
|
||||||
|
<IconButtonEx tooltip="Refresh devices" onClick={() => this.requestAlsaInfo()} aria-label="refresh-audio-devices">
|
||||||
|
<RefreshIcon />
|
||||||
|
</IconButtonEx>
|
||||||
|
</div>
|
||||||
</div><div>
|
</div><div>
|
||||||
<FormControl className={classes.formControl}>
|
<FormControl variant="standard" className={classes.formControl}>
|
||||||
<InputLabel htmlFor="jsd_sampleRate">Sample rate</InputLabel>
|
<InputLabel shrink className={classes.inputLabel} htmlFor="jsd_sampleRate">Sample rate</InputLabel>
|
||||||
<Select variant="standard"
|
<Select variant="standard"
|
||||||
onChange={(e) => this.handleRateChanged(e)}
|
onChange={(e) => this.handleRateChanged(e)}
|
||||||
value={this.state.jackServerSettings.sampleRate}
|
value={this.state.jackServerSettings.sampleRate}
|
||||||
@@ -472,16 +744,14 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selectedDevice &&
|
{sampleRateOptions.map((sr) => {
|
||||||
selectedDevice.sampleRates.map((sr) => {
|
return (<MenuItem value={sr}>{sr}</MenuItem> );
|
||||||
return ( <MenuItem value={sr}>{sr}</MenuItem> );
|
})}
|
||||||
})
|
|
||||||
}
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<div style={{ display: "inline", whiteSpace: "nowrap" }}>
|
<div style={{ display: "inline", whiteSpace: "nowrap" }}>
|
||||||
<FormControl className={classes.formControl}>
|
<FormControl variant="standard" className={classes.formControl}>
|
||||||
<InputLabel htmlFor="bufferSize">Buffer size</InputLabel>
|
<InputLabel shrink className={classes.inputLabel} htmlFor="bufferSize">Buffer size</InputLabel>
|
||||||
<Select variant="standard"
|
<Select variant="standard"
|
||||||
onChange={(e) => this.handleSizeChanged(e)}
|
onChange={(e) => this.handleSizeChanged(e)}
|
||||||
value={this.state.jackServerSettings.bufferSize}
|
value={this.state.jackServerSettings.bufferSize}
|
||||||
@@ -499,8 +769,8 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
)}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl className={classes.formControl}>
|
<FormControl variant="standard" className={classes.formControl}>
|
||||||
<InputLabel htmlFor="numberofBuffers">Buffers</InputLabel>
|
<InputLabel shrink className={classes.inputLabel} htmlFor="numberofBuffers">Buffers</InputLabel>
|
||||||
<Select variant="standard"
|
<Select variant="standard"
|
||||||
onChange={(e) => this.handleNumberOfBuffersChanged(e)}
|
onChange={(e) => this.handleNumberOfBuffersChanged(e)}
|
||||||
value={this.state.jackServerSettings.numberOfBuffers}
|
value={this.state.jackServerSettings.numberOfBuffers}
|
||||||
@@ -530,16 +800,39 @@ const JackServerSettingsDialog = withStyles(
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button variant="dialogSecondary" onClick={handleClose} >
|
<Button variant="dialogSecondary" onClick={handleClose}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="dialogPrimary" onClick={() => this.handleApply()}
|
<Button variant="dialogSecondary" onClick={() => this.handleApply()} disabled={!this.state.okEnabled}>Apply</Button>
|
||||||
disabled={!this.state.okEnabled}>
|
<Button variant="dialogPrimary" onClick={() => this.handleOk()} disabled={!this.state.okEnabled}>
|
||||||
OK
|
OK
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
|
|
||||||
</DialogEx>
|
</DialogEx>
|
||||||
|
{this.state.showDeviceWarning && (
|
||||||
|
<DialogEx open={this.state.showDeviceWarning} tag="ioWarning"
|
||||||
|
onEnterKey={() => this.handleWarningProceed()}
|
||||||
|
onClose={() => this.handleWarningCancel()}
|
||||||
|
style={{ userSelect: "none" }}
|
||||||
|
>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography variant="body2" color="textPrimary" gutterBottom>
|
||||||
|
Using different input and output devices may cause issues when streaming audio.
|
||||||
|
Are you sure you want to continue?
|
||||||
|
</Typography>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox checked={this.state.dontShowWarningAgain} onChange={(e,c)=>this.handleWarningCheck(e,c)} />}
|
||||||
|
label="Don't show me this message again"
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => this.handleWarningCancel()} variant="dialogSecondary" style={{ width: 120 }}>Cancel</Button>
|
||||||
|
<Button onClick={() => this.handleWarningProceed()} variant="dialogPrimary" style={{ width: 120 }}>PROCEED</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</DialogEx>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user