Split IO dialog fixes.

This commit is contained in:
Robin E. R. Davies
2025-09-09 15:25:26 -04:00
parent c5a8345192
commit 367394843a
6 changed files with 443 additions and 474 deletions
+10 -3
View File
@@ -271,7 +271,7 @@ namespace pipedal
auto inAvail = snd_pcm_avail_update(this->captureHandle); auto inAvail = snd_pcm_avail_update(this->captureHandle);
auto outAvail = snd_pcm_avail_update(this->playbackHandle); auto outAvail = snd_pcm_avail_update(this->playbackHandle);
auto total = inAvail + outAvail + framesInBuffer; auto total = (inAvail >= 0? inAvail: 0) + (outAvail >= 0 ? outAvail: 0) + framesInBuffer;
bufferTraces[bufferTraceIndex++] = { bufferTraces[bufferTraceIndex++] = {
time, time,
inAvail, inAvail,
@@ -1180,11 +1180,13 @@ namespace pipedal
throw std::runtime_error("Unable to restart the audio stream."); throw std::runtime_error("Unable to restart the audio stream.");
} }
int err; int err;
if ((err = snd_pcm_start(captureHandle)) < 0) if ((err = snd_pcm_start(captureHandle)) < 0)
{ {
Lv2Log::error(SS("Unable to restart ALSA capture: " << snd_strerror(err))); Lv2Log::error(SS("Unable to restart ALSA capture: " << snd_strerror(err)));
throw PiPedalStateException("Unable to restart ALSA capture."); throw PiPedalStateException("Unable to restart ALSA capture.");
} }
TraceBufferPositions(0,'+');
audioRunning = true; audioRunning = true;
} }
@@ -1518,9 +1520,11 @@ namespace pipedal
} }
snd_pcm_drain(capture_handle); snd_pcm_drain(capture_handle);
FillOutputBuffer(); FillOutputBuffer();
TraceBufferPositions(framesRead, 'x');
} }
else else
{ {
TraceBufferPositions(framesRead, 'z');
Lv2Log::error(SS("Can't recover from ALSA output underrun. (" << snd_strerror(err) << ")")); Lv2Log::error(SS("Can't recover from ALSA output underrun. (" << snd_strerror(err) << ")"));
throw PiPedalStateException(SS("Can't recover from ALSA output error. (" << snd_strerror(err) << ")")); throw PiPedalStateException(SS("Can't recover from ALSA output error. (" << snd_strerror(err) << ")"));
} }
@@ -1621,7 +1625,7 @@ namespace pipedal
// expand running status if neccessary. // expand running status if neccessary.
// deal with regular and sysex messages split across // deal with regular and sysex messages split across
// buffer boundaries (but discard them) // buffer boundaries (but discard them)
snd_pcm_sframes_t framesRead; snd_pcm_sframes_t framesRead = 0;
auto state = snd_pcm_state(handle); auto state = snd_pcm_state(handle);
auto frame_bytes = this->captureFrameSize; auto frame_bytes = this->captureFrameSize;
@@ -2369,6 +2373,8 @@ namespace pipedal
{ {
auto&bufferTrace = bufferTraces[ix]; auto&bufferTrace = bufferTraces[ix];
if (bufferTrace.time != 0)
{
int64_t dt = (int64_t)bufferTrace.time - (int64_t)t0; int64_t dt = (int64_t)bufferTrace.time - (int64_t)t0;
@@ -2376,9 +2382,10 @@ namespace pipedal
<< fixed << setprecision(3) << dt*0.001 << fixed << setprecision(3) << dt*0.001
<< " " << "inAvail: " << bufferTrace.inAvail << " " << "inAvail: " << bufferTrace.inAvail
<< " " << "outAvail: " << bufferTrace.outAvail << " " << "outAvail: " << bufferTrace.outAvail
<< " " << "bufferd: " << bufferTrace.buffered << " " << "buffered: " << bufferTrace.buffered
<< " " << "total: " << bufferTrace.total << " " << "total: " << bufferTrace.total
<< endl; << endl;
}
++ix; ++ix;
if (ix == bufferTraces.size()) if (ix == bufferTraces.size())
+17 -10
View File
@@ -108,10 +108,25 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
info.cardId_ = cardNum; info.cardId_ = cardNum;
info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo); info.id_ = std::string("hw:") + snd_ctl_card_info_get_id(alsaInfo);
const char *driver = snd_ctl_card_info_get_driver(alsaInfo); const char *driver = snd_ctl_card_info_get_driver(alsaInfo);
(void)driver;
info.name_ = snd_ctl_card_info_get_name(alsaInfo); info.name_ = snd_ctl_card_info_get_name(alsaInfo);
info.longName_ = snd_ctl_card_info_get_longname(alsaInfo); info.longName_ = snd_ctl_card_info_get_longname(alsaInfo);
// we can't read our own device if it's open so use data that gets
// cached before we open audio devices.
AlsaDeviceInfo cachedInfo;
if (getCachedDevice(info.name_, &cachedInfo))
{
// may have been plugged into a different USB connector.
cachedInfo.cardId_ = info.cardId_;
cachedInfo.id_ = info.id_;
result.push_back(cachedInfo);
}
else
{
snd_pcm_t *captureDevice = nullptr; snd_pcm_t *captureDevice = nullptr;
snd_pcm_t *playbackDevice = nullptr; snd_pcm_t *playbackDevice = nullptr;
bool captureOk = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) == 0; bool captureOk = snd_pcm_open(&captureDevice, cardId.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) == 0;
@@ -194,16 +209,6 @@ std::vector<AlsaDeviceInfo> PiPedalAlsaDevices::GetAlsaDevices()
cacheDevice(info.name_, info); cacheDevice(info.name_, info);
result.push_back(info); result.push_back(info);
} }
else if (getCachedDevice(info.name_, &info))
{
result.push_back(info);
}
}
else
{
if (getCachedDevice(info.name_, &info))
{
result.push_back(info);
} }
} }
} }
@@ -528,6 +533,8 @@ JSON_MAP_REFERENCE(AlsaDeviceInfo, longName)
JSON_MAP_REFERENCE(AlsaDeviceInfo, sampleRates) JSON_MAP_REFERENCE(AlsaDeviceInfo, sampleRates)
JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize) JSON_MAP_REFERENCE(AlsaDeviceInfo, minBufferSize)
JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize) JSON_MAP_REFERENCE(AlsaDeviceInfo, maxBufferSize)
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsCapture)
JSON_MAP_REFERENCE(AlsaDeviceInfo, supportsPlayback)
JSON_MAP_END() JSON_MAP_END()
JSON_MAP_BEGIN(AlsaMidiDeviceInfo) JSON_MAP_BEGIN(AlsaMidiDeviceInfo)
+12 -4
View File
@@ -237,6 +237,14 @@ void Storage::MaybeCopyDefaultPresets()
} }
} }
static void removeFileNoThrow(const std::filesystem::path &path)
{
try {
fs::remove(path);
} catch (const std::exception&)
{
}
}
void Storage::UpgradeFactoryPresets() void Storage::UpgradeFactoryPresets()
{ {
auto presetsDirectory = this->GetPresetsDirectory(); auto presetsDirectory = this->GetPresetsDirectory();
@@ -256,11 +264,11 @@ void Storage::UpgradeFactoryPresets()
if (defaultConfigPresetsVersion.Version() > presetsVersion.Version() || defaultConfigPresetsVersion.Version() == 0) if (defaultConfigPresetsVersion.Version() > presetsVersion.Version() || defaultConfigPresetsVersion.Version() == 0)
{ {
// remove TooB ML README.md // remove TooB ML README.md
fs::remove("/usr/lib/lv2/ToobAmp.lv2/models/tones/README.md"); removeFileNoThrow("/usr/lib/lv2/ToobAmp.lv2/models/tones/README.md");
fs::remove("/var/pipedal/audio_uploads/ToobMlModels/model.index"); removeFileNoThrow("/var/pipedal/audio_uploads/ToobMlModels/model.index");
fs::remove("/var/pipedal/audio_uploads/ToobMlModels/README.md"); removeFileNoThrow("/var/pipedal/audio_uploads/ToobMlModels/README.md");
fs::remove("/var/pipedal/audio_uploads/ToobMlModels/model.index"); removeFileNoThrow("/var/pipedal/audio_uploads/ToobMlModels/model.index");
std::string name = "Factory Presets"; std::string name = "Factory Presets";
BankFile newFactoryPresets; BankFile newFactoryPresets;
+3 -4
View File
@@ -20,7 +20,7 @@
export default class JackServerSettings { export default class JackServerSettings {
deserialize(input: any) : JackServerSettings{ deserialize(input: any): JackServerSettings {
this.valid = input.valid; this.valid = input.valid;
this.isOnboarding = input.isOnboarding; this.isOnboarding = input.isOnboarding;
this.isJackAudio = input.isJackAudio; this.isJackAudio = input.isJackAudio;
@@ -41,8 +41,7 @@ export default class JackServerSettings {
// this.valid = true; // this.valid = true;
// } // }
// } // }
clone(): JackServerSettings clone(): JackServerSettings {
{
return new JackServerSettings().deserialize(this); return new JackServerSettings().deserialize(this);
} }
valid: boolean = false; valid: boolean = false;
@@ -86,7 +85,7 @@ export default class JackServerSettings {
return inDev; return inDev;
} else { } else {
return inDev+" "+outDev; return inDev + "-> " + outDev;
} }
} }
+171 -222
View File
@@ -21,7 +21,7 @@
import { Theme } from '@mui/material/styles'; import { Theme } from '@mui/material/styles';
import WithStyles from './WithStyles'; import WithStyles from './WithStyles';
import {createStyles} from './WithStyles'; import { createStyles } from './WithStyles';
import { withStyles } from "tss-react/mui"; import { withStyles } from "tss-react/mui";
@@ -29,6 +29,7 @@ import Button from '@mui/material/Button';
import DialogActions from '@mui/material/DialogActions'; import DialogActions from '@mui/material/DialogActions';
import DialogEx from './DialogEx'; import DialogEx from './DialogEx';
import JackServerSettings from './JackServerSettings'; import JackServerSettings from './JackServerSettings';
import JackHostStatus from './JackHostStatus';
import InputLabel from '@mui/material/InputLabel'; import InputLabel from '@mui/material/InputLabel';
@@ -60,7 +61,7 @@ function sortDevices(devices: AlsaDeviceInfo[]): AlsaDeviceInfo[] {
return 2; return 2;
} }
let copy = [...devices]; let copy = [...devices];
copy.sort((a,b) => { copy.sort((a, b) => {
let ca = category(a); let ca = category(a);
let cb = category(b); let cb = category(b);
if (ca !== cb) return ca - cb; if (ca !== cb) return ca - cb;
@@ -72,6 +73,8 @@ function sortDevices(devices: AlsaDeviceInfo[]): AlsaDeviceInfo[] {
const MIN_BUFFER_SIZE = 16; const MIN_BUFFER_SIZE = 16;
const MAX_BUFFER_SIZE = 2048; const MAX_BUFFER_SIZE = 2048;
const valid_buffer_sizes = [16, 32, 48, 64, 128, 256, 512, 1024, 2048];
interface BufferSetting { interface BufferSetting {
bufferSize: number; bufferSize: number;
@@ -80,15 +83,23 @@ interface BufferSetting {
// empty string used when no valid device is selected - the default. // empty string used when no valid device is selected - the default.
const INVALID_DEVICE_ID = ""; const INVALID_DEVICE_ID = "";
enum WarningDialogType {
None,
Apply,
Ok,
};
interface JackServerSettingsDialogState { interface JackServerSettingsDialogState {
latencyText: string; latencyText: string;
jackServerSettings: JackServerSettings; jackServerSettings: JackServerSettings;
jackHostStatus?: JackHostStatus;
alsaDevices?: AlsaDeviceInfo[]; alsaDevices?: AlsaDeviceInfo[];
okEnabled: boolean; okEnabled: boolean;
fullScreen: boolean; fullScreen: boolean;
compactWidth: boolean; compactWidth: boolean;
showDeviceWarning: boolean; warningDialogType: WarningDialogType;
dontShowWarningAgain: boolean; suppressDeviceWarning: boolean;
windowSize: { width: number, height: number};
} }
const styles = (theme: Theme) => const styles = (theme: Theme) =>
@@ -111,7 +122,7 @@ export interface JackServerSettingsDialogProps extends WithStyles<typeof styles>
onApply: (jackServerSettings: JackServerSettings) => void; onApply: (jackServerSettings: JackServerSettings) => void;
} }
function getLatencyText(settings?: JackServerSettings ): string { function getLatencyText(settings?: JackServerSettings): string {
if (!settings) { if (!settings) {
return "\u00A0"; return "\u00A0";
} }
@@ -124,26 +135,21 @@ function getLatencyText(settings?: JackServerSettings ): string {
return ms.toFixed(1) + "ms"; return ms.toFixed(1) + "ms";
} }
function getValidBufferCounts(bufferSize: number, alsaDeviceInfo?: AlsaDeviceInfo): number[] function getValidBufferCounts(bufferSize: number, alsaDeviceInfo?: AlsaDeviceInfo): number[] {
{ if (!alsaDeviceInfo) {
if (!alsaDeviceInfo)
{
return []; return [];
} }
let result: number[] = []; let result: number[] = [];
if (bufferSize * 2 >= alsaDeviceInfo.minBufferSize) if (bufferSize * 2 >= alsaDeviceInfo.minBufferSize) {
{ result = [2, 3, 4, 5, 6];
result = [2,3,4,5,6];
} else { } else {
let minBuffers = Math.ceil(alsaDeviceInfo.minBufferSize/bufferSize/2-0.0001); let minBuffers = Math.ceil(alsaDeviceInfo.minBufferSize / bufferSize / 2 - 0.0001);
result = [minBuffers*2,minBuffers*3, minBuffers*4, minBuffers*5, minBuffers*6]; result = [minBuffers * 2, minBuffers * 3, minBuffers * 4, minBuffers * 5, minBuffers * 6];
} }
for (let i = 0; i < result.length; ++i) for (let i = 0; i < result.length; ++i) {
{ let selectedSize = result[i] * bufferSize;
let selectedSize = result[i]*bufferSize; if (selectedSize < alsaDeviceInfo.minBufferSize || selectedSize > alsaDeviceInfo.maxBufferSize) {
if (selectedSize < alsaDeviceInfo.minBufferSize || selectedSize > alsaDeviceInfo.maxBufferSize) result.splice(i, 1);
{
result.splice(i,1);
--i; --i;
} }
} }
@@ -166,18 +172,15 @@ function getValidBufferSizesMultiple(inDevice?: AlsaDeviceInfo, outDevice?: Alsa
if (!outDevice) return s1; if (!outDevice) return s1;
return intersectArrays(s1, getValidBufferSizes(outDevice)); return intersectArrays(s1, getValidBufferSizes(outDevice));
} }
function getValidBufferSizes(alsaDeviceInfo? : AlsaDeviceInfo): number[] function getValidBufferSizes(alsaDeviceInfo?: AlsaDeviceInfo): number[] {
{ if (!alsaDeviceInfo) {
if (!alsaDeviceInfo )
{
return []; return [];
} }
let result: number[] = []; let result: number[] = [];
for (let i = MIN_BUFFER_SIZE; i <= MAX_BUFFER_SIZE; i *= 2) for (let i = 0; i < valid_buffer_sizes.length; i++) {
{ let v = valid_buffer_sizes[i];
if (getValidBufferCounts(i,alsaDeviceInfo).length !== 0) if (getValidBufferCounts(v, alsaDeviceInfo).length !== 0) {
{ result.push(v);
result.push(i);
} }
} }
return result; return result;
@@ -188,49 +191,43 @@ function getBestBuffers(
bufferSize: number, bufferSize: number,
numberOfBuffers: number, numberOfBuffers: number,
bufferSizeChanging: boolean = false bufferSizeChanging: boolean = false
) : BufferSetting { ): BufferSetting {
if (!inDevice && !outDevice) if (!inDevice && !outDevice) {
{ return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers };
return { bufferSize:bufferSize, numberOfBuffers: numberOfBuffers};
} }
// If the numberOfbuffers is fine as is, don't change the number of Buffers. // If the numberOfbuffers is fine as is, don't change the number of Buffers.
// set default values. Otherwise, choose the best buffer count from available buffer counts. // set default values. Otherwise, choose the best buffer count from available buffer counts.
let validBuffercounts = getValidBufferCountsMultiple(bufferSize,inDevice,outDevice); let validBuffercounts = getValidBufferCountsMultiple(bufferSize, inDevice, outDevice);
const minSize = Math.max(inDevice?.minBufferSize ?? MIN_BUFFER_SIZE, outDevice?.minBufferSize ?? MIN_BUFFER_SIZE); const minSize = Math.max(inDevice?.minBufferSize ?? MIN_BUFFER_SIZE, outDevice?.minBufferSize ?? MIN_BUFFER_SIZE);
const maxSize = Math.min(inDevice?.maxBufferSize ?? MAX_BUFFER_SIZE, outDevice?.maxBufferSize ?? MAX_BUFFER_SIZE); const maxSize = Math.min(inDevice?.maxBufferSize ?? MAX_BUFFER_SIZE, outDevice?.maxBufferSize ?? MAX_BUFFER_SIZE);
let ix = validBuffercounts.indexOf(numberOfBuffers); let ix = validBuffercounts.indexOf(numberOfBuffers);
if (ix !== -1) { if (ix !== -1) {
if (bufferSize*numberOfBuffers <= maxSize if (bufferSize * numberOfBuffers <= maxSize
&& bufferSize*numberOfBuffers >= minSize) && bufferSize * numberOfBuffers >= minSize) {
{ return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers };
return {bufferSize: bufferSize, numberOfBuffers: numberOfBuffers};
} }
} }
// if numberOfBuffers is not valid, and the user has just selected a new buffers size, // if numberOfBuffers is not valid, and the user has just selected a new buffers size,
// then choose from available buffer counts. // then choose from available buffer counts.
if (bufferSizeChanging) if (bufferSizeChanging) {
{ if (validBuffercounts.length !== 0) {
if (validBuffercounts.length !== 0)
{
// prefer the one thats divisible by 3. // prefer the one thats divisible by 3.
for (let i = 0; i < validBuffercounts.length; ++i) for (let i = 0; i < validBuffercounts.length; ++i) {
{ if (validBuffercounts[i] % 3 === 0) {
if (validBuffercounts[i] % 3 === 0)
{
numberOfBuffers = validBuffercounts[i]; numberOfBuffers = validBuffercounts[i];
return {bufferSize: bufferSize, numberOfBuffers:numberOfBuffers}; return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers };
} }
} }
numberOfBuffers = validBuffercounts[0]; numberOfBuffers = validBuffercounts[0];
return {bufferSize: bufferSize, numberOfBuffers:numberOfBuffers}; return { bufferSize: bufferSize, numberOfBuffers: numberOfBuffers };
} }
} }
// otherwise select a sensible starting value. // otherwise select a sensible starting value.
// Build candidate sizes with 32 first if present. // Build candidate sizes with 32 first if present.
let validBufferSizes = getValidBufferSizesMultiple(inDevice, outDevice); let validBufferSizes = getValidBufferSizesMultiple(inDevice, outDevice);
validBufferSizes.sort((a,b)=>a-b); validBufferSizes.sort((a, b) => a - b);
if (validBufferSizes.indexOf(32) !== -1) { if (validBufferSizes.indexOf(32) !== -1) {
validBufferSizes = [32, ...validBufferSizes.filter(v => v !== 32)]; validBufferSizes = [32, ...validBufferSizes.filter(v => v !== 32)];
} }
@@ -253,10 +250,10 @@ function getBestBuffers(
if (result) return result; if (result) return result;
// Fallback to previous behaviour. // Fallback to previous behaviour.
if (64*3 >= minSize && 64*3 <= maxSize) { if (64 * 3 >= minSize && 64 * 3 <= maxSize) {
return { bufferSize: 64, numberOfBuffers: 3 }; return { bufferSize: 64, numberOfBuffers: 3 };
} }
bufferSize = minSize/2; bufferSize = minSize / 2;
numberOfBuffers = 4; numberOfBuffers = 4;
if (bufferSize * numberOfBuffers > maxSize) { if (bufferSize * numberOfBuffers > maxSize) {
numberOfBuffers = 2; numberOfBuffers = 2;
@@ -265,8 +262,7 @@ function getBestBuffers(
}; };
function isOkEnabled(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) function isOkEnabled(jackServerSettings: JackServerSettings, alsaDevices?: AlsaDeviceInfo[]) {
{
if (!alsaDevices) return false; if (!alsaDevices) return false;
if (!jackServerSettings.alsaInputDevice || !jackServerSettings.alsaOutputDevice) return false; if (!jackServerSettings.alsaInputDevice || !jackServerSettings.alsaOutputDevice) return false;
@@ -293,45 +289,41 @@ const JackServerSettingsDialog = withStyles(
class extends ResizeResponsiveComponent<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"; let suppressWarnings = true; //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(), fullScreen: this.getFullScreen(),
compactWidth: document.documentElement.clientWidth < 600, compactWidth: this.windowSize.width < 600,
showDeviceWarning: false, warningDialogType: WarningDialogType.None,
dontShowWarningAgain: false suppressDeviceWarning: suppressWarnings,
windowSize: this.windowSize,
jackHostStatus: undefined
}; };
} }
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() { getFullScreen() {
return document.documentElement.clientWidth < 420 || return false;
document.documentElement.clientHeight < 700; // return document.documentElement.clientWidth < 420 ||
// document.documentElement.clientHeight < 700;
} }
onWindowSizeChanged(width: number, height: number): void { onWindowSizeChanged(width: number, height: number): void {
super.onWindowSizeChanged(width, height); // super.onWindowSizeChanged(width, height);
const fullScreen = this.getFullScreen(); // const fullScreen = this.getFullScreen();
const compactWidth = width < 600; const compactWidth = width < 600;
if (fullScreen !== this.state.fullScreen || compactWidth !== this.state.compactWidth) { this.setState({ compactWidth: compactWidth, windowSize: { width: width, height: height } });
this.setState({ fullScreen, compactWidth });
}
} }
requestAlsaInfo() { requestAlsaInfo() {
@@ -357,59 +349,6 @@ const JackServerSettingsDialog = withStyles(
}); });
} }
/** 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 || alsaDevices.length === 0) { if (!alsaDevices || alsaDevices.length === 0) {
@@ -463,48 +402,70 @@ const JackServerSettingsDialog = withStyles(
return result; return result;
} }
tick() {
this.model.getJackStatus()
.then((jackStatus) =>
this.setState(
{
jackHostStatus: jackStatus
})
)
.catch((error) => { });
}
componentDidMount() { componentDidMount() {
super.componentDidMount(); super.componentDidMount();
this.mounted = true; this.mounted = true;
this.ignoreClose = false;
if (this.props.open) { this.updateActive();
this.requestAlsaInfo();
this.saveSettingsTemporary(this.props.jackServerSettings);
}
} }
componentDidUpdate(oldProps: JackServerSettingsDialogProps) { private active: boolean = false;
if ((this.props.open && !oldProps.open) && this.mounted) { private timerHandle: number | null = null;
this.ignoreClose = false;
this.saveSettingsTemporary(this.props.jackServerSettings); updateActive() {
// When opening, preserve the current settings until ALSA device let active = this.mounted && this.props.open;
// information is loaded. If we don't have device info yet, just if (active !== this.active) {
// clone the provided settings without applying device defaults. this.active = active;
let settings = this.state.alsaDevices if (this.active) {
? this.applyAlsaDevices(this.props.jackServerSettings.clone(), this.state.alsaDevices)
: this.props.jackServerSettings.clone();
this.setState({ this.setState({
jackServerSettings: settings, jackServerSettings: this.props.jackServerSettings.clone(),
latencyText: getLatencyText(settings), alsaDevices: undefined,
okEnabled: isOkEnabled(settings, this.state.alsaDevices) latencyText: "",
jackHostStatus: undefined,
okEnabled: false
}); });
if (!this.state.alsaDevices) {
this.requestAlsaInfo(); this.requestAlsaInfo();
}
} else if (!this.props.open && oldProps.open) { this.timerHandle = setInterval(() => this.tick(), 1000);
this.originalJackServerSettings = undefined;
} else {
if (this.timerHandle !== null) {
clearInterval(this.timerHandle);
this.timerHandle = null;
} }
} }
}
}
componentDidUpdate(oldProps: JackServerSettingsDialogProps) {
this.updateActive();
}
componentWillUnmount() { componentWillUnmount() {
super.componentWillUnmount(); super.componentWillUnmount();
this.mounted = false; this.mounted = false;
if (this.originalJackServerSettings) {
// Revert any unapplied changes when the dialog is unmounted this.updateActive();
this.applySettings(this.originalJackServerSettings);
this.originalJackServerSettings = undefined;
}
} }
getSelectedDevice(deviceId: string): AlsaDeviceInfo | undefined { getSelectedDevice(deviceId: string): AlsaDeviceInfo | undefined {
@@ -530,7 +491,7 @@ const JackServerSettingsDialog = withStyles(
this.setState({ this.setState({
jackServerSettings: settings, jackServerSettings: settings,
latencyText: getLatencyText(settings), latencyText: getLatencyText(settings),
okEnabled: isOkEnabled(settings,this.state.alsaDevices) okEnabled: isOkEnabled(settings, this.state.alsaDevices)
}); });
} }
handleSizeChanged(e: any) { handleSizeChanged(e: any) {
@@ -542,14 +503,14 @@ const JackServerSettingsDialog = withStyles(
let settings = this.state.jackServerSettings.clone(); let settings = this.state.jackServerSettings.clone();
settings.bufferSize = size; settings.bufferSize = size;
settings.valid = false; settings.valid = false;
let bestBufferSetting = getBestBuffers(inDev,outDev,settings.bufferSize,settings.numberOfBuffers,true); 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;
this.setState({ this.setState({
jackServerSettings: settings, jackServerSettings: settings,
latencyText: getLatencyText(settings), latencyText: getLatencyText(settings),
okEnabled: isOkEnabled(settings,this.state.alsaDevices) okEnabled: isOkEnabled(settings, this.state.alsaDevices)
}); });
} }
handleNumberOfBuffersChanged(e: any) { handleNumberOfBuffersChanged(e: any) {
@@ -565,66 +526,57 @@ const JackServerSettingsDialog = withStyles(
this.setState({ this.setState({
jackServerSettings: settings, jackServerSettings: settings,
latencyText: getLatencyText(settings), latencyText: getLatencyText(settings),
okEnabled: isOkEnabled(settings,this.state.alsaDevices) okEnabled: isOkEnabled(settings, this.state.alsaDevices)
}); });
} }
handleApply() { applySettings() {
if (this.state.okEnabled) { const settings = this.state.jackServerSettings.clone();
this.applySettings(); // Fire and forget settings.valid = true;
this.props.onApply(settings);
} }
handleApply() {
if (!this.state.okEnabled) return;
if (this.state.jackServerSettings.alsaInputDevice !== this.state.jackServerSettings.alsaOutputDevice
&& this.state.suppressDeviceWarning === false) {
this.setState({ warningDialogType: WarningDialogType.Apply });
} else {
this.applySettings();
}; };
}
handleOk() { handleOk() {
if (!this.state.okEnabled) return; 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 if (this.state.jackServerSettings.alsaInputDevice !== this.state.jackServerSettings.alsaOutputDevice
&& !this.suppressDeviceWarning) { && this.state.suppressDeviceWarning === false) {
this.setState({ showDeviceWarning: true }); this.setState({ warningDialogType: WarningDialogType.Ok });
return; } else {
this.applySettings();
this.props.onClose(); // Close the dialog
} }
proceedWithOk();
}; };
handleWarningProceed() { handleWarningProceed() {
if (this.state.dontShowWarningAgain) { this.setState({ warningDialogType: WarningDialogType.None });
localStorage.setItem("suppressSeparateDeviceWarning", "1");
this.suppressDeviceWarning = true; if (this.state.warningDialogType === WarningDialogType.Ok) {
} this.applySettings();
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 this.props.onClose(); // Close the dialog after the warning
}); } else if (this.state.warningDialogType === WarningDialogType.Apply) {
this.applySettings();
}
} }
handleWarningCancel() { handleWarningCancel() {
this.setState({ showDeviceWarning: false, dontShowWarningAgain: false }); this.setState({ warningDialogType: WarningDialogType.None });
} }
handleWarningCheck(e: any, checked: boolean) { handleWarningCheck(e: any, checked: boolean) {
this.setState({ dontShowWarningAgain: checked }); this.setState({ suppressDeviceWarning: checked });
localStorage.setItem("suppressSeparateDeviceWarning", checked ? "1" : "0");
} }
handleInputDeviceChanged(e: any) { handleInputDeviceChanged(e: any) {
const d = e.target.value as string; const d = e.target.value as string;
@@ -652,24 +604,14 @@ const JackServerSettingsDialog = withStyles(
}); });
} }
handleClose() {
this.props.onClose();
};
render() { render() {
const classes = withStyles.getClasses(this.props); 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 = () => {
if (this.ignoreClose) {
return;
} else {
if (this.originalJackServerSettings) {
// Revert any applied settings
this.applySettings(this.originalJackServerSettings); // Fire and forget
this.originalJackServerSettings = undefined;
}
}
onClose();
};
const sortedDevices = sortDevices(this.state.alsaDevices ?? []); const sortedDevices = sortDevices(this.state.alsaDevices ?? []);
@@ -688,10 +630,10 @@ const JackServerSettingsDialog = withStyles(
intersectArrays(selectedInputDevice.sampleRates, selectedOutputDevice.sampleRates) intersectArrays(selectedInputDevice.sampleRates, selectedOutputDevice.sampleRates)
: []; : [];
let sampleRateOptions = sampleRates.length === 0 && this.state.jackServerSettings.sampleRate ? [this.state.jackServerSettings.sampleRate] : sampleRates; let sampleRateOptions = sampleRates.length === 0 && this.state.jackServerSettings.sampleRate ? [this.state.jackServerSettings.sampleRate] : sampleRates;
let ultraCompact = this.windowSize.width < 500;
return ( return (
<> <>
<DialogEx tag="jack" onClose={handleClose} aria-labelledby="select-channels-title" open={open} <DialogEx tag="jack" onClose={() => { this.handleClose(); }} aria-labelledby="select-channels-title" open={this.props.open}
onEnterKey={() => { onEnterKey={() => {
this.handleOk(); this.handleOk();
}} }}
@@ -748,17 +690,18 @@ const JackServerSettingsDialog = withStyles(
inputProps={{ inputProps={{
id: 'jsd_sampleRate', id: 'jsd_sampleRate',
style: { style: {
textAlign: "right" textAlign: "right",
} }
}} }}
> >
{sampleRateOptions.map((sr) => { {sampleRateOptions.map((sr) => {
return (<MenuItem value={sr}>{sr}</MenuItem> ); return (<MenuItem key={sr} value={sr}>{sr}</MenuItem>);
})} })}
</Select> </Select>
</FormControl> </FormControl>
<div style={{ display: "inline", whiteSpace: "nowrap" }}> <div style={{ display: "flex", flexFlow: "row wrap", justifyContent: "start" }}>
<FormControl variant="standard" className={classes.formControl}> <FormControl variant="standard" className={classes.formControl}
>
<InputLabel shrink className={classes.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)}
@@ -771,7 +714,7 @@ const JackServerSettingsDialog = withStyles(
> >
{bufferSizes.map((buffSize) => {bufferSizes.map((buffSize) =>
( (
<MenuItem key={"b"+buffSize} value={buffSize}>{buffSize}</MenuItem> <MenuItem key={"b" + buffSize} value={buffSize}>{buffSize}</MenuItem>
) )
)} )}
@@ -789,8 +732,7 @@ const JackServerSettingsDialog = withStyles(
}} }}
> >
{ {
bufferCounts.map((bufferCount)=> bufferCounts.map((bufferCount) => {
{
return ( return (
<MenuItem key={bufferCount} value={bufferCount}>{bufferCount.toString()}</MenuItem> <MenuItem key={bufferCount} value={bufferCount}>{bufferCount.toString()}</MenuItem>
@@ -801,14 +743,21 @@ const JackServerSettingsDialog = withStyles(
</FormControl> </FormControl>
</div> </div>
</div> </div>
<Typography display="block" variant="caption" style={{ textAlign: "left", marginTop: 8, marginLeft: 24 }} <Typography display="block" variant="caption" style={{ textAlign: "left", marginTop: 12, marginLeft: 24 }}
color="textSecondary"> color="textSecondary">
Latency: {this.state.latencyText} Latency: {this.state.latencyText}
</Typography> </Typography>
<Typography display="block" variant="caption" style={{ textAlign: "left", marginTop: 0, marginLeft: 24 }}
color="textSecondary">
{
JackHostStatus.getDisplayView("Status: ", this.state.jackHostStatus)
}
</Typography>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button variant="dialogSecondary" onClick={handleClose}> <Button variant="dialogSecondary" onClick={() => { this.handleClose(); }}>
Cancel Cancel
</Button> </Button>
<Button variant="dialogSecondary" onClick={() => this.handleApply()} disabled={!this.state.okEnabled}>Apply</Button> <Button variant="dialogSecondary" onClick={() => this.handleApply()} disabled={!this.state.okEnabled}>Apply</Button>
@@ -818,19 +767,19 @@ const JackServerSettingsDialog = withStyles(
</DialogActions> </DialogActions>
</DialogEx> </DialogEx>
{this.state.showDeviceWarning && ( {this.state.warningDialogType !== WarningDialogType.None && (
<DialogEx open={this.state.showDeviceWarning} tag="ioWarning" <DialogEx open={true} tag="ioWarning"
onEnterKey={() => this.handleWarningProceed()} onEnterKey={() => this.handleWarningProceed()}
onClose={() => this.handleWarningCancel()} onClose={() => this.handleWarningCancel()}
style={{ userSelect: "none" }} style={{ userSelect: "none" }}
> >
<DialogContent> <DialogContent>
<Typography variant="body2" color="textPrimary" gutterBottom> <Typography display="block" variant="body2" color="textSecondary" style={{ fontSize: 16, fontWeight: 400 }} gutterBottom>
Using different input and output devices may cause issues when streaming audio. For best results, you should the same device for both audio input and output.
Are you sure you want to continue? Are you sure you want to continue?
</Typography> </Typography>
<FormControlLabel <FormControlLabel
control={<Checkbox checked={this.state.dontShowWarningAgain} onChange={(e,c)=>this.handleWarningCheck(e,c)} />} control={<Checkbox checked={this.state.suppressDeviceWarning} onChange={(e, c) => this.handleWarningCheck(e, c)} />}
label="Don't show me this message again" label="Don't show me this message again"
/> />
</DialogContent> </DialogContent>
-1
View File
@@ -702,7 +702,6 @@ const SettingsDialog = withStyles(
onClose={() => this.setState({ showJackServerSettingsDialog: false })} onClose={() => this.setState({ showJackServerSettingsDialog: false })}
onApply={(jackServerSettings) => { onApply={(jackServerSettings) => {
this.setState({ this.setState({
showJackServerSettingsDialog: false,
jackServerSettings: jackServerSettings jackServerSettings: jackServerSettings
}); });
this.model.setJackServerSettings(jackServerSettings); this.model.setJackServerSettings(jackServerSettings);