Merge branch 'splitio_base' of https://github.com/rerdavies/pipedal into dev_splitio_merge

This commit is contained in:
Robin E. R. Davies
2025-09-08 08:55:44 -04:00
24 changed files with 664 additions and 207 deletions
+1
View File
@@ -1,3 +1,4 @@
#!/bin/bash
wpa_cli -ip2p-dev-wlan0 GET device_name pipedal
wpa_cli -ip2p-dev-wlan0 GET country CA
wpa_cli -ip2p-dev-wlan0 GET device_type 1-0050F204-1
+1 -1
View File
@@ -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)
### [How to Debug PiPedal](https://rerdavies.github.io/pipedal/Debugging.html)
 
 
#### [PiPedal Architecture](https://rerdavies.github.io/pipedal/Architecture.html)
+1 -1
View File
@@ -6,4 +6,4 @@
# bundle install
#
cd docs
bundle exec jekyll serve --host
bundle exec jekyll serve --host 0.0.0.0
+1
View File
@@ -21,6 +21,7 @@ Run the following commands to install dependent libraries required by the PiPeda
sudo apt update
sudo apt upgrade
sudo apt install -y liblilv-dev libboost-dev \
libsystemd-dev catch libasound2-dev uuid-dev \
authbind libavahi-client-dev libnm-dev libicu-dev \
+3 -2
View File
@@ -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,
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)
./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.
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.
+2 -1
View File
@@ -1,3 +1,4 @@
#!/bin/bash
cd build
cpack -G DEB -C Release -config CPackConfig.cmake
cpack -G DEB -C Release -config CPackConfig.cmake
cd ..
+2
View File
@@ -1,3 +1,5 @@
#!/bin/bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
${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
+1
View File
@@ -1,3 +1,4 @@
#!/bin/bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
${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
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/bash
rm -rf ./lv2/aarch64/*
cp -R /usr/lib/lv2/ToobAmp.lv2/ ./lv2/aarch64/
+10 -4
View File
@@ -683,6 +683,7 @@ namespace pipedal
if (this->captureHandle)
{
this->alsa_device_name = this->jackServerSettings.GetAlsaInputDevice();
AlsaConfigureStream(
this->alsa_device_name,
"capture",
@@ -695,6 +696,7 @@ namespace pipedal
}
if (this->playbackHandle)
{
this->alsa_device_name = this->jackServerSettings.GetAlsaOutputDevice();
AlsaConfigureStream(
this->alsa_device_name,
"playback",
@@ -1326,7 +1328,9 @@ namespace pipedal
int err;
alsa_device_name = jackServerSettings.GetAlsaInputDevice();
std::string inputName = jackServerSettings.GetAlsaInputDevice();
std::string outputName = jackServerSettings.GetAlsaOutputDevice();
this->numberOfBuffers = jackServerSettings.GetNumberOfBuffers();
this->bufferSize = jackServerSettings.GetBufferSize();
@@ -1335,7 +1339,8 @@ namespace pipedal
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)
{
switch (errno)
@@ -1369,8 +1374,9 @@ namespace pipedal
{
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)
{
+1 -1
View File
@@ -64,7 +64,7 @@ public:
AlsaFormatEncodeDecodeTest(this);
JackServerSettings serverSettings("hw:M2",48000,32,3);
JackServerSettings serverSettings("hw:M2","hw:M2",48000,32,3);
JackConfiguration jackConfiguration;
if (useJack)
+11 -6
View File
@@ -140,14 +140,19 @@ namespace pipedal
overheadTime = readTime;
}
SampleT totalTime = writeTime+ readTime + processingTime;
SampleT maxTime = waitTime+processingTime;
SampleT totalTime = writeTime + readTime + processingTime;
SampleT maxTime = waitTime + processingTime;
float result = 100.0f*(processingTime)/(maxTime);
float overhead = 100.0F*(overheadTime*2)/totalTime;
float result = 0.0f;
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;
currentOverhead = overhead;
}
+2
View File
@@ -517,6 +517,8 @@ namespace pipedal
result.sampleRates_.push_back(48000);
result.minBufferSize_ = 16;
result.maxBufferSize_ = 1024;
result.supportsCapture_ = true;
result.supportsPlayback_ = true;
return result;
}
+10 -2
View File
@@ -148,7 +148,13 @@ void JackServerSettings::ReadJackDaemonConfiguration()
this->bufferSize_ = GetJackArg(argv, "-p", "--period");
this->numberOfBuffers_ = (uint32_t)GetJackArg(argv, "-n", "--nperiods");
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;
}
catch (std::exception &)
@@ -246,7 +252,9 @@ JSON_MAP_REFERENCE(JackServerSettings, valid)
JSON_MAP_REFERENCE(JackServerSettings, isOnboarding)
JSON_MAP_REFERENCE(JackServerSettings, rebootRequired)
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, bufferSize)
JSON_MAP_REFERENCE(JackServerSettings, numberOfBuffers)
+22 -15
View File
@@ -31,7 +31,9 @@ namespace pipedal
bool isOnboarding_ = true;
bool isJackAudio_ = JACK_HOST ? true : false;
bool rebootRequired_ = false;
std::string alsaDevice_;
std::string alsaInputDevice_;
std::string alsaOutputDevice_;
std::string alsaDevice_; // legacy
uint64_t sampleRate_ = 0;
uint32_t bufferSize_ = 64;
uint32_t numberOfBuffers_ = 3;
@@ -39,10 +41,14 @@ namespace pipedal
public:
JackServerSettings();
JackServerSettings(
const std::string alsaInputDevice,
uint64_t sampleRate, uint32_t bufferSize, uint32_t numberOfBuffers)
const std::string &alsaInputDevice,
const std::string &alsaOutputDevice,
uint64_t sampleRate,
uint32_t bufferSize,
uint32_t numberOfBuffers)
: valid_(true),
alsaDevice_(alsaInputDevice),
alsaInputDevice_(alsaInputDevice),
alsaOutputDevice_(alsaOutputDevice),
sampleRate_(sampleRate),
bufferSize_(bufferSize),
numberOfBuffers_(numberOfBuffers),
@@ -51,9 +57,13 @@ namespace pipedal
}
uint64_t GetSampleRate() const { return sampleRate_; }
uint32_t GetBufferSize() const { return bufferSize_; }
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() {
this->valid_ = true;
if (sampleRate_ == 0) sampleRate_ = 48000;
@@ -69,14 +79,6 @@ namespace pipedal
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 SetRebootRequired(bool value)
{
@@ -87,9 +89,14 @@ namespace pipedal
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);
+25 -16
View File
@@ -57,9 +57,9 @@ void PrintHelp()
pp << "Copyright (c) 2022 Robin Davies\n";
pp << "\n";
pp << Indent(0) << "Syntax\n\n";
pp << Indent(2) << "pipedal_latency_test [<options>] <device-name>\n\n";
pp << "where <device-name> is the name of an ALSA device. Typically this should be the name of a hardware "
"device (a device name starting with 'hw:').\n\n";
pp << Indent(2) << "pipedal_latency_test [<options>] <input-device> [<output-device>]\n\n";
pp << "where <input-device> is the name of an ALSA capture device and <output-device> is the name of a playback device. "
"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(15);
@@ -95,7 +95,8 @@ void PrintHelp()
pp << Indent(0) << "Examples\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()
@@ -138,7 +139,8 @@ public:
private:
AudioDriver *audioDriver = nullptr;
const std::string &deviceId;
const std::string &inputDeviceId;
const std::string &outputDeviceId;
ChannelsT inputChannels;
ChannelsT outputChannels;
uint32_t sampleRate;
@@ -147,11 +149,13 @@ private:
public:
AlsaTester(
const std::string &deviceId,
const std::string &inputDeviceId,
const std::string &outputDeviceId,
const ChannelsT &inputChannels,
const ChannelsT &outputChannels,
uint32_t sampleRate, int bufferSize, int buffers)
: deviceId(deviceId),
: inputDeviceId(inputDeviceId),
outputDeviceId(outputDeviceId),
sampleRate(sampleRate),
inputChannels(inputChannels),
outputChannels(outputChannels),
@@ -186,7 +190,7 @@ public:
TestResult result;
try
{
JackServerSettings serverSettings(deviceId, sampleRate, bufferSize, buffers);
JackServerSettings serverSettings(inputDeviceId, outputDeviceId, sampleRate, bufferSize, buffers);
JackConfiguration jackConfiguration;
jackConfiguration.AlsaInitialize(serverSettings);
@@ -403,12 +407,13 @@ public:
};
TestResult RunLatencyTest(
const std::string deviceId,
const std::string inputDeviceId,
const std::string outputDeviceId,
const ChannelsT &inputChannels,
const ChannelsT &outputChannels,
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();
}
@@ -428,13 +433,14 @@ static std::string overheadDisplay(float value)
}
void RunLatencyTest(
const std::string &deviceId,
const std::string &inputDeviceId,
const std::string &outputDeviceId,
const ChannelsT &inputChannels,
const ChannelsT &outputChannels,
uint32_t sampleRate)
{
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 BUFFERS_COLUMN_WIDTH = 20;
@@ -461,7 +467,7 @@ void RunLatencyTest(
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);
column += BUFFERS_COLUMN_WIDTH;
@@ -556,11 +562,14 @@ std:
{
ListDevices();
}
else if (parser.Arguments().size() == 1)
else if (parser.Arguments().size() >= 1 && parser.Arguments().size() <= 2)
{
inputChannels = ParseChannels(strInputChannels);
outputChannels = ParseChannels(strInputChannels);
RunLatencyTest(parser.Arguments()[0], inputChannels, outputChannels, sampleRate);
outputChannels = ParseChannels(strOutputChannels);
std::string inDev = parser.Arguments()[0];
std::string outDev = parser.Arguments().size() == 2 ? parser.Arguments()[1] : inDev;
RunLatencyTest(inDev, outDev, inputChannels, outputChannels, sampleRate);
}
else
{
+55 -16
View File
@@ -22,6 +22,7 @@
#include "alsa/asoundlib.h"
#include "Lv2Log.hpp"
#include <mutex>
#include <algorithm>
using namespace pipedal;
@@ -111,6 +112,7 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
}
if (err == 0)
{
snd_pcm_t *hDevice = captureOk ? captureDevice : playbackDevice; // xxx: HECK NO!
snd_pcm_hw_params_t *params = nullptr;
err = snd_pcm_hw_params_malloc(&params);
if (err == 0)
@@ -121,29 +123,42 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
unsigned int minRate = 0, maxRate = 0;
snd_pcm_uframes_t minBufferSize = 0, maxBufferSize = 0;
int dir;
err = snd_pcm_hw_params_get_rate_min(params, &minRate, &dir);
if (err == 0)
{
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)
{
err = snd_pcm_hw_params_get_buffer_size_min(params, &minBufferSize);
}
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)
if (err == 0)
{
uint32_t rate = RATES[i];
if (rate >= minRate && rate <= maxRate)
{
info.sampleRates_.push_back(rate);
}
err = snd_pcm_hw_params_get_buffer_size_max(params, &maxBufferSize);
}
}
if (err == 0)
{
if (minBufferSize < 16)
{
minBufferSize = 16;
@@ -158,7 +173,19 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
}
if (params != nullptr)
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
{
@@ -174,12 +201,24 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
}
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 --");
for (auto &device : result)
for (auto &device : filtered)
{
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)
+2
View File
@@ -31,6 +31,8 @@ namespace pipedal {
std::string longName_;
std::vector<uint32_t> sampleRates_;
uint32_t minBufferSize_ = 0,maxBufferSize_ = 0;
bool supportsCapture_ = false;
bool supportsPlayback_ = false;
bool isDummyDevice() const {
return id_.starts_with("dummy:");
+8
View File
@@ -1750,6 +1750,14 @@ pipedal::JackServerSettings Storage::GetJackServerSettings()
{
json_reader reader(f);
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
result.Initialize();
+1
View File
@@ -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
+2
View File
@@ -9,6 +9,8 @@ export default class AlsaDeviceInfo {
this.sampleRates = input.sampleRates as number[];
this.minBufferSize = input.minBufferSize;
this.maxBufferSize = input.maxBufferSize;
this.supportsCapture = input.supportsCapture ? true : false;
this.supportsPlayback = input.supportsPlayback ? true : false;
return this;
}
static deserialize_array(input: any): AlsaDeviceInfo[]
+42
View File
@@ -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">&nbsp;</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&nbsp;&nbsp;</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}&nbsp;&nbsp;</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:&nbsp;{status.underruns + ""}&nbsp;&nbsp;
</Typography>
</span>
</div>
);
}
}
};
+32 -6
View File
@@ -48,18 +48,44 @@ export default class JackServerSettings {
isOnboarding: boolean = true;
rebootRequired = false;
isJackAudio = false;
alsaDevice: string = "";
alsaInputDevice: string = "";
alsaOutputDevice: string = "";
sampleRate = 48000;
bufferSize = 64;
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() {
if (this.valid) {
let device = this.alsaDevice;
if (device.startsWith("hw:")) device = device.substring(3);
return device + " - Rate: " + this.sampleRate + " BufferSize: " + this.bufferSize + " Buffers: " + this.numberOfBuffers;
if (!this.valid || !this.alsaInputDevice || !this.alsaOutputDevice) {
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;
if (inDev === outDev) {
return inDev;
} else {
return "Not configured";
return inDev+" → "+outDev;
}
}
+428 -135
View File
@@ -16,10 +16,9 @@
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import { Component } from 'react';
import { Theme } from '@mui/material/styles';
import WithStyles from './WithStyles';
@@ -40,8 +39,36 @@ import DialogContent from '@mui/material/DialogContent';
import MenuItem from '@mui/material/MenuItem';
import Typography from '@mui/material/Typography';
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 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 MAX_BUFFER_SIZE = 2048;
@@ -52,12 +79,17 @@ interface BufferSetting {
numberOfBuffers: number;
};
const INVALID_DEVICE_ID = "_invalid_";
// empty string used when no valid device is selected - the default.
const INVALID_DEVICE_ID = "";
interface JackServerSettingsDialogState {
latencyText: string;
jackServerSettings: JackServerSettings;
alsaDevices?: AlsaDeviceInfo[];
okEnabled: boolean;
fullScreen: boolean;
compactWidth: boolean;
showDeviceWarning: boolean;
dontShowWarningAgain: boolean;
}
const styles = (theme: Theme) =>
@@ -69,6 +101,9 @@ const styles = (theme: Theme) =>
selectEmpty: {
marginTop: theme.spacing(2),
},
inputLabel: {
whiteSpace: "nowrap"
}
});
export interface JackServerSettingsDialogProps extends WithStyles<typeof styles> {
open: boolean;
@@ -78,13 +113,15 @@ export interface JackServerSettingsDialogProps extends WithStyles<typeof styles>
}
function getLatencyText(settings?: JackServerSettings ): string {
if (!settings)
{
if (!settings) {
return "\u00A0";
}
if (!settings.valid) return "\u00A0";
let ms = settings.bufferSize * settings.numberOfBuffers / settings.sampleRate * 1000;
if (!settings.sampleRate || !settings.bufferSize || !settings.numberOfBuffers) {
return "\u00A0";
}
let ms = (settings.bufferSize * settings.numberOfBuffers) / settings.sampleRate * 1000;
return ms.toFixed(1) + "ms";
}
@@ -171,158 +208,286 @@ function getBestBuffers(
return {bufferSize: bufferSize, numberOfBuffers:numberOfBuffers};
}
}
// otherwise select a sensible starting value.
// otherwise select a sensible starting value.
// favored default: 64x3.
if (64*3 >= alsaDeviceInfo.minBufferSize && 64*3 <= alsaDeviceInfo.maxBufferSize)
{
return { bufferSize: 64, numberOfBuffers: 3};
// Build candidate sizes with 32 first if present.
let validBufferSizes = getValidBufferSizesMultiple(inDevice, outDevice);
validBufferSizes.sort((a,b)=>a-b);
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;
// otherwise, minBufferSize/2 x 2.
if (bufferSize*numberOfBuffers > alsaDeviceInfo.maxBufferSize)
{
if (bufferSize * numberOfBuffers > maxSize) {
numberOfBuffers = 2;
}
return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers};
return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers };
};
function isOkEnabled(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[])
{
if (!jackServerSettings.valid) return false;
if (!alsaDevices) return false;
let alsaDevice: AlsaDeviceInfo | undefined = undefined;
for (let i = 0; i < alsaDevices.length; ++i)
{
if (alsaDevices[i].id === jackServerSettings.alsaDevice)
{
alsaDevice = alsaDevices[i];
}
}
if (!alsaDevice)
{
return false;
}
let deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers;
if (deviceBufferSize < alsaDevice.minBufferSize || deviceBufferSize > alsaDevice.maxBufferSize)
{
return false;
}
let validBufferCounts = getValidBufferCounts(jackServerSettings.bufferSize, alsaDevice);
let ix = validBufferCounts.indexOf(jackServerSettings.numberOfBuffers);
if (ix === -1)
{
return false;
}
if (!jackServerSettings.alsaInputDevice || !jackServerSettings.alsaOutputDevice) return false;
const inDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaInputDevice && d.supportsCapture);
const outDevice = alsaDevices.find(d => d.id === jackServerSettings.alsaOutputDevice && d.supportsPlayback);
if (!inDevice || !outDevice) return false;
const sampleRates = intersectArrays(inDevice.sampleRates, outDevice.sampleRates);
if (sampleRates.indexOf(jackServerSettings.sampleRate) === -1) return false;
const deviceBufferSize = jackServerSettings.bufferSize * jackServerSettings.numberOfBuffers;
const minSize = Math.max(inDevice.minBufferSize, outDevice.minBufferSize);
const maxSize = Math.min(inDevice.maxBufferSize, outDevice.maxBufferSize);
if (deviceBufferSize < minSize || deviceBufferSize > maxSize) return false;
const validBufferCounts = getValidBufferCountsMultiple(jackServerSettings.bufferSize, inDevice, outDevice);
if (validBufferCounts.indexOf(jackServerSettings.numberOfBuffers) === -1) return false;
return true;
}
const JackServerSettingsDialog = withStyles(
class extends Component<JackServerSettingsDialogProps, JackServerSettingsDialogState> {
class extends ResizeResponsiveComponent<JackServerSettingsDialogProps, JackServerSettingsDialogState> {
model: PiPedalModel;
ignoreClose: boolean = false;
constructor(props: JackServerSettingsDialogProps) {
super(props);
this.model = PiPedalModelFactory.getInstance();
this.suppressDeviceWarning = localStorage.getItem("suppressSeparateDeviceWarning") === "1";
this.state = {
latencyText: getLatencyText(props.jackServerSettings),
jackServerSettings: props.jackServerSettings.clone(), // invalid, but not nullish
alsaDevices: undefined,
okEnabled: false
okEnabled: false,
fullScreen: this.getFullScreen(),
compactWidth: document.documentElement.clientWidth < 600,
showDeviceWarning: false,
dontShowWarningAgain: 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() {
this.model.getAlsaDevices()
.then((devices) => {
if (this.mounted) {
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({
alsaDevices: devices,
alsaDevices: f,
jackServerSettings: settings,
latencyText: getLatencyText(settings),
okEnabled: isOkEnabled(settings,devices)
okEnabled: isOkEnabled(settings, f)
});
} else {
this.setState({ alsaDevices: devices });
this.setState({ alsaDevices: filterDevices(devices) });
}
}
})
.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[]) {
let result = jackServerSettings.clone();
if (!alsaDevices) {
return result;
}
result.valid = false;
if (alsaDevices.length === 0) {
result.alsaDevice = INVALID_DEVICE_ID;
let devices = filterDevices(alsaDevices);
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;
}
let selectedDevice: AlsaDeviceInfo | undefined = undefined;
for (let i = 0; i < alsaDevices.length; ++i) {
if (alsaDevices[i].id === result.alsaDevice) {
selectedDevice = alsaDevices[i]
break;
let sampleRates = intersectArrays(inDevice.sampleRates, outDevice.sampleRates);
if (sampleRates.length !== 0 && sampleRates.indexOf(result.sampleRate) === -1) {
let bestSr = sampleRates[0];
let bestErr = 1e36;
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;
}
if (result.sampleRate === 0) result.sampleRate = 48000;
result.sampleRate = selectedDevice.closestSampleRate(result.sampleRate);
let bestBuffers = getBestBuffers(selectedDevice,result.bufferSize,result.numberOfBuffers);
let bestBuffers = getBestBuffers(inDevice, outDevice, result.bufferSize, result.numberOfBuffers);
result.bufferSize = bestBuffers.bufferSize;
result.numberOfBuffers = bestBuffers.numberOfBuffers;
result.valid = true;
return result;
}
componentDidMount() {
super.componentDidMount();
this.mounted = true;
this.ignoreClose = false;
if (this.props.open) {
this.requestAlsaInfo();
this.saveSettingsTemporary(this.props.jackServerSettings);
}
}
componentDidUpdate(oldProps: JackServerSettingsDialogProps) {
if ((this.props.open && !oldProps.open) && this.mounted) {
let settings = this.applyAlsaDevices(this.props.jackServerSettings.clone(), this.state.alsaDevices);
this.setState({
this.ignoreClose = false;
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,
latencyText: getLatencyText(settings),
okEnabled: isOkEnabled(settings,this.state.alsaDevices)
});
okEnabled: isOkEnabled(settings, this.state.alsaDevices)
});
if (!this.state.alsaDevices) {
this.requestAlsaInfo();
}
}
} else if (!this.props.open && oldProps.open) {
this.originalJackServerSettings = undefined;
}
}
componentWillUnmount() {
super.componentWillUnmount();
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) {
if (this.state.alsaDevices[i].id === deviceId) {
return this.state.alsaDevices[i];
@@ -354,6 +519,7 @@ const JackServerSettingsDialog = withStyles(
if (!selectedDevice) return;
let settings = this.state.jackServerSettings.clone();
settings.sampleRate = rate;
settings.valid = false;
this.setState({
jackServerSettings: settings,
@@ -368,7 +534,8 @@ const JackServerSettingsDialog = withStyles(
if (!selectedDevice) return;
let settings = this.state.jackServerSettings.clone();
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.numberOfBuffers = bestBufferSetting.numberOfBuffers;
@@ -385,6 +552,7 @@ const JackServerSettingsDialog = withStyles(
if (!selectedDevice) return;
let settings = this.state.jackServerSettings.clone();
settings.numberOfBuffers = bufferCount;
settings.valid = false;
this.setState({
jackServerSettings: settings,
@@ -394,73 +562,177 @@ const JackServerSettingsDialog = withStyles(
}
handleApply() {
if (this.state.okEnabled)
{
this.props.onApply(this.state.jackServerSettings.clone());
if (this.state.okEnabled) {
this.applySettings(); // Fire and forget
}
};
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 { onClose, open } = this.props;
//Ignore close rutine if the ignoreclose is true. (After OK or Proceed or Multi Device Warning)
const handleClose = () => {
onClose();
};
let waitingForDevices = !this.state.alsaDevices
let noDevices = this.state.alsaDevices && this.state.alsaDevices.length === 0;
let selectedDevice: AlsaDeviceInfo | undefined = undefined;
if (this.state.alsaDevices) {
for (let device of this.state.alsaDevices) {
if (device.id === this.state.jackServerSettings.alsaDevice) {
selectedDevice = device;
break;
if (this.ignoreClose) {
return;
} else {
if (this.originalJackServerSettings) {
// Revert any applied settings
this.applySettings(this.originalJackServerSettings); // Fire and forget
this.originalJackServerSettings = undefined;
}
}
}
let bufferSizes: number[] = getValidBufferSizes(selectedDevice);
let bufferCounts = getValidBufferCounts(this.state.jackServerSettings.bufferSize,selectedDevice);
let bufferSizeDisabled = !selectedDevice;
let bufferCountDisabled = !selectedDevice;
onClose();
};
const sortedDevices = sortDevices(this.state.alsaDevices ?? []);
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 (
<>
<DialogEx tag="jack" onClose={handleClose} aria-labelledby="select-channels-title" open={open}
onEnterKey={() => {
this.handleApply();
this.handleOk();
}}
fullScreen={this.state.fullScreen}
>
<DialogContent>
<div>
<FormControl className={classes.formControl}>
<InputLabel htmlFor="jsd_device">Device</InputLabel>
<Select variant="standard" onChange={(e) => this.handleDeviceChanged(e)}
value={this.state.jackServerSettings.alsaDevice}
style={{width: 220}}
inputProps={{
name: "Device",
id: "jsd_device",
}}
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<div style={{ display: "flex", flexDirection: "row", alignItems: "center", gap: 8 }}>
<div style={{ display: "flex", flexDirection: this.state.compactWidth ? "column" : "row", gap: 8 }}>
{/* Audio Input Device */}
<FormControl variant="standard" className={classes.formControl}>
<InputLabel shrink className={classes.inputLabel} htmlFor="jsd_inputDevice">Input Device</InputLabel>
<Select variant="standard"
id="jsd_inputDevice"
value={this.state.jackServerSettings.alsaInputDevice}
onChange={e => this.handleInputDeviceChanged(e)}
disabled={!this.state.alsaDevices || this.state.alsaDevices.length === 0}
style={{ width: 220 }}
>
{(noDevices && !waitingForDevices) &&
(
<MenuItem value={INVALID_DEVICE_ID}>No suitable devices.</MenuItem>
)
}
{((!noDevices) && !waitingForDevices) && (
this.state.alsaDevices!.map((device) =>
(
<MenuItem key={device.id} value={device.id}>{device.name}</MenuItem>
)
)
)}
{sortedDevices.filter(d => d.supportsCapture).map(d => (
<MenuItem key={d.id} value={d.id}>{d.name}</MenuItem>
)) || <MenuItem value="" disabled>Loading...</MenuItem>}
</Select>
</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>
<FormControl className={classes.formControl}>
<InputLabel htmlFor="jsd_sampleRate">Sample rate</InputLabel>
<FormControl variant="standard" className={classes.formControl}>
<InputLabel shrink className={classes.inputLabel} htmlFor="jsd_sampleRate">Sample rate</InputLabel>
<Select variant="standard"
onChange={(e) => this.handleRateChanged(e)}
value={this.state.jackServerSettings.sampleRate}
@@ -472,16 +744,14 @@ const JackServerSettingsDialog = withStyles(
}
}}
>
{selectedDevice &&
selectedDevice.sampleRates.map((sr) => {
return ( <MenuItem value={sr}>{sr}</MenuItem> );
})
}
{sampleRateOptions.map((sr) => {
return (<MenuItem value={sr}>{sr}</MenuItem> );
})}
</Select>
</FormControl>
<div style={{ display: "inline", whiteSpace: "nowrap" }}>
<FormControl className={classes.formControl}>
<InputLabel htmlFor="bufferSize">Buffer size</InputLabel>
<FormControl variant="standard" className={classes.formControl}>
<InputLabel shrink className={classes.inputLabel} htmlFor="bufferSize">Buffer size</InputLabel>
<Select variant="standard"
onChange={(e) => this.handleSizeChanged(e)}
value={this.state.jackServerSettings.bufferSize}
@@ -499,8 +769,8 @@ const JackServerSettingsDialog = withStyles(
)}
</Select>
</FormControl>
<FormControl className={classes.formControl}>
<InputLabel htmlFor="numberofBuffers">Buffers</InputLabel>
<FormControl variant="standard" className={classes.formControl}>
<InputLabel shrink className={classes.inputLabel} htmlFor="numberofBuffers">Buffers</InputLabel>
<Select variant="standard"
onChange={(e) => this.handleNumberOfBuffersChanged(e)}
value={this.state.jackServerSettings.numberOfBuffers}
@@ -530,16 +800,39 @@ const JackServerSettingsDialog = withStyles(
</DialogContent>
<DialogActions>
<Button variant="dialogSecondary" onClick={handleClose} >
<Button variant="dialogSecondary" onClick={handleClose}>
Cancel
</Button>
<Button variant="dialogPrimary" onClick={() => this.handleApply()}
disabled={!this.state.okEnabled}>
<Button variant="dialogSecondary" onClick={() => this.handleApply()} disabled={!this.state.okEnabled}>Apply</Button>
<Button variant="dialogPrimary" onClick={() => this.handleOk()} disabled={!this.state.okEnabled}>
OK
</Button>
</DialogActions>
</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>
)}
</>
);
}
},